Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Модули ядра 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 ); 

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


Предыдущий раздел: Оглавление Следующий раздел:
Тасклеты   Сравнение и примеры