Библиотека сайта rus-linux.net
| Цилюрик О.И. Модули ядра Linux | ||
| Назад | Внутренние механизмы ядра | Вперед |
Демон ksoftirqd
Обработка отложенных прерываний (softirq) и, соответственно, тасклетов осуществляется с помощью набора потоков пространства ядра (по одному потоку на каждый процессор). Потоки пространства ядра помогают обрабатывать отложенные прерывания, когда система перегружена большим количеством отложенных прерываний.
$ ps -ALf | head -n12
UID PID PPID LWP C NLWP STIME TTY TIME CMD root 1 0 1 0 1 08:55 ? 00:00:01 /sbin/init ... root 4 2 4 0 1 08:55 ? 00:00:00 [ksoftirqd/0] ... root 7 2 7 0 1 08:55 ? 00:00:00 [ksoftirqd/1] ...
Для каждого процессора существует свой поток. Каждый поток имеет имя в виде ksoftirqd/n , где п — номер процессора. Например, в двухпроцессорной системе будут запущены два потока с именами ksoftirqd/0 и ksoftirqd/1. To, что на каждом процессоре выполняется свой поток, гарантирует, что если в системе есть свободный процессор, то он всегда будет в состоянии выполнять отложенные прерывания. После того как потоки запущены, они выполняют замкнутый цикл.
Очереди отложенных действий (workqueue)
Очереди отложенных действий (workqueue) — это еще один, но совершенно другой, способ реализации отложенных операций. Очереди отложенных действий позволяют откладывать некоторые операции для последующего выполнения потоком пространства ядра (эти потоки ядра называют рабочими потоками - worker threads ) — отложенные действия всегда выполняются в контексте процесса. Поэтому код, выполнение которого отложено с помощью постановки в очередь отложенных действий, получает все преимущества, которыми обладает код, выполняющийся в контексте процесса, главное из которых — это возможность переходить в блокированные состояния. Рабочие потоки, которые выполняются по умолчанию, называются events/n, где n — номер процессора, для 2-х процессоров это будут events/0 и events/1 :
$ ps -ALf | head -n12
... root 9 2 9 0 1 08:55 ? 00:00:00 [events/0] root 10 2 10 0 1 08:55 ? 00:00:00 [events/1] ...
Когда какие-либо действия ставятся в очередь, поток ядра возвращается к выполнению и выполняет эти действия. Когда в очереди не остается работы, которую нужно выполнять, поток снова возвращается в состояние ожидания. Каждое действие представлено с помощью struct work_struct (определяется в файле <linux/workqueue.h> - очень меняется от версии к версии ядра!):
typedef void (*work_func_t)( struct work_struct *work );
struct work_struct {
atomic_long_t data; /* аргумент функции-обработчика */
struct list_head entry; /* связанный список всех действий */
work_func_t func; /* функция-обработчик */
...
};
Для создания статической структуры действия на этапе компиляции необходимо использовать макрос:
DECLARE_WORK( name, void (*func) (void *), void *data );
Это выражение создает struct work_struct с именем name, с функцией-обработчиком func() и аргументом функции-обработчика data. Динамически отложенное действие создается с помощью указателя на ранее созданную структуру, используя следующий макрос:
INIT_WORK( struct work_struct *work, void (*func)(void *), void *data );
Функция-обработчика имеет тот же прототип, что и для отложенных прерываний и тасклетов, поэтому в примерах будет использоваться та же функция (xxx_analyze()).
Для реализации нижней половины обработчика IRQ на технике workqueue, выполнем последовательность действий примерно следующего содержания:
При инициализации модуля создаём отложенное действие:
#include <linux/workqueue.h>
struct work_struct *hardwork;
void __init xxx_init() {
/* ... */
request_irq( irq, xxx_interrupt, 0, "xxx", NULL );
hardwork = kmalloc( sizeof(struct work_struct), GFP_KERNEL );
/* Init the work structure */
INIT_WORK( hardwork, xxx_analyze, data );
}
Или то же самое может быть выполнено статически
#include <linux/workqueue.h>
DECLARE_WORK( hardwork, xxx_analyze, data );
void __init xxx_init() {
/* ... */
request_irq( irq, xxx_interrupt, 0, "xxx", NULL );
}
Самая интересная работа начинается когда нужно запланировать отложенное действие; при использовании для этого рабочего потока ядра по умолчанию (events/n) это делается функциями :
- schedule_work( struct work_struct *work ); - действие планируется на выполнение немедленно и будет выполнено, как только рабочий поток events, работающий на данном процессоре, перейдет в состояние выполнения.
- schedule_delayed_work( struct delayed_work *work, unsigned long delay ); - в этом случае запланированное действие не будет выполнено, пока не пройдет хотя бы заданное в параметре delay количество импульсов системного таймера.
В обработчике прерывания это выглядит так:
static irqreturn_t xxx_interrupt( int irq, void *dev_id ) {
/* ... */
schedule_work( hardwork );
/* или schedule_work( &hardwork ); - для статической инициализации */
return IRQ_HANDLED;
}
Очень часто бывает необходимо ждать пока очередь отложенных действий очистится (отложенные действия завершатся), это обеспечивает функция:
void flush_scheduled_work( void );
Для отмены незавершённых отложенных действий с задержками используется функция:
int cancel_delayed_work( struct work_struct *work );
Но мы не обязательно должны рассчитывать на общую очереди (потоки ядра events) для выполнения отложенных действий — мы можем создать под эти цели собственные очереди (вместе с обслуживающим потоком). Создание обеспечивается макросами вида:
struct workqueue_struct *create_workqueue( const char *name ); struct workqueue_struct *create_singlethread_workqueue( const char *name );
Планирование на выполнение в этом случае осуществляют функции:
int queue_work( struct workqueue_struct *wq, struct work_struct *work ); int queue_delayed_work( struct workqueue_struct *wq, struct wesrk_struct *work, unsigned long delay);
Они аналогичны рассмотренным выше schedule_*(), но работают с созданной очередью, указанной 1-м параметром. С вновь созданными потоками предыдущий пример может выглядеть так:
struct workqueue_struct *wq;
/* Driver Initialization */
static int __init xxx_init( void ) {
/* ... */
request_irq( irq, xxx_interrupt, 0, "xxx", NULL );
hardwork = kmalloc( sizeof(struct work_struct), GFP_KERNEL );
/* Init the work structure */
INIT_WORK( hardwork, xxx_analyze, data );
wq = create_singlethread_workqueue( "xxxdrv" );
return 0;
}
static irqreturn_t xxx_interrupt( int irq, void *dev_id ) {
/* ... */
queue_work( wq, hardwork );
return IRQ_HANDLED;
}
Аналогично тому, как и для очереди по умолчанию, ожидание завершения действий в заданной очереди может быть выполнено с помощью функции :
void flush_workqueue( struct workqueue_struct *wq );
Техника очередей отложенных действий показана здесь на примере обработчика прерываний, но она гораздо шире по сферам её применения (в отличие, например, от тасклетов), для других целей.
| Предыдущий раздел: | Оглавление | Следующий раздел: |
| Тасклеты | Сравнение и примеры |
