Выбрать главу

 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, то планировщик не будет выполнять текущее задание, а выберет для выполнения другой процесс.

Когда интервал времени таймера истекает, то вызывается функция process_timeout(), которая имеет следующий вид.

void process_timeout(unsigned long data) {

 wake_up_process((task_t*)data);

}

Эта функция устанавливает задание в состояние TASK_RUNNING и помещает его в очередь выполнения.

Когда задание снова планируется на выполнение, то оно возвращается в функцию schedule_timeout() (сразу после вызова функции schedule()). Если задание возвращается к выполнению преждевременно, то таймер ликвидируется. После этого задание возвращается из функции ожидания по тайм-ауту.

Код оператора switch() служит для обработки специальных случаев и не является основной частью функции. Проверка на значение MAX_SCHEDULE_TIMEOUT позволяет заданию находиться в состоянии ожидания неопределенное время. В этом случае таймер не устанавливается (поскольку нет ограничений на интервал времени ожидания), и сразу же активизируется планировщик. Если вы это применяете, то, наверное, у вас есть лучший способ вернуть задание в состояние выполнения!

Ожидание в очереди wait queue в течение интервала времени

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

Иногда желательно ожидать наступления некоторого события или пока не пройдет определенный интервал времени, в зависимости от того, что наступит раньше, В этом случае код должен просто вызвать функцию schedule_timeout() вместо функции schedule() после того, как он поместил себя в очередь ожидания. Задание будет возвращено к выполнению, когда произойдет желаемое событие или пройдет указанный интервал времени. Код обязательно должен проверить, почему он возвратился к выполнению — это может произойти потому, что произошло событие, прошел интервал времени или был получен сигнал — после этого необходимо соответственным образом продолжить выполнение.

Время вышло

В этой главе были рассмотрены понятия, связанные с представлением о времени в ядре и с тем, как при этом происходит управление абсолютным и относительным ходом времени. Были показаны отличия абсолютного и относительного времени, а также периодических и относительных событий. Далее были рассмотрены прерывания таймера, импульсы таймера, константа HZ и переменная jiffies.

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

Большая часть кода ядра, который вам придется писать, требует понимания того, как время течет в ядре и как его отслеживать. С очень большой вероятностью, особенно при разработке драйверов, вам необходимо будет иметь дело с таймерами ядра. Материал этой главы принесет практическую пользу.

Глава 11

Управление памятью

Выделить память внутри ядра не так просто, как вне ядра. Это связано со многими факторами. Главным образом, причина в том, что в ядре не доступны те элементы роскоши, которыми можно пользоваться в пространстве пользователя, В отличие от пространства пользователя, в ядре не всегда можно позволить себе легко выделять память. Например, в режиме ядра часто нельзя переходить в состояние ожидания. Более того, в ядре не так просто справиться с ошибками, которые возникают при работе с памятью. Из-за этих ограничений и из-за необходимости, чтобы схема выделения памяти была быстрой, работа с памятью в режиме ядра становится более сложной, чем в режиме пользователя. Конечно, нельзя сказать, что выделение памяти в ядре — очень сложная процедура, однако скоро все будет ясно — просто это делается несколько по-другому.

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

Страницы памяти

Ядро рассматривает страницы физической памяти как основные единицы управления памятью. Хотя наименьшая единица памяти, которую может адресовать процессор, — это машинное слово, модуль управления памятью (MMU, Memory Management Unit) — аппаратное устройство, которое управляет памятью и отвечает за трансляцию виртуальных адресов в физические  — обычно работает со страницами. Поэтому модуль MMU управляет таблицами страниц на уровне страничной детализации (отсюда и название). С точки зрения виртуальной памяти, страница — это наименьшая значащая единица.

Как будет показано в главе 19, "Переносимость", каждая аппаратная платформа поддерживает свой характерный размер страницы. Многие аппаратные платформы поддерживают даже несколько разных размеров страниц. Большинство 32-разрядных платформ имеют размер страницы, равный 4 Кбайт, а большинство 64-разрядных платформ — 8 Кбайт. Это значит, что на машине, размер страницы которой равен 4 Кбайт, при объеме физической памяти, равном 1 Гбайт, эта физическая память разбивается на 262 144 страницы.

Ядро сопоставляет каждой странице физической памяти в системе структуру struct page. Эта структура определена в файле <linux/mm.h> следующим образом.

struct page {

 page_flags_t         flags;

 atomic_t             _count;

 atomic_t             _mapcount;