на равенство нулю, чтобы убедиться, что тасклет не запрещен. Если тасклет запрещен (поле count не равно нулю), то нужно перейти к следующему тасклету, который ожидает на выполнение.
• Теперь можно быть уверенным, что тасклет нигде не выполняется, нигде не будет выполняться (так как он помечен как выполняющийся на данном процессоре) и что значение поля count равно нулю. Необходимо выполнить обработчик тасклета. После того как тасклет выполнился, следует очистить флаг
TASLET_STATE_RUN
и поле
state
.
• Повторить описанный алгоритм для следующего тасклета, пока не останется ни одного тасклета, ожидающего выполнения.
Реализация
тасклетов проста, но в то же время очень остроумна. Как видно, все тасклеты реализованы на базе двух отложенных прерываний
TASKLET_SOFTIRQ
и
HI_SOFTIRQ
. Когда тасклет запланирован на выполнение, ядро генерирует одно из этих двух отложенных прерываний. Отложенные прерывания, в свою очередь, обрабатываются специальными функциями, которые выполняют все запланированные на выполнение тасклеты. Эти специальные функции гарантируют, что только один тасклет данного типа выполняется в любой момент времени (но тасклеты разных типов могут выполняться одновременно). Вся эта сложность спрятана за простым и ясным интерфейсом.
Использование тасклетов
В большинстве случаев тасклеты — это самый предпочтительный механизм, с помощью которого следует реализовать обработчики нижних половин для обычных аппаратных устройств. Тасклеты можно создавать динамически, их просто использовать, и они сравнительно быстро работают.
Объявление тасклетов
Тасклеты можно создавать статически и динамически. Какой вариант лучше выбрать, зависит от того, как необходимо (или желательно) пользователю обращаться к тасклету: прямо или через указатель. Для статического создания тасклета (и соответственно, обеспечения прямого доступа к нему) необходимо использовать один из двух следующих макросов, которые определены в файле
<linux/interrupts.h>
:
DECLARE_TASKLET(name, func, data);
DECLARE_TASKLET_DISABLED(name, func, data);
Оба макроса статически создают экземпляр структуры
struct_tasklet_struct
с указанным именем (
name
). Когда тасклет запланирован на выполнение, то вызывается функция
func
, которой передается аргумент
data
. Различие между этими макросами состоит в значении счетчика ссылок на тасклет (поле
count
). Первый макрос создает тасклет, у которого значение поля count равно нулю, и, соответственно, этот тасклет разрешен. Второй макрос создает тасклет и устанавливает для него значение поля
count
, равное единице, и, соответственно, этот тасклет будет запрещен. Можно привести следующий пример.
будет обработчиком этого тасклета. Значение параметра
dev
передается в функцию-обработчик при вызове данной функции.
Для инициализации тасклета, на который указывает заданный указатель
struct tasklet_struct* t
— косвенная ссылка на динамически созданную ранее структуру, необходимо использовать следующий вызов.
tasklet_init(t, tasklet_handler, dev); /* динамически, а не статически */
Написание собственной функции-обработчика тасклета
Функция-обработчик тасклета должна соответствовать правильному прототипу.
void tasklet_handler(unsigned long data);
Так
же как и в случае отложенных прерываний, тасклет не может переходить в состояние ожидания (блокироваться). Это означает, что в тасклетах нельзя использовать семафоры или другие функции, которые могут блокироваться. Тасклеты также выполняются при всех разрешенных прерываниях, поэтому необходимо принять все меры предосторожности (например, может понадобиться запретить прерывания и захватить блокировку), если тасклет имеет совместно используемые данные с обработчиком прерывания. В отличие от отложенных прерываний, ни один тасклет не выполняется параллельно самому себе, хотя два разных тасклета могут выполняться на разных процессорах параллельно. Если тасклет совместно использует данные с обработчиком прерывания или другим тасклетом, то необходимо использовать соответствующие блокировки (см. главу 8, "Введение в синхронизацию выполнения кода ядра" и главу 9, "Средства синхронизации в ядре").
Планирование тасклета на выполнение
Для того чтобы запланировать тасклет на выполнение, должна быть вызвана функция
tasklet_schedule
, которой в качестве аргумента передается указатель на соответствующий экземпляр структуры
tasklet_struct
.
tasklet_schedule(&my_tasklet); /* отметить, что тасклет my_tasklet
ожидает на выполнение */
После того как тасклет запланирован на выполнение, он выполняется один раз в некоторый момент времени в ближайшем будущем. Если тасклет, который запланирован на выполнение, будет запланирован еще раз до того, как он выполнится, то он также выполнится всего один раз. Если тасклет уже выполняется, скажем, на другом процессоре, то будет запланирован снова и снова выполнится. Для оптимизации тасклет всегда выполняется на том процессоре, который его запланировал на выполнение, что дает надежду на лучшее использование кэша процессора.
Указанный тасклет может быть запрещен с помощью вызова функции
tasklet_disable
. Если тасклет в данный момент времени выполняется, то эта функция не возвратит управление, пока тасклет не закончит выполняться. Как альтернативу можно использовать функцию
tasklet_disable_nosync
, которая запрещает указанный тасклет, но возвращается сразу и не ждет, пока тасклет завершит выполнение. Это обычно небезопасно, так как в данном случае нельзя гарантировать, что тасклет не закончил выполнение. Вызов функции
tasklet_enable
разрешает тасклет. Эта функция также должна быть вызвана для того, чтобы можно было использовать тасклет, созданный с помощью макроса
DECLARE_TASKLET_DISABLED
, как показано в следующем примере.
tasklet_disable(&my_tasklet); /* тасклет теперь запрещен */
/* Мы можем делать все, что угодно, зная,
что тасклет не может выполняться. */
tasklet_enable(&my_tasklet); /* теперь тасклет разрешен */
Из очереди тасклетов, ожидающих на выполнение, тасклет может быть удален с помощью функции
tasklet_kill
. Эта функция получает указатель на соответствующую структуру
tasklet_struct
в качестве единственного аргумента. Удаление запланированного на выполнение тасклета из очереди очень полезно в случае, когда используются тасклеты, которые сами себя планируют на выполнение. Эта функция сначала ожидает, пока тасклет не закончит выполнение, а потом удаляет его из очереди. Однако это, конечно, не может предотвратить возможности, что другой код запланирует этот же тасклет на выполнение. Так как данная функция может переходить в состояние ожидания, то ее нельзя вызывать из контекста прерывания.