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

UnixForum





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

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

Сигналы

Точно так же, как запуск процесса, по аналогии с пользовательским пространством, можно посылать из ядра сигналы UNIX как пользовательским процессам, так и потокам пространства ядра. Для уяснения возможностей использования сигналов из ядра (и в ядре) я воспользовался несколько видоизменённым (архив signal.tgz) проектом из [6]. Идея теста проста:

  • имеем пользовательское приложение sigreq («мишень» на которую направляются сигналы), и которое регистрирует полученные сигналы;
  • имеем модуль ядра lab3_ioctl_signal.ko, которому можно «заказать»: какому процессу (PID) отсылать сигнал и какой сигнал, пользовательское приложение, в качестве целеуказания мы и будем указывать sigreq;
  • и имеется диалоговый пользовательский процесс ioctl, который казывает модулю ядра: какой сигнал отсылать и кому.

ioctl.h :

	#define MYIOC_TYPE 'k' 
	#define MYIOC_SETPID  _IO(MYIOC_TYPE,1) 
	#define MYIOC_SETSIG  _IO(MYIOC_TYPE,2) 
	#define MYIOC_SENDSIG _IO(MYIOC_TYPE,3) 
	#define SIGDEFAULT SIGKILL 

- команды ioctl(), которые отрабатываются модулем: MYIOC_SETPID - установить PID процесса, которому будет направляться сигнал; MYIOC_SETSIG - установить номер отсылаемого сигнала; MYIOC_SENDSIG - отправить сигнал.

Собственно код модуля:

lab3_ioctl_signal.c :

	#include <linux/module.h> 
	#include "ioctl.h" 
	#include "lab_miscdev.h"
	
	static int sig_pid = 0; 
	static struct task_struct *sig_tsk = NULL; 
	static int sig_tosend = SIGDEFAULT; 
	
	static inline long mycdrv_unlocked_ioctl( struct file *fp, unsigned int cmd, unsigned long arg ) { 
	   int retval; 
	   switch( cmd ) { 
	      case MYIOC_SETPID: 
	         sig_pid = (int)arg; 
	         printk( KERN_INFO "Setting pid to send signals to, sigpid = %d\n", sig_pid ); 
	         /* sig_tsk = find_task_by_vpid (sig_pid); */ 
	         sig_tsk = pid_task( find_vpid( sig_pid ), PIDTYPE_PID ); 
	         break; 
	      case MYIOC_SETSIG: 
	         sig_tosend = (int)arg; 
	         printk( KERN_INFO "Setting signal to send as: %d \n", sig_tosend ); 
	         break; 
	      case MYIOC_SENDSIG: 
	         if( !sig_tsk ) { 
	            printk( KERN_INFO "You haven't set the pid; using current\n" ); 
	            sig_tsk = current; 
	            sig_pid = (int)current->pid; 
	         } 
	         printk( KERN_INFO "Sending signal %d to process ID %d\n", sig_tosend, sig_pid ); 
	         retval = send_sig( sig_tosend, sig_tsk, 0 ); 
	         printk( KERN_INFO "retval = %d\n", retval ); 
	         break; 
	      default: 
	         printk( KERN_INFO " got invalid case, CMD=%d\n", cmd ); 
	         return -EINVAL; 
	   } 
	   return 0; 
	} 
	
	static const struct file_operations mycdrv_fops = { 
	   .owner = THIS_MODULE, 
	   .unlocked_ioctl = mycdrv_unlocked_ioctl, 
	   .open = mycdrv_generic_open, 
	   .release = mycdrv_generic_release 
	}; 
	
	module_init( my_generic_init ); 
	module_exit( my_generic_exit ); 
	MODULE_AUTHOR("Jerry Cooperstein"); 
	MODULE_DESCRIPTION("LDD:1.0 s_13/lab3_ioctl_signal.c"); 
	MODULE_LICENSE("GPL v2");

- в этом файле содержится интересующий нас обработчик функций ioctl(), все остальные операции модуля (создание символьного устройства /dev/mycdrv, open(), close(), ...) - отнесены во включаемый файл lab_miscdev.h, общий для многих примеров, и не представляющий интереса — всё это было подробно рассмотрено ранее, при рассмотрении операций символьного устройства. Пока остановим внимание на группе функций, находящих процесс по его PID, что близко смыкается с задачей запуска процесса, рассматриваемой выше:

	#include <linux/sched.h> 
	struct task_struct *find_task_by_vpid( pid_t nr );
	#include <linux/pid.h> 
	struct pid *find_vpid( int nr );
	struct task_struct *pid_task( struct pid *pid, enum pid_type );
	enum pid_type { 
	   PIDTYPE_PID, 
	   PIDTYPE_PGID, 
	   PIDTYPE_SID, 
	   PIDTYPE_MAX 
	}; 

Тестовая задача, выполняющая последовательность команда ioctl() над модулем: установку PID процесса, установку номера сигнала — отправку сигнала:

ioctl.c :

	#include <stdio.h> 
	#include <stdlib.h> 
	#include <fcntl.h> 
	#include <sys/ioctl.h> 
	#include <signal.h> 
	#include "ioctl.h" 
	
	static void sig_handler( int signo ) { 
	   printf( "---> signal %d\n", signo ); 
	} 
	
	int main( int argc, char *argv[] ) { 
	   int fd, rc; 
	   unsigned long pid, sig; 
	   char *nodename = "/dev/mycdrv";
	   pid = getpid(); 
	   sig = SIGDEFAULT; 
	   if( argc > 1 ) sig = atoi( argv[ 1 ] ); 
	   if( argc > 2 ) pid = atoi( argv[ 2 ] ); 
	   if( argc > 3 ) nodename = argv[ 3 ]; 
	   if( SIG_ERR == signal( sig, sig_handler ) ) 
	      printf( "set signal handler error\n" ); 
	   /* open the device node */ 
	   fd = open( nodename, O_RDWR ); 
	   printf( "I opened the device node, file descriptor = %d\n", fd ); 
	   /* send the IOCTL to set the PID */ 
	   rc = ioctl( fd, MYIOC_SETPID, pid ); 
	   printf("rc from ioctl setting pid is = %d\n", rc ); 
	   /* send the IOCTL to set the signal */ 
	   rc = ioctl( fd, MYIOC_SETSIG, sig ); 
	   printf("rc from ioctl setting signal is = %d\n", rc ); 
	   /* send the IOCTL to send the signal */ 
	   rc = ioctl( fd, MYIOC_SENDSIG, "anything" ); 
	   printf("rc from ioctl sending signal is = %d\n", rc ); 
	   /* ok go home */ 
	   close( fd ); 
	   printf( "FINISHED, TERMINATING NORMALLY\n"); 
	   exit( 0 ); 
	}

Тестовая задача, являющаяся оконечным приёмником-регистраторов отправляемых сигналов:

sigreq.c :

	#include <stdio.h> 
	#include <stdlib.h> 
	#include <signal.h> 
	#include "ioctl.h" 
	
	static void sig_handler( int signo ) { 
	   printf( "---> signal %d\n", signo ); 
	} 
	
	int main( int argc, char *argv[] ) { 
	   unsigned long sig = SIGDEFAULT;
	   printf( "my own PID is %d\n", getpid() );.. 
	   sig = SIGDEFAULT; 
	   if( argc > 1 ) sig = atoi( argv[ 1 ] ); 
	   if( SIG_ERR == signal( sig, sig_handler ) ) 
	      printf( "set signal handler error\n" ); 
	   while( 1 ) pause(); 
	   exit( 0 ); 
	}

Примечание: В этом приложении (как и в предыдущем) для установки обработчика сигнала используется старая, так называемая «ненадёжная модель» обработки сигналов, использованием вызова signal(), но в данном случае это никак не влияет на достоверность получаемых результатов.

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

$ kill -l

	 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP 
	 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1 
	11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM 
	16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP 
	21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ 
	26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR 
	31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3 
	38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8 
	43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13 
	48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12 
	53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7 
	58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2 
	63) SIGRTMAX-1	64) SIGRTMAX	

Для проверок функционирования выберем безобидный сигнал SIGUSR1(сигнал номер 10):

$ ./sigreq 10

	my own PID is 10737 
	---> signal 10 

$ kill -n 10 10737

- вот как отреагировал процесс регистратор на получение сигнала. А теперь выполним весь комплекс: процесс ioctl последовательностью вызовов ioctl() заставляет загруженный модуль ядра отправить указанный сигнал процессу sigreq :

$ sudo insmod lab3_ioctl_signal.ko

$ lsmod | head -n2

	Module                  Size  Used by 
	lab3_ioctl_signal       2053  0. 

$ dmesg | tail -n2

	Succeeded in registering character device mycdrv 

$ cat /sys/devices/virtual/misc/mycdrv/dev

	10:56 

$ ls -l /dev | grep my

	crw-rw----  1 root root     10, 56 Май  6 17:15 mycdrv 

$ ./ioctl 10 11684

	I opened the device node, file descriptor = 3 
	rc from ioctl setting pid is = 0 
	rc from ioctl setting signal is = 0 
	rc from ioctl sending signal is = 0 
	FINISHED, TERMINATING NORMALLY 

$ dmesg | tail -n14

	Succeeded in registering character device mycdrv 
	 attempting to open device: mycdrv: 
	  MAJOR number = 10, MINOR number = 56 
	   successfully open  device: mycdrv: 
	I have been opened  1 times since being loaded 
	ref=1 
	Setting pid to send signals to, sigpid = 11684 
	Setting signal to send as: 10. 
	Sending signal 10 to process ID 11684 
	retval = 0 
	closing character device: mycdrv: 

$ ./sigreq 10

	my own PID is 11684 
	---> signal 10 
	^C 

Отправку сигнала в этой реализации осуществляет вызов send_sig(), он, и ещё большая группа функций, связанных с отправкой сигналов, определены в <linux/sched.h>, некоторые из которых:

	int send_sig_info( int signal, struct siginfo *info, struct task_struct *task ); 
	int send_sig( int signal, struct task_struct *task, int priv ); 
	int kill_pid_info( int signal, struct siginfo *info, struct pid *pid );
	int kill_pgrp( struct pid *pid, int signal, int priv ); 
	int kill_pid( struct pid *pid, int signal, int priv ); 
	int kill_proc_info( int signal, struct siginfo *info, pid_t pid ); 

Описания достаточно сложной структуры siginfo включено из заголовочных файлов пространства пользователя (/usr/include/asm-generic/siginfo.h):

	typedef struct siginfo { 
	   int si_signo; 
	   int si_errno; 
	   int si_code;
	...
	}

Тема сигналов чрезвычайно важная — на них основаны все механизмы асинхронных уведомлений, например, работа пользовательских API select() и pool(), или асинхронных операций ввода-вывода. Но тема сигналов и одна из самых слабо освещённых в литературе.


Предыдущий раздел: Оглавление Следующий раздел:
Запуск процессов из ядра   Операции I/O пространства пользователя