Ядро Linux в комментариях

       

Wait_event


При помощи этого макроса код ядра переводит текущий выполняемый процесс в режим ожидания в очереди wq до тех пор, пока не будет удовлетворено заданное условие condition (которое может быть произвольным выражением).

Если условие истино, процесс не должен ожидать.

В противном случае процесс будет находиться в состоянии ожидания до тех пор, пока условие не станет истинным. Это завершается обращением к __wait_event (строка ). Поскольку __wait_event отделено от wait_event, фрагменты кода ядра, которым известно, что условие ожидания ложно, могут вызывать __wait_event напрямую, а не через макрос. В данном случае макрос выполняет избыточную проверку. Если условие истино, 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-функции.)


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