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

UnixForum





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

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Модули ядра Linux
Назад Внутренние механизмы ядра Вперед

Потоки ядра

Для создания нового потока ядра используем вызов:

	int kernel_thread( int (*fn)(void *), void * arg, unsigned long flags ); 

Параметры такого вызова понятны: функция потока, безтиповой указатель — параметр, передаваемый этой функции, и флаги, обычные для Linux вызова clone(). Возвращаемое функцией значение — это PID вновь созданного потока (если он больше нуля).

А вот он же среди экспортируемых символов ядра:

$ cat /proc/kallsyms | grep kernel_thread

	c0407c44 T kernel_thread
	...

Примечание: Позже, при рассмотрении обработчиков прерываний, мы увидим механизм рабочих очередей (workqueue), обслуживаемый потоками ядра. Должно быть понятно, что уже одного такого механизма высокого уровня достаточно для инициации параллельных действий в ядре (с неявным использованием потоков ядра). Здесь же мы пока рассмотрим только низкоуровневые механизмы, которые и лежат в базисе таких возможностей.

Первый простейший пример для прояснения того, как создаются потоки ядра (архив thread.tgz):

mod_thr1.c :

	#include <linux/module.h> 
	#include <linux/sched.h> 
	#include <linux/delay.h> 
	
	static int param = 3; 
	module_param( param, int, 0 ); 
	
	static int thread( void * data ) {
	   printk( KERN_INFO "thread: child process [%d] is running\n", current->pid ); 
	   ssleep( param );                                  /* Пауза 3 с. или как параметр укажет... */ 
	   printk( KERN_INFO "thread: child process [%d] is completed\n", current->pid ); 
	   return 0; 
	} 
	
	int test_thread( void ) { 
	   pid_t pid; 
	   printk( KERN_INFO "thread: main process [%d] is running\n", current->pid ); 
	   pid = kernel_thread( thread, NULL, CLONE_FS );     /* Запускаем новый поток */ 
	   ssleep( 5 );                                      /* Пауза 5 с. */ 
	   printk( KERN_INFO "thread: main process [%d] is completed\n", current->pid ); 
	   return -1; 
	} 
	
	module_init( test_thread ); 

В принципе, этот модуль ядра ничего и не выполняет, за исключением того, что запускает новый поток ядра. При выполнении этого примера мы получим что-то подобное следующему:

$ uname -r

	2.6.32.9-70.fc12.i686.PAE

$ time sudo insmod ./mod_thr1.ko

	insmod: error inserting './mod_thr1.ko': -1 Operation not permitted 
	real	0m5.025s 
	user	0m0.004s 
	sys	0m0.012s 

$ sudo cat /var/log/messages | tail -n30 | grep thread:

	Jul 24 18:43:57 notebook kernel: thread: main process [12526] is running 
	Jul 24 18:43:57 notebook kernel: thread: child process [12527] is running 
	Jul 24 18:44:00 notebook kernel: thread: child process [12527] is completed 
	Jul 24 18:44:02 notebook kernel: thread: main process [12526] is completed 

Примечание: Если мы станем выполнять пример с задержкой дочернего процесса больше родительского, то получим (после завершения запуска, при завершении созданного потока ядра!) сообщение Oops ошибки ядра:

$ sudo insmod ./mod_thr1.ko param=7

	insmod: error inserting './mod_thr1.ko': -1 Operation not permitted 
	$
	Message from syslogd@notebook at Jul 24 18:51:00 ... 
	kernel:Oops: 0002 [#1] SMP 
	...

$ sudo cat /var/log/messages | tail -n70 | grep thread:

	Jul 24 18:50:53 notebook kernel: thread: main process [12658] is running 
	Jul 24 18:50:53 notebook kernel: thread: child process [12659] is running
	Jul 24 18:50:58 notebook kernel: thread: main process [12658] is completed 

Последний параметр flags вызова kernel_thread() определяет детальный, побитово устанавливаемый набор тех свойств, которыми будет обладать созданный потока ядра, так как это вообще делается в практике Linux вызовом clone() (в этом месте, создании потоков-процессов, наблюдается существенное отличие Linux от традиций UNIX/POSIX). Часто в коде модулей можно видеть создание потока с таким набором флагов:

	kernel_thread( thread_function, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);    

Созданному потоку ядра (как и пользовательским процессам и потокам) присущ целый ряд параметров (<linux/sched.h>), часть которых будет иметь значения по умолчанию (такие, например, как параметры диспетчеризации), но которые могут быть и изменены. Для работы с параметрами потока используем следующие API:

1. Взаимно однозначное соответствие PID потока и соответствующей ему основной структуры данных, записи о задаче, которая уже обсуждалась (struct task_struct) — устанавливается в обоих направлениях вызовами:

	static inline pid_t task_pid_nr( struct task_struct *tsk ) { 
	   return tsk->pid; 
	} 
	struct task_struct *find_task_by_vpid( pid_t nr );

Или, пользуясь описаниями из <linux/pid.h>:

	// find_vpid() find the pid by its virtual id, i.e. in the current namespace
	extern struct pid *find_vpid( int nr );
	enum pid_type { 
	   PIDTYPE_PID, 
	   PIDTYPE_PGID, 
	   PIDTYPE_SID, 
	   PIDTYPE_MAX 
	}; 
	struct task_struct *pid_task( struct pid *pid, enum pid_type ); 
	struct task_struct *get_pid_task( struct pid *pid, enum pid_type ); 
	struct pid *get_task_pid( struct task_struct *task, enum pid_type type ); 

В коде модуля это может выглядеть так:

	struct task_struct *tsk;
	tsk = find_task_by_vpid( pid );

Или так:

	tsk = pid_task( find_vpid( pid ), PIDTYPE_PID ); 

2. Дисциплина планирования и параметры диспетчеризации, предписанные потоку, могут быть установлены в новые состояния так:

	struct sched_param { 
	   int sched_priority; 
	}; 
	int sched_setscheduler( struct task_struct *task, int policy, struct sched_param *parm ); 
	// Scheduling policies 
	#define SCHED_NORMAL  0 
	#define SCHED_FIFO    1 
	#define SCHED_RR      2 
	#define SCHED_BATCH   3 
	/* SCHED_ISO: reserved but not implemented yet */ 
	#define SCHED_IDLE    5 

3. Другие вызовы, имеющие отношение к приоритетам процесса:

	void set_user_nice( struct task_struct *p, long nice ); 
	int task_prio( const struct task_struct *p ); 
	int task_nice( const struct task_struct *p ); 

4. Разрешения на использование выполнения на разных процессорах в SMP системах (афинити-маска процесса):

	extern long sched_setaffinity( pid_t pid, const struct cpumask *new_mask ); 
	extern long sched_getaffinity( pid_t pid, struct cpumask *mask ); 

где (<linux/cpumask.h>):

	typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;

Вообще, во всём связанном с созданием нового потока в ядре, прослеживаются прямые аналогии с созданием параллельных ветвей в пользовательском пространстве, что очень сильно облегчает работу с такими механизмами.


Предыдущий раздел: Оглавление Следующий раздел:
Параллелизм и синхронизация   Синхронизации