转自http://my.oschina.net/nalenwind/blog/125449
在Linux系统中信号是一个很重要的概念。信号是软件中断,它提供了一种处理异步事件的方法。
例如两个各自执行的进程需要对对方发送的信号做出响应而又不使用阻塞的方式,进程不能只是简单的测试一个变量来判断是否出现了一个信号,而是必须告诉内核此信号出现时应执行哪些操作。
信号的概念:
首先,每个信号都有一个名字,这些名字以SIG三个字符开头。这些信号在头文件<signal.h>中被
定义为正整数。
信号的实现:
在linux系统中,内核通过进程控制表对进程进行管理。进程控制表在数据结构上被定义为一个结
构体:
struct task_struct {
...
long signal; //信号,是位图,每一位代表一个信号。
...
};
其中的signal便用于实现信号的发送等功能。这么设计是因为信号是发送于进程之间的,内核通过
对目标进程的signal项的相应位置位,便完成了信号的发送。
信号的产生:
·当用户按某些终端键时,引发终端产生的信号。例:<Ctrl-C>产生中断信号SIGINT
·硬件异常产生信号:例如除数为0时,由硬件检测到通知内核,再由内核产生信号发送到进程中。
·某些系统调用:例如kill(2)可将信号发送给一个进程或进程组。
·当检测到某些软件条件发生时。
信号的处理:
我们必须在某个信号发生之前显示的告诉系统对这个信号我要执行哪些操作。通常有一下三种处理
方式:
(1)忽略此信号。大多数信号都可使用这种方式处理。
(2)捕捉信号。为了做到这一点,要通知内核在某种函数发生时调用一个用户函数。
(3)执行系统默认动作。系统对大多数信号的默认动作时终止进程。
本文将不会对30多种信号种类做详细介绍,下面介绍如何使用signal和sigaction系统调用注册信号处理
函数。
signal函数(不可靠的信号处理函数):
#include <signal.h>
void (*signal(int signo,void (*func)(int)))(int);
此函数原型有些复杂,我们使用typedef语句简化一下:
typedef void Sigfunc(int);
然后可将signal函数原型写成:
Sigfunc* signal(int,Sigfunc*);
这回我想大家应该都能看懂了吧,通过此声明我们可以知道signal函数以一个整形,和一个函数指针
为参数,返回一个函数指针。其中signo是我们要处理的信号名,func是处理此信号的回调函数(以
int为参数即要处理的信号,无返回值)。若signal函数成功返回,则返回func参数的设置,若出错,
则返回SIG_ERR。
形参func可取值SIG_IGN,SIG_DEF或信号处理函数。SIG_IGN表示忽略此信号,SIG_DEF表示使用系统
默认动作处理此信号,当指定函数地址时,则在信号发生时以该信号为参数调用该函数。需要注意的
是,在处理函数被调用过一次后,信号处理句柄又会恢复成默认处理句柄SIG_IGN。
查看系统头文件<signal.h>,则很可能找到如下声明:
#define SIG_ERR (void (*)(int)-1
#define SIG_IGN (void (*)(int)1
#define SIG_DEF (void (*)(int)0
示例程序如下:
01 | #include <signal.h> |
02 | #include <stdio.h> |
03 | static void sig_usr( int ); |
04 | int main( void ) |
05 | { |
06 | if ( signal (SIGUSR1,sig_usr)==SIG_ERR) |
07 | err_sys( "can't catch SIGUSR1" ); |
08 | if ( signal (SIGUSR2,sig_usr)==SIG_ERR) |
09 | err_sys( "can't catch SIGUSR2" ); |
10 | while ( true ) |
11 | pause(); |
12 | } |
13 | static void sig_usr( int signo) |
14 | { |
15 | if (signo==SIG_USR1){ |
16 | signal (SIG_USR1,sig_usr); //这样写的目的是为处理下一次信号发生而重置自己的处理函数。 |
17 | printf ( "received SIGUSR1\n" ); |
18 | } |
19 | else if (signo==SIG_USR2){ |
20 | signal (SIG_USR1,sig_usr); //同上 |
21 | printf ( "received SIGUSR2\n" ); |
22 | } |
23 | else |
24 | err_sys( "received signal %d\n" ,signo); |
25 | } |
sigaction函数(可靠的信号处理函数):
sigaction函数的功能是检查或修改与指定信号相关联的处理动作。
#include <signal.h>
int sigaction(int signo,const struct sigaction* act,struct sigaction* oact);
其中,参数signo是要检测或修改其具体动作的信号编号。若act指针非空,则要修改其动作。若oact指针
非空,则可通过oact指针返回该信号的上一个动作。因此oact是一个出口参数。sigaction使用如下结构:
1 | struct sigaction{ |
2 | void (*sa_handler)( int ); //信号处理函数,或SIG_IGN,SIG_DEF |
3 | sigset_t sa_mask; //信号掩码 |
4 | int sa_flags; //信号选项标志 |
5 | void (*sa_sigaction)( int ,siginfo_t*, void *); //信号恢复函数指针,系统内部使用 |
6 | } |
句柄SIG_IGN,那么在sa_handler 处理句柄可被调用前,sa_mask 字段就指定了需要加入到进程信号屏
蔽位图中的一个信号集。如果信号处理句柄返回,系统就会恢复进程原来的信号屏蔽位图。这样在一个
信号句柄被调用时,我们就可以阻塞指定的一些信号。当信号句柄被调用时,新的信号屏蔽位图会自动
地把当前发送的信号包括进去,阻塞该信号的继续发送。从而在我们处理一指定信号期间能确保阻塞同
一个信号而不让其丢失,直到此次处理完毕。另外,在一个信号被阻塞期间而又多次发生时通常只保存
其一个样例,也即在阻塞解除时对于阻塞的多个同一信号只会再调用一次信号处理句柄。在我们修改了
一个信号的处理句柄之后,除非再次更改,否则就一直使用该处理句柄。这与传统的signal()函数不一样。
signal()函数会在一处理句柄结束后将其恢复成信号的默认处理句柄。[引用自《linux内核完全注释》]
sigaction结构中的sa_flags用于指定其它一些处理信号的选项,详细见联机手册。
下面是使用sigaction函数实现signal函数的代码:
01 | Sigfunc* signal ( int signo,Sigfunc* func) |
02 | { |
03 | struct sigaction act,oact; |
04 | act.sa_handler=func; |
05 | sigemptyset(&act.sa_mask); |
06 | act.sa_flags=0; |
07 | if (signo==SIGALRM) |
08 | { |
09 | #ifdef SA_INTERRUPT |
10 | act.sa_flags |=SA_INTERRUPT; |
11 | #endif |
12 | } |
13 | else |
14 | { |
15 | #ifdef SA_RESTART |
16 | act.sa_flags |=SA_RESTART; |
17 | #endif |
18 | } |
19 | if (sigaction(signo,&act,&oact)<0) |
20 | return SIG_ERR; |
21 | return oact.sa_handler; |
22 | } |