netlib.narod.ru< Назад | Оглавление | Далее >

Очереди ожидания

В предыдущем разделе кратко упоминалось о том, что процессы (т.е. выполняющиеся программы) могут быть переведены в состояние ожидания определенного события («спящее» состояние) и выведены из этого состояния после прихода события. Реализованная в ядре технология заключается в связывании с каждым событием очереди ожидания (wait queue). Процесс, который должен получить событие, переводится в режим ожидания и помещается в очередь. После прихода события ядро сканирует очередь и активизирует ожидающие задания. Проблема удаления из очереди находится полностью в компетенции заданий.

Очереди ожидания — на удивление мощный механизм, используемый повсеместно во всем ядре. К тому же реализация этого механизма требует не столь уж много кода.

struct wait_queue

wait_event

Код wait_event заключен в несколько необычную конструкцию:

do {
  /* . . . */
} while (0)

Этот небольшой трюк не настолько хорошо известен, как он того заслуживает. Идея состоит в том, чтобы заставить помещенный внутрь конструкции код действовать подобно одному оператору. Рассмотрим следующий макрос, который вызывает free, если p является ненулевым указателем:

#define  FREE1(p)   if  (p)  free(p)

Все хорошо до тех пор, пока FREE1 не будет задействован в ситуации наподобие:

if (expression)
  FREE1(p);
else
  printf ("expression ложно.\n");

После разворачивания макроса FREE1 конструкция else ассоциируется не с тем if (т.е. с if относящимся к FREE1).

Я наблюдал, как некоторые программисты так решали подобную проблему:

#define  FREE2(p)   if  (p) { free(p); }
#define  FREE3(p)   { if  (p) { free(p); } }

Ни одно из вышеприведенных решений нельзя считать удовлетворительным — точка с запятой, которую программист естественно ставит после вызова макроса, портит разворачиваемый текст. Возьмите, к примеру, FREE2. После разворачивания макроса и добавления отступов для лучшего чтения компилятор получит такой текст:

if  (expression)
  if  (p) { free(p); }
;
else
  printf("expression ложно.\n");

Результат — синтаксическая ошибка, поскольку else не связан ни с одним if. Аналогичная проблема имеет место и с FREE3. После некоторых размышлений становится ясно, что абсолютно неважно, есть ли if внутри тела макроса. Ту же проблему можно получить и в результате заключения тела макроса в скобки, причем неважно, что находится внутри макроса.

Вот почему применяется трюк с do/while(0). Посмотрите на макрос FREE4, свободный от описанных выше проблем:

#define  FREE4(p)           \
do {                        \
  if  (p)                   \
    free(p);                \
} while (0)

После помещения макроса в тот же самый код и его разворачивания получается:

if  (expression)
  do {
    if  (p)
      free(p);
  } while (0);  /* ";" после макроса. */
else
  printf("expression ложно.\n");

Разумеется, такой код работает корректно. Компилятор выполнит оптимизацию и минимизирует накладные расходы для этого поддельного цикла, поэтому никаких потерь в скорости не будет, а макрос будет работать именно так, как необходимо.

Перед завершением обсуждения данной проблемы нельзя не заметить, что даже несмотря на приемлимость последнего решения, написание функций гораздо лучше написания макросов. Если накладные расходы на вызов функции непозволительны (что имеет место для ядра, но в гораздо меньшей степени для других задач), воспользуйтесь inline-функциями. (Последнее доступно только в компиляторе С++, gcc или в компиляторах, поддерживающих последний стандарт ISO C, где добавлены inline-функции.)

__wait_event

wait_event_interruptible и __wait_event_interruptible (соответственно, строки 16868 и 16847) похожи на wait_event и __wait_event за тем лишь исключением, что они разрешают прерывание ожидающего процесса по сигналу. (Описание сигналов находится в главе 6.)

Кроме того, wait_event_interruptible заключено в скобки:

({
  /* . . . */
})

Подобно трюку с do/while(0), показанное выше заставляет заключенный внутри код действовать как один модуль. Здесь заключенный внутри код — это одиночное выражение, а не оператор, т.е. он оценивается как одно значение, которое может участвовать в больших выражениях. Причина применения трюка связана со своеобразной магией непереносимости в gcc, когда последнее вычисленное в таком блоке выражение делается значением целого блока. В случае использования wait_event_interruptible в выражении, тело макроса выполняется и значением макроса будет значение __ret (см. строку 16873). С подобной концепцией хорошо знакомы программирующие на Lisp, однако она может показаться обескураживающей для тех, кто имел дело только с С и ему подобными процедурными языками.

__wake_up


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz