вторник, 7 декабря 2010 г.

7.5. Тасклеты

7.5. Тасклеты


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

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

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

#include 

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. Два списка тасклетов (нормальный и высоко-приоритетный) объявлены как структуры данных, имеющие копии для каждого процессора, используя такой же механизм родства процессоров, как и для таймеров ядра. Структура данных, используемая для управления тасклетами, является простым связным списком, потому что тасклеты не имеют ни одного из требований сортировки таймеров ядра.

Комментариев нет:

Отправить комментарий

Примечание. Отправлять комментарии могут только участники этого блога.