Чтение онлайн

ЖАНРЫ

Разработка ядра Linux (Второе издание)
Шрифт:

Короткие задержки

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

jiffies
, как показано в предыдущем примере. При частоте системного таймера, равной 100 Гц, значение периода системного таймера достаточно большое — 10 миллисекунд! Даже при частоте системного таймера 1000 Гц, период системного таймера равен одной миллисекунде. Ясно, что необходимо другое решение, которое обеспечивает более короткие
и точные задержки.

Ядро предоставляет две функции для обеспечения микросекундных и миллисекундных задержек, которые определены в файле

<linux/delay.h>
и не используют переменную
jiffies
.

void udelay(unsigned long usecs);

void mdelay(unsigned long msecs);

Первая функция позволяет задержать выполнение на указанное количество микросекунд с использованием цикла. Вторая функция задерживает выполнение на указанное количество миллисекунд. Следует вспомнить, что одна секунда равна 1000 миллисекундам, что эквивалентно 1000000 микросекунд. Использование этих функций тривиально.

udelay(150); /* задержка на 150 μs */

Функция

udelay
выполнена на основе цикла, для которого известно, сколько итераций необходимо выполнить за указанный период времени. Функция
mdelay
выполнена на основе функции
udelay
. Так как в ядре известно, сколько циклов процессор может выполнить в одну секунду (смотрите ниже замечание по поводу характеристики BogoMlPS), функция
udelay
просто масштабирует это значение для того, чтобы скорректировать количество итераций цикла для получения указанной задержки.

Мой BogoMIPS больше, чем у Вас!

Характеристика BogoMlPS всегда была источником недоразумений и шуток. На самом деле вычисленное значение BogoMlPS не имеет ничего общего с производительностью компьютера и используется только для функций

udelay
и
mdelay
. Название этого параметра состоит из двух частей bogus (фиктивный) и MIPS (million of instructions per second, миллион инструкций в секунду). Все знакомы с сообщением, которое выдается при загрузке системы и похоже на следующее (данное сообщение соответствует процессору Pentium III с частотой 1 ГГц).

Detected 1004.932 MHz processor.

Calibrating delay loop... 1990.65 BogoMlPS

Значение параметра BogoMIPS - это количество циклов, которые процессор может выполнить за указанный период времени, В действительности эта характеристика показывает, насколько быстро процессор может ничего не делать! Это значение хранится в переменной

loops_per_jiffy
, и его можно считать из файла
/proc/cpuinfo
.

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

Ядро вычисляет значение переменной

loops_per_jiffy
при загрузке системы в функции
calibrate_delay
, реализация которой описана в файле
init/main.c
.

Функция

udelay
должна вызываться только для небольших задержек, поскольку при большом времени задержки на быстрой машине может возникнуть переполнение в переменных цикла. Общее правило: по возможности не использовать функцию
udelay
для задержек, больше одной миллисекунды. Для более продолжительных задержек хорошо работает функция
mdelay
. Так же как и другие методы задержки выполнения, основанные на циклах, эти функции (особенно функция
mdelay
, так как она дает длительные задержки) должны использоваться, только если это абсолютно необходимо. Следует помнить, что очень плохо использовать циклы задержек, когда удерживается блокировка или запрещены прерывания, потому что это очень сильно влияет на производительность и время реакции системы. Если необходимо обеспечить точное время задержки, то эти функции — наилучшее решение. Обычное использование этих функций — это задержки на не очень короткий период времени, который лежит в микросекундном диапазоне.

Функция

schedule_timeout

Более

оптимальный метод задержки выполнения — это использование функции
schedule_timeouit
. Этот вызов переводит вызывающее задание в состояние ожидания (sleep) по крайней до тех пор, пока не пройдет указанный период времени. Нет никакой гарантии, что время ожидания будет точно равно указанному значению, гарантируется только, что задержка будет не меньше указанной. Когда проходит указанный период времени, ядро возвращает задание в состояние готовности к выполнению (wake up) и помещает его в очередь выполнения. Использовать эту функцию просто.

/* установить состояние задания в значение прерываемого ожидания */

set_current_state(TASK_INTERRUPTIBLE);

/* перейти в приостановленное состояние на s секунд */

schedule_timeout(s * HZ);

Единственный параметр функции — это желаемое относительное время, выраженное в количестве импульсов системного таймера. В этом примере задание переводится в прерываемое состояние ожидания, которое будет длиться s секунд. Поскольку задание отмечено как

TASK_INTERRUPTIBLE
, то оно может быть возвращено к выполнению раньше времени, как только оно получит сигнал. Если не нужно, чтобы код обрабатывал сигналы, то можно использовать состояние
TASK_UNINTERRUPTIBLE
. Перед вызовом функции
schedule_timeout
задание должно быть в одном из этих двух состояний, иначе задание в состояние ожидания переведено не будет.

Следует обратить внимание, что поскольку функция

schedule_timeout
использует планировщик, то код, который ее вызывает, должен быть совместим с состоянием ожидания. Обсуждение, посвященное атомарности и переходу в состояние ожидания, приведено в главах 8 и 9. Если коротко, то эту функцию необходимо вызывать в контексте процесса и не удерживать при этом блокировку.

Функция

schedule_timeout
достаточно проста. Она просто использует таймеры ядра. Рассмотрим эту функцию подробнее.

signed long schedule_timeout(signed long timeout) {

 timer_t timer;

 unsigned long expire;

 switch (timeout) {

 case MAX_SCHEDULE_TIMEOUT:

schedule;

goto out;

 default:

if (timeout < 0) {

printk(KERN_ERR "schedule_timeout: wrong timeout "

"value %lx from %p\n", timeout, builtin_return_address(0));

current->state = TASK_RUNNING;

goto out;

}

 }

 expire = timeout + jiffies;

 init_timer(&timer);

 timer.expires = expire;

 timer.data = (unsigned long) current;

 timer.function = process_timeout;

 add_timer(&timer);

 schedule;

 del_timer_sync(&timer);

 timeout = expire - jiffies;

out:

 return timeout < 0 ? 0 : timeout;

}

Эта функция создает таймер

timer
и устанавливает время срабатывания в значение
timeout
импульсов системного таймера в будущем. В качестве обработчика таймера устанавливается функция
process_timeout
, которая вызывается, когда истекает период времени таймера. Далее таймер активизируется, и вызывается функция
schedule
. Так как предполагается, что текущее задание находится в состоянии
TASK_INTERRUPTIBLE
или
TASK_UNINTERRUPTIBLE
, то планировщик не будет выполнять текущее задание, а выберет для выполнения другой процесс.

Поделиться с друзьями: