Внутреннее устройство ядра Linux 2.4

       

Очереди ожидания (Wait Queues)


Когда процесс передает ядру запрос, который не может быть исполнен сразу же, то процесс "погружается в сон" и "пробуждается", когда запрос может быть удовлетворен. Один из механизмов ядра для реализации подобного поведения называется "wait queue" (очередь ожидания).

Реализация в Linux позволяет использовать семантику "индивидуального пробуждения" с помощью флага TASK_EXCLUSIVE. При использовании механизма waitqueues, можно использовать существующую очередь и просто вызывать sleep_on/sleep_on_timeout/interruptible_sleep_on/interruptible_sleep_on_timeout, либо можно определить свою очередь ожидания и использовать add/remove_wait_queue для добавления и удаления задач в/из нее и wake_up/wake_up_interruptible - для "пробуждения" их по мере необходимости

Пример первого варианта использования очередей ожидания - это взаимодействие между менеджером страниц (page allocator) (в mm/page_alloc.c:__alloc_pages()) и демоном kswapd (в mm/vmscan.c:kswap()). Посредством очереди ожидания kswapd_wait,, объявленной в mm/vmscan.c; демон kswapd бездействует в этой очереди и "пробуждается" как только менеджеру страниц (page allocator) требуется освободить какие-либо страницы.

Примером использования автономной очереди может служить взаимодействие между пользовательским процессом, запрашивающим данные через системный вызов read(2), и ядром, передающим данные, в контексте прерывания. Пример обработчика может выглядеть примерно так (упрощенный код из drivers/char/rtc_interrupt()):

static DECLARE_WAIT_QUEUE_HEAD(rtc_wait);

void rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs) { spin_lock(&rtc_lock); rtc_irq_data = CMOS_READ(RTC_INTR_FLAGS); spin_unlock(&rtc_lock); wake_up_interruptible(&rtc_wait); }

Обработчик прерывания считывает данные с некоторого устройства (макрокоманда CMOS_READ()) и затем "будит" всех, кто находится в очереди ожидания rtc_wait.

Системный вызов read(2) мог бы быть реализован так:


ssize_t rtc_read(struct file file, char *buf, size_t count, loff_t *ppos) { DECLARE_WAITQUEUE(wait, current); unsigned long data; ssize_t retval;

add_wait_queue(&rtc_wait, &wait); current->state = TASK_INTERRUPTIBLE; do { spin_lock_irq(&rtc_lock); data = rtc_irq_data; rtc_irq_data = 0; spin_unlock_irq(&rtc_lock);

if (data != 0) break;

if (file->f_flags & O_NONBLOCK) { retval = -EAGAIN; goto out; } if (signal_pending(current)) { retval = -ERESTARTSYS; goto out; } schedule(); } while(1); retval = put_user(data, (unsigned long *)buf); if (!retval) retval = sizeof(unsigned long);



out: current->state = TASK_RUNNING; remove_wait_queue(&rtc_wait, &wait); return retval; }

Разберем функцию rtc_read():

  • Объявляется новый элемент очереди ожидания указывающий на текщий процесс.


  • Этот элемент добавляется в очередь rtc_wait.


  • Текущий процесс переводится в состояние TASK_INTERRUPTIBLE которое предполагает, что процесс не должен учавствовать в процессе планирования.


  • Проверяется - доступны ли данные. Если да - то цикл прерывается, данные копируются в пользовательский буфер, процесс переводится в состояние TASK_RUNNING, удаляется из очереди и производится возврат.


  • Если данные недоступны, а пользователь запросил неблокирующую опрацию ввода-вывода, то возвращается код ошибки EAGAIN (который имеет тоже значение, что и EWOULDBLOCK)


  • При наличии ожидающих обработки сигналов - "верхнему уровню" сообщается, что системный вызов должен быть перезапущен, если это необходимо. Под "если это необходимо" подразумеваются детали размещения сигнала, как это определено в системном вызове sigaction(2)


  • Далее задача "отключается", т.е. "засыпает", до "пробуждения" обработчиком прерывания. Если не переводить процесс в состояние TASK_INTERRUPTIBLE то планировщик может вызвать задачу раньше, чем данные будут доступны, выполняя тем самым ненужную работу.


  • Следует так же указать, что с помощью очередей ожидания реализация системного вызова poll(2)

    становится более простой.

    static unsigned int rtc_poll(struct file *file, poll_table *wait) { unsigned long l;

    poll_wait(file, &rtc_wait, wait);

    spin_lock_irq(&rtc_lock); l = rtc_irq_data; spin_unlock_irq(&rtc_lock);

    if (l != 0) return POLLIN | POLLRDNORM; return 0; }

    Вся работа выполняется независимой от типа устройства функцией poll_wait(), которая выполняет необходимые манипуляции; все что требуется сделать - это указать очередь,, которую следует "разбудить" обработчиком прерываний от устройства.


    Содержание раздела