Существуют простые интерфейсы для перехода в приостановленное состояние, и они широко используются. Однако использование этих интерфейсов может привести к состояниям конкуренции: возможен переход в приостановленное состояние
/* пусть q — это очередь ожидания (созданная в другом месте) ,
где мы хотим находиться в приостановленном состоянии */
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(q, &wait);
set_current_state(TASK_INTERRUPTIBLE); /* или TASK_UNINTERRUPTIBLE */
/* переменная condition характеризует наступление события,
которого мы ожидаем */
while (!condition)
schedule();
set_current_state(TASK_RUNNING);
remove_wait_queue(q, &wait);
Опишем шаги, которые должна проделать задача для того, чтобы поместить себя в очередь ожидания.
• Создать элемент очереди ожидания с помощью макроса DECLARE_WAITQUEUE()
.
• Добавить себя в очередь ожидания с помощью функции add_wait_queue()
. С помощью этой очереди ожидания процесс будет возвращен в состояние готовности к выполнению, когда условие, на выполнение которого ожидает процесс, будет выполнено. Конечно, для этого где-то в другом месте должен быть код, который вызывает функцию wake_up()
для данной очереди, когда произойдет соответствующее событие.
• Изменить состояние процесса в значение TASK_INTERRUPTIBLE
или TASK_UNINTERRUPTIBLE
.
• Проверить, не выполнилось ли ожидаемое условие. Если выполнилось, то больше нет необходимости переходить в приостановленное состояние. Если нет, то вызвать функцию schedule()
.
• Когда задача становится готовой к выполнению, она снова проверяет выполнение ожидаемого условия. Если условие выполнено, то производится выход из цикла. Если нет, то снова вызывается функция schedule()
и повторяется проверка условия.
• Когда условие выполнено, задача может установить свое состояние в значение TASK_RUNNING
и удалить себя из очереди ожидания с помощью функции remove_wait_queue()
.
Если условие выполнится перед тем, как задача переходит в приостановленное состояние, то цикл прервется и задача не перейдет в приостановленное состояние по ошибке. Следует заметить, что во время выполнения тела цикла код ядра часто может выполнять и другие задачи. Например, перед выполнением функции schedule()
может возникнуть необходимость освободить некоторые блокировки и захватить их снова после возврата из этой функции; если процессу был доставлен сигнал, то необходимо возвратить значение -ERESTARTSYS
; может возникнуть необходимость отреагировать на некоторые другие события.
Возврат к выполнению (wake up) производится с помощью функции wake_up()
, которая возвращает все задачи, ожидающие в данной очереди, в состояние готовности к выполнению. Вначале вызывается функция try_to_wake_up()
, которая устанавливает поле состояния задачи в значение TASK_RUNNING
, далее вызывается функция activate_task()
для добавления задачи в очередь выполнения и устанавливается флаг need_resched
в ненулевое значение, если приоритет задачи, которая возвращается к выполнению, больше приоритета текущей задачи. Код, который отвечает за наступление некоторого события, обычно вызывает функцию wake_up()
после того, как это событие произошло. Например, после того как данные прочитаны с жесткого диска, подсистема VFS вызывает функцию wake_up()
для очереди ожидания, которая содержит все процессы, ожидающие поступления данных.
Важным может быть замечание о том, что переход в приостановленное состояние часто сопровождается ложными переходами к выполнению. Это возникает потому, что переход задачи в состояние выполнения не означает, что событие, которого ожидала задача, уже наступило: поэтому переход в приостановленное состояние должен всегда выполняться в цикле, который гарантирует, что условие, на которое ожидает задача, действительно выполнилось (рис. 4.3).
Рис. 4.3. Переход в приостановленное состояние (sleeping) и возврат к выполнению (wake up)
Балансировка нагрузки