Микрозадачи

Предыдущая  Содержание  Следующая V*D*V

Другим объектом ядра, связанным с вопросом времени, является механизм микрозадачи (tasklet, тасклет). Он в основном используется в управлении прерываниями (мы увидим его снова в Главе 10).

 

Микрозадачи в некотором смысле напоминают таймеры ядра. Они всегда выполняются во время прерывания, они всегда работают на том же процессоре, который их запланировал, и они получают unsigned long аргумент. Однако, в отличие от таймеров ядра, невозможно запросить выполнения функции в заданное время. Запланировав микрозадачу, вы просто попросите, чтобы она выполнилась позже, во время, выбранное ядром. Такое поведение особенно полезно для обработчиков прерываний, когда аппаратное прерывание должно обслуживаться как можно быстрее, но большинство управлений данными можно смело отложить на более позднее время. На самом деле микрозадача, как и таймер ядра, выполняется (в атомарном режиме) в контексте "программного прерывания", механизма ядра, который выполняет асинхронные задачи при разрешённых аппаратных прерываниях.

 

Микрозадача существует как структура данных, которая должна быть проинициализирована перед использованием. Инициализация может быть выполнена путём вызова специальной функции или объявлением структуры с помощью определённых макросов:

 

#include <linux/interrupt.h>

 

struct tasklet_struct {

    /* ... */

    void (*func)(unsigned long);

    unsigned long data;

};

 

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);

DECLARE_TASKLET(name, func, data);

DECLARE_TASKLET_DISABLED(name, func, data);

 

Микрозадачи предлагают ряд интересных особенностей:

 

Микрозадачу можно отключить и повторно включить позже; она не будет выполнена, пока она не включена столько раз, сколько была отключена.

Так же как таймеры, микрозадача может перерегистрировать себя.

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

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

Микрозадачи могут конкурировать с другими микрозадачами, но строго последовательны в отношении самих себя - та же микрозадача никогда не работает одновременно более чем на одном процессоре. Кроме того, как уже отмечалось, микрозадача всегда работает на том же процессоре, который её запланировал.

 

Модуль jit включает два файла, /proc/jitasklet и /proc/jitasklethi, которые возвращают те же данные, что и /proc/jitimer, описанный в разделе "Таймеры ядра". Когда вы читаете один из этих файлов, вы получаете обратно заголовок и шесть строк данных. Первая строка данных описывает контекст вызывающего процесса, а другие строки описывают контекст последовательных запусков процедуры микрозадачи. Это пример работы во время компиляции ядра:

 

phon% cat /proc/jitasklet

   time   delta  inirq    pid   cpu command

 6076139     0     0      4370   0   cat

 6076140     1     1      4368   0   cc1

 6076141     1     1      4368   0   cc1

 6076141     0     1         2   0   ksoftirqd/0

 6076141     0     1         2   0   ksoftirqd/0

 6076141     0     1         2   0   ksoftirqd/0

 

Как подтверждается приведенными выше данными, микрозадача выполняется при следующем тике таймера до тех пор, пока процессор занят выполнением процесса, но она запускается сразу, когда процессор простаивает. Ядро предоставляет набор потоков ядра ksoftirqd, один на процессор, чтобы просто запускать обработчики "программных прерываний", такие, как функция tasklet_action. Таким образом последние три запуска микрозадачи проходили в контексте потока ядра ksoftirqd, связанного с CPU 0. Реализация jitasklethi использует высокоприоритетную микрозадачу, объясняемую в последующем списке функций.

 

Фактический код в jit, который реализует /proc/jitasklet и /proc/jitasklethi, почти идентичен коду, который реализует /proc/jitimer, но вместо таймера он использует вызовы микрозадачи. Ниже приводится список деталей интерфейса ядра для микрозадач после того, как структура микрозадачи была проинициализирована:

 

void tasklet_disable(struct tasklet_struct *t);

Эта функция отключает данную микрозадачу. Микрозадача по-прежнему может быть запланирована с помощью tasklet_schedule, но её выполнение отложено, пока микрозадача снова не будет включена. Если микрозадача в настоящее время работает, эта функция активно ждёт завершения микрозадачи; таким образом, после вызова tasklet_disable вы можете быть уверены, что микрозадача не работает нигде в системе.

 

void tasklet_disable_nosync(struct tasklet_struct *t);

Отключает микрозадачу, но не дожидается завершения никакой запущенной функции. Когда возвращается, микрозадача является отключённой и не будет планироваться в будущем, пока снова не будет включена, но всё ещё может работать на другом процессоре, когда функция возвращается.

 

void tasklet_enable(struct tasklet_struct *t);

Включает микрозадачу, которая была ранее отключена. Если микрозадача уже запланирована, она будет запущена в ближайшее время. Вызов tasklet_enable должен соответствовать каждому вызову tasklet_disable, так как ядро отслеживает "счётчик отключений" для каждой микрозадачи.

 

void tasklet_schedule(struct tasklet_struct *t);

Планирует микрозадачу на исполнение. Если микрозадача запланирована снова прежде, чем у неё появился шанс заработать, она запустится только один раз. Однако, если она запланирована во время работы, она запустится снова после своего завершения; это гарантирует, что события, происходящие во время обработки других событий, получат должное внимание. Такое поведение также позволяет микрозадаче перепланировать себя.

 

void tasklet_hi_schedule(struct tasklet_struct *t);

Планирует микрозадачу для выполнения с более высоким приоритетом. Когда обработчик программного прерывания работает, он выполняет высокоприоритетные микрозадачи перед другими задачами программных прерываний, в том числе "нормальных" микрозадач. В идеале только задания с требованиями малой задержки (такие, как заполнение звукового буфера) должны использовать эту функцию, чтобы избежать дополнительных задержек, вводимых другими обработчиками программных прерываний. На самом деле /proc/jitasklethi не показывает видимое человеку отличие от /proc/jitasklet.

 

void tasklet_kill(struct tasklet_struct *t);

Эта функция гарантирует, что микрозадачу не планируется запустить снова; её обычно вызывают, когда устройство закрывается или модуль удаляется. Если микрозадача запланирована на запуск, функция ждёт её выполнения. Если микрозадача перепланирует сама себя, вы должны запретить ей перепланировать себя перед вызовом tasklet_kill, как и в случае del_timer_sync.

 

Микрозадачи реализованы в kernel/softirq.c. Два списка микрозадач (нормальный и высокоприоритетный) объявлены как структуры данных, имеющие копии для каждого процессора, используя такой же механизм родства процессоров, как и для таймеров ядра. Структура данных, используемая для управления микрозадачами, является простым связным списком, потому что микрозадачи не имеют ни одного из требований сортировки таймеров ядра.

Предыдущая  Содержание  Следующая