Библиотека сайта 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 );
Техника очередей отложенных действий показана здесь на примере обработчика прерываний, но она гораздо шире по сферам её применения (в отличие, например, от тасклетов), для других целей.
Предыдущий раздел: | Оглавление | Следующий раздел: |
Тасклеты | Сравнение и примеры |