信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。
信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。
2、信号的种类
可以从两个不同的分类角度对信号进行分类:(1)可靠性方面:可靠信号与不可靠信号;(2)与时间的关系上:实时信号与非实时信号。在《Linux环境进程间通信(一):管道及有名管道》的附1中列出了系统所支持的所有信号。
2.1可靠信号与不可靠信号
2.1.1 "不可靠信号"
Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做"不可靠信号",信号值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是:
进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。
信号可能丢失,后面将对此详细阐述。
因此,早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。
Linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,Linux下的不可靠信号问题主要指的是信号可能丢失。
2.1.2 "可靠信号"
随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种Unix版本分别在这方面进行了研究,力图实现"可靠信号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。POSIX.4对可靠信号机制做了标准化。但是,POSIX只对可靠信号机制应具有的功能以及信号机制的对外接口做了标准化,对信号机制的实现没有作具体的规定。
可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。
但应注意的是:信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。
信号值小于SIGRTMIN的信号为不可靠信号,信号值在SIGRTMIN及SIGRTMAX之间的信号为可靠信号。
2.2 实时信号与非实时信号
早期Unix系统只定义了32种信号,Ret hat7.2支持64种信号,编号0-63(SIGRTMIN=31,SIGRTMAX=63),将来可能进一步增加,这需要得到内核的支持。前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
3 进程对信号的响应
进程可以通过三种方式来响应一个信号:(1)忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;(3)执行缺省操作,Linux对每种信号都规定了默认操作,详细情况请参考[2]以及其它资料。注意,进程对实时信号的缺省反应是进程终止。Linux究竟采用上述三种方式的哪一个来响应信号,取决于传递给相应API函数的参数。
4 信号的发送
发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。这里仅给出kill()、raise()。
4.1 kill()
#i nclude <sys/types.h>
#i nclude <signal.h>
int kill(pid_t pid,int signo)
参数pid的值 信号的接收进程
---------------------------------------
pid>0 进程ID为pid的进程
pid=0 同一个进程组的进程
pid<0 pid!=-1 进程组ID为 -pid的所有进程
pid=-1 除发送进程自身外,所有进程ID大于1的进程
Sinno是信号值,当为0时(即空信号),实际不发送任何信号,但照常进行错误检查,因此,可用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)。
Kill()最常用于pid>0时的信号发送,调用成功返回 0; 否则,返回 -1。注:对于pid<0时的情况,对于哪些进程将接受信号,各种版本说法不一,其实很简单,参阅内核源码kernal/signal.c即可,上表中的规则是参考red hat 7.2。
4.2 raise()
#i nclude <signal.h>
int raise(int signo)
向进程本身发送信号,参数为即将发送的信号值。调用成功返回 0;否则,返回 -1。
#i nclude <stdlib.h>
void abort(void);
向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。
5 信号的安装(设置信号关联动作)
如果进程要处理某一信号,那么就要在进程中安装该信号。安装信号主要用来确定信号值及进程针对该信号值的动作之间的映射关系,即进程将要处理哪个信号;该信号被传递给进程时,将执行何种操作。
linux主要有两个函数实现信号的安装:signal()、sigaction()。其中signal()在可靠信号系统调用的基础上实现, 是库函数。它只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与 sigqueue() 系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。sigaction()优于signal()主要体现在支持信号带有参数。
5.1 signal()
#i nclude <signal.h>
void (*signal(int signum, void (*handler))(int)))(int);
如果该函数原型不容易理解的话,可以参考下面的分解方式来理解:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler));
第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。
如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR。
5.2 sigaction()
#i nclude <signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等。
信号是软件中断。很多比较重要的应用程序都需要处理信号。信号提供了一种处理异步事件的方法.每个信号都有一个名字,这些名字都以三个字符SIG开头。在头文件<signal.h>中,这些信号都被定义为正整数(信号编号)。
很多条件可以产生一个信号。
1.当用户按某些终端键时,产生信号。如:用户按ctrl+c 产生中断信号,中止当前进程。
2.硬件异常产生信号。如:除数为0,无效的存储访问等等。这些条件通常由硬件检测到,并将其通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。
3.进程用kill(1) 命令将信号发送给另一个进程或进程组。自然,有些限制:按收信号进程和发送信号进程的所有这者必须相同,或发送信号的所有者必须是超级用户。
4.用户可用kill(1) 命令将信号发送给其他进程。此程序是kill函数的界面,常用此命名终止一个失控的后台进程。
5.当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。
在Redhat上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 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 32) SIGRTMIN 33) SIGRTMIN+1
34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5
38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9
42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13
46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14
50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10
54) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-6
58) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-2
62) SIGRTMAX-1 63) SIGRTMAX
二、不可靠信号安装和发送函数。
1.名称: signal
功能:信号安装(设置信号关联动作)
头文件:#i nclude <signal.h>
函数原形:typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
参数:
signum 信号名
handler 操作方式
返回值: 成功则为以前的信号处理配置,若出错则为SIG_ERR
signum参数是信号名,handler的值是:1。常数SIG_IGN:表示忽略此信号。2。SIG_DFL:表示接到此信号后的动作是系统默认动作。3。函数地址:表示我们捕捉此信号,并且调用用户设置的信号处理程序。当调用signal设置信号处理程序时,第二个参数是指向该函数的指针。
***************************************
/*10_1.c*/
#i nclude <stdio.h>
#i nclude <unistd.h>
#i nclude <signal.h>
main(){
signal(SIGINT,SIG_IGN);
printf("hello!n");
sleep(10);
printf("hellon");
}
***************************************
上面的代码忽略了SININT信号.
此程序执行会在屏幕上先打印一个“hello!”,然后睡眠10分钟。在此期间用户按ctrl+c没有任何反应,因为signal函数已将SIGINT信号(按ctrl+c会产生)设为忽略。
然后看下面的程序:
**************************************
/*10_2.c*/
#i nclude <stdio.h>
#i nclude <unistd.h>
#i nclude <signal.h>
void catch(int sig);
main(){
signal(SIGINT,catch);
printf("hello!n");
sleep(10);
printf("hello!n");
}
void catch(int sig){
printf("catch signaln");
}
***************************************
当用户按下ctrl+c时,进程被中断,catch()被执行.中断处理函数处理完毕后,转回断点执行下面的指令.
当编写自己的中断处理函数时,注意下面两点:
1.信号不能打断系统调用.
2.信号不能打断信号处理函数.
2.
名称: pause
功能:等待信号
头文件:#i nclude <unistd.h>
函数原形:int pause(void);
参数: 无
返回值: -1,errno设置为EINTR
pause函数使调用进程挂起直至捕捉到一个信号,pause才返回。在这种情况下,pause返回-1,errno设置为EINTR..
下面是一个例子:
******************************************
/*10_3.c*/
#i nclude <signal.h>
static void sig_usr(int signo);
int main()
{
if(signal(SIGUSR1,sig_usr)==SIG_ERR)
perror(SIGUSR1);
if(signal(SIGUSR2,sig_usr)==SIG_ERR)
perror(SIGUSR2);
while(1)
pause();
}
static void sig_usr(int signo)
{
if(signo==SIGUSR1)
printf(“received SIGUSR1\n”);
else if(signo==SIGUSR2)
printf(“received SIGUSR2\n”);
else
printf(“received signal %d\n”,signo);
}
******************************************
pause();函数使调用进程挂起,直至捕捉到一个信号。
下面我们来运行一下:
****************************************************
#./10_1 & 在后台运行进程
[3] 18864
#.kill -USR1 18864 向该进程发送SIGUSR1
received SIGUSR1
# kill –USR2 18864 向该进程发送SIGUSR1
received SIGUSR2
# kill 18864 向该进程发送SIGTERM
[3]+ Terminated ./signal
可以看到当用户kill -USR1 18864的时候产生了SIGUSR1信号,signal 定义了处理此信号要调用sig_usr函数,所以就在屏幕上打印出received SIGUSR1。
shell自动将后台进程对中断和退出信号的处理方式设置为忽略。于是当按中断键时就不会影响到后台进程。如果没有执行这样的处理,那么当按中断键时,它不但会终止前台进程,还会终止所以的后台进程。
我们还应注意的是,我们不能在信号处理程序中调用某些函数,这些函数被称为不可重入函数,例如malloc,getpwnam..那是因为当发生中断的时候系统有可能正在执行这些函数,在中断中调用这些函数可能会覆盖原来的信息,因而产生错误。
3.
名称: kill/raise
功能: 信号发送函数
头文件: #i nclude <signal.h>
函数原形: int kill(pid_t pid,int signo);
int raise(int signo);
参数:
pid 进程id
signo 信号
返回值: 若成功返回0,若出错返回-1
kill函数将信号发送给进程或进程组。raise函数则允许进程向自己发送信号。
raise(signo)等价于kill(getpid(),signo);
kill的pid函数有4种不同的情况:
pid>0 将信号发送给进程ID为pid的进程。
pid==0将信号发送给与发送进程同一组的所有进程。
pid<0 将该信号发送给其进程组id等于pid绝对值。
pid==-1将信号发送给进程有权向它发送信号的系统上的所有进程。
进程将信号发送给其他进程需要许可权。超级用户可将信号发送另一个进程。对于非超级用户,其基本规则是发送者的实际或有效用户ID必须等于接收者的实际或有效用户ID。
***************************************************
/*10_4.c*/
#i nclude <signal.h>
#i nclude <stdio.h>
void sig_usr(int);
main()
{
if(signal(SIGINT,sig_usr)==SIG_ERR)
perror(“error”);
while(1);
}
void sig_usr(int signo)
{
if(signo==SIGINT)
printf(“received SIGINT\n”);
kill(getpid(),SIGKILL);
}
***************************************************
程序运行后,当用户按ctrl+c后,程序调用信号处理函数输出received SIGINT,然后调用kill函数中止进程。此程序的kill(getpid(),SIGKILL);也可以写成raise(SIGKILL);
4.
名称: alarm
功能:set an alarm clock for delivery of a signal
头文件:#i nclude <unistd.h>
函数原形: unsigned int alarm(unsigned int seconds);
参数:seconds 时间
返回值: 0或以前设置时间的剩余数
使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻时间值会被超过。当所设时间值被超过后,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其默认动作是终止该进程。
******************************************************
/*10_5.c*/
#i nclude <signal.h>
#i nclude <unistd.h>
main()
{
unsigned int i;
alarm(1);
for(i=0;1;i++)
printf("I=%d",i);
}
*****************************************************
下面这个函数会有什么结果呢?
SIGALRM的缺省操作是结束进程,所以程序在1秒之后结束,你可以看看你的最后I值为多少,来比较一下大家的系统性能差异(我的是40300)。
5.
名称:abort
功能: 信号发送函数
头文件: #i nclude <stdlib.h>
函数原形:void abort(void);
参数:无
返回值:无
此函数将SIGABRT信号发送给调用进程。进程不应该忽略此信号。
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
三、可靠信号安装和发送函数。
可靠信号的处理函数和不可靠信号的处理函数基本原理是一样的,只不过是可靠信号的处理函数支持排队,信号不会丢失。
6.
名称: sigaction
功能: 可靠信号的安装函数
头文件: #i nclude <signal.h>
函数原形: int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);
参数:
返回值: 若成功返回0,若出错返回-1。
sigaction结构的原形为:
struct sigaction {
void (*sa_handler)(int signo);
void (*sa_sigaction)(int siginfo_t *info,void *act);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore)(void);
}
这个函数和结构看起来是不是有点恐怖呢。不要被这个吓着了,其实这个函数的使用相当简单的。我们先解释一下各个参数的含义。 signo很简单就是我们要处理的信号了,可以是任何的合法的信号。有两个信号不能够使用(SIGKILL和SIGSTOP)。 act包含我们要对这个信号进行如何处理的信息。oact更简单了就是以前对这个函数的处理信息了,主要用来保存信息的,一般用NULL就OK了。
信号结构有点复杂。不要紧我们慢慢的学习。
sa_handler是一个函数型指针,这个指针指向一个函数,这个函数有一个参数。这个函数就是我们要进行的信号操作的函数。 sa_sigaction,sa_restore和sa_handler差不多的,只是参数不同罢了。这两个元素我们很少使用,就不管了。
sa_flags用来设置信号操作的各个情况。一般设置为0好了。sa_mask用来设置信号屏蔽字,将在后面介绍。
在使用的时候我们用sa_handler指向我们的一个信号操作函数,就可以了。sa_handler有两个特殊的值:SIG_DEL和SIG_IGN。SIG_DEL是使用缺省的信号操作函数,而SIG_IGN是使用忽略该信号的操作函数。
这个函数复杂,我们使用一个实例来说明。下面这个函数可以捕捉用户的CTRL+C信号。并输出一个提示语句。
**********************************************
/*10_6.c*/
#i nclude <stdio.h>
#i nclude <signal.h>
#define PROMPT "catch the signal of ‘ctrl+c’\nplease enter ‘ctrl+z’ to exit\n"
char *prompt=PROMPT;
void ctrl_c_op(int signo) /*信号处理程序*/
{
write(STDERR_FILENO,prompt,strlen(prompt));
}
int main()
{
struct sigaction act;
act.sa_handler=ctrl_c_op;
act.sa_flags=0;
if(sigaction(SIGINT,&act,NULL)<0)
{
preeor(“error”);
exit(1);
}
while(1);
}
**********************************************
运行程序后,当用户按ctrl+c(会产生SIGINT信号)后屏幕上会打印。
catch the signal of ‘ctrl+c’
please enter ‘ctrl+z’ to exit
也就是说当用户按ctrl+c时我们调用我们自己写的函数来处理中断。
7.
名称: sigqueue
功能: 可靠信号的发送函数
头文件: #i nclude <signal.h>
函数原形: int sigqueue(pid_t pid,int sig,const union sigval value);
参数:
返回值: 若成功返回0,若出错返回-1。
typedef union sigval
{
int sival_int;
void *sival_ptr;
}sigval_t;
sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果sig为0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。
在调用sigqueue时,sigval_t指定的信息会拷贝到3参数信号处理函数的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。
注:sigqueue()发送非实时信号时,第三个参数包含的信息仍然能够传递给信号处理函数; sigqueue()发送非实时信号时,仍然不支持排队,即在信号处理函数执行过程中到来的所有相同信号,都被合并为一个信号。
四、信号屏蔽字:
有时候我们希望进程正确的执行,而不想进程受到信号的影响,比如我们希望上面那个程序在1秒钟之后不结束。这个时候我们就要进行信号的操作了。
信号操作最常用的方法是信号屏蔽。信号屏蔽要用到下面的几个函数。
sigemptyset,sigfillset,sigaddset,sigdelset,sigismember,sigprocmask。下面对他们分别进行讲解。
8.
名称: sigemptyset/sigfillset/sigaddset/sigdelset/sigismember
功能: 处理信号集
头文件: #i nclude <signal.h>
函数原形:
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signum);
int sigdelset(sigset_t *set,int signum);
int sigismember(const sigset_t *set,int signum);
参数:
set 信号集
signum 信号
返回值:若成功返回0,若出错返回-1。
若真返回1,若假返回0,若出错返回-1。
我们需要有一个能表示多个信号—信号集的数据类型。我们将在诸如sigprocmask之类的函数中使用这种数据类型,以便告诉内核不允许发生该信号集中的信号。上面的5个函数可以对信号集进行处理。
函数sigemptyset 初始化由set指向的信号集,清除其中所有信号。函数sigfillset初始化由set指向的信号集,使其包含所有信号。所以信号在使用信号集前,要对信号集调用sigemptyset或sigfillset一次。
函数sigaddset 将一个信号添加到现有集中,sigdelset则从信号集中删除一个信号。对所有以信号集作为参数的函数,我们总是以信号集地址作为其传送的参数。
gismember查询信号是否在信号集合之中。
下面的例子:
*******************************************
/*10_7.c*/
#i nclude <stdio.h>
#i nclude <signal.h>
main()
{
sigset_t *set;
set=(sigset_t*)malloc(sizeof(set));
sigemptyset(set);/*初始化信号集*/
sigaddset(set,SIGUSR1);/*添加信号SIGUSR1到信号集中*/
sigaddset(set,SIGINT);/*添加信号SIGUSR2到信号集中*/
if((sigismember(set,SIGUSR1))==1)/*测试信号SIGUSR1是否在信号集中*/
printf(“SIGUSR1\n”);
if((sigismember(set,SIGUSR2))==1)
printf(“SIGUSR2\n”);
if((sigismember(set,SIGINT))==1)
printf(“SIGINT\n”);
}
*******************************************
下面是执行结果:
# ./10_7
SIGUSR1
SIGINT
程序先初始化信号集,清除其中所有信号,然后把SIGUSR1和SIGINT添加到信号集中,然后测试SIGUSR1,SIGUSR2,SIGINT信号是否在信号集中。因为SIGUSR2不在信号集中,所以程序并不打印SIGUSR2。
9.
名称: sigprocmask
功能: 检测或更改信号屏蔽字
头文件: #i nclude <signal.h>
函数原形: int sigprocmask(int how,const sigsett_t *set,sigset_t *oldset);
参数: how 操作方式
set 信号集
oldest
返回值: 若成功返回0,若出错返回-1。
每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。
sigprocmask是最为关键的一个函数。在使用之前要先设置好信号集合set。这个函数的作用是将指定的信号集合set加入到进程的信号阻塞集合之中去,如果提供了oldset那么当前的进程信号阻塞集合将会保存在oldset里面。参数how决定函数的操作方式。
SIG_BLOCK:增加一个信号集合到当前进程的阻塞集合之中。
SIG_UNBLOCK:从当前的阻塞集合之中删除一个信号集合。
SIG_SETMASK:将当前的信号集合设置为信号阻塞集合。
我们把10_7.c稍微修改一下,看看sigprocmask函数的功能。
**********************************************
/*10_8.c*/
#i nclude <stdio.h>
#i nclude <signal.h>
main()
{
sigset_t *set;
set=(sigset_t*)malloc(sizeof(set));
sigemptyset(set);/*定义信号集set*/
sigaddset(set,SIGINT);/*把信号SIGINT添加到信号集中*/
sigprocmask(SIG_SETMASK,set,NULL);/*把set设置为信号阻塞集合*/
wile(1);/*死循环*/
}
***********************************************
程序先定义信号集set,然后把信号SIGINT添加到set信号集中,最后把set设置为信号阻塞集合。当我们运行程序时,进程进入死循环。我们按“ctrl+c”系统并没有中断程序,因为我们已经把SIGINT信号屏蔽掉了。我们可以按”ctrl+z”来结束程序。
sigeprocmask函数通常和sigemptyset/sigfillset/sigaddset/sigdelset/sigismember函数配合使用,主要有两种用途:
1.我们不希望某些不太重要的信号来影响我们的进程,我们就可以把这些信号添加到信号屏蔽集中。使它们不打扰进程的执行。
2.如果系统现在很忙,没有时间及时相应信号,进程可以先把信号阻塞掉,等系统有空闲时间在去相应,这也保证了信号的可靠性。下面的函数可以做到这一点。
10.
名称: sigpending
功能: 返回信号集
头文件: #i nclude <signal.h>
函数原形: int sigpending(sigset_t *set);
参数:
返回值: 若成功返回0,若出错返回-1。
我们要注意的是,阻塞信号并不是丢弃信号,它们被保存在一个进程的信号阻塞队列里,sigpending可以获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回这些信号。
下面是一个例子:
*************************************************
/*10_9.c*/
#i nclude <stdio.h>
#i nclude <signal.h>
#i nclude <stdlib.h>
int main(void)
{
sigset_t newmask,oldmask,pendmask;
sigemptyset(&newmask); /*初始化信号集*/
sigaddset(&newmask,SIGINT); /*添加信号SIGINT到信号集*/
if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0) /*信号集newmask设置为阻塞信号集*/
perror(“error”);
sleep(5);
if(sigpending(&pendmask)<0) /*把当前已递送到进程,却被阻塞的信号保存到进程的信号阻 塞队列里*/
perror(“error”);
if(sigismember(&pendmask,SIGINT)) /*检测进程阻塞队列里是否有SIGINT信号*/
printf(“\nSIGINT pending\n”);
exit(0);
}
*************************************************
程序开始运行,如果我们在5秒钟内按”ctrl+c”,SIGINT信号会被传递到进程,但是由于进程设置了信号阻塞集,信号SIGINT在这个集合中,所有这个信号被阻塞。并由sigpend保存到进程的信号阻塞队列pendmask里.然后我们用sigismember检测SIGINT是否在信号阻塞队列pendmask里。
11.
名称: sigsuspend
功能:
头文件: #i nclude <signal.h>
函数原形: int sigsuspend(const sigset_t *sigmask);
参数: sigmask 要替换的进程信号屏蔽字。
返回值: -1,errno设置为EINTR.
sigsuspend用于在接收到某个信号之前, 临时用sigmask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。
但要注意的是,sigsuspend的整个操作都是原子的,也就不允许被打断的。sigsuspend的整个原子操作过程为:
(1) 设置新的mask阻塞当前进程;
(2) 收到信号,恢复原先mask;
(3) 调用该进程设置的信号处理函数;
(4) 待信号处理函数返回后,sigsuspend返回。
这种技术的功能有:
1.可以保护不希望由信号中断的代码临界区。
2.等待一个信号处理程序设置一个全局变量。
3.实现父子进程之间的同步。
这个函数的执行步骤有点难理解,但我们仔细分析一下就会明白,sigsuspend函数是先用我们定义的sigmask来暂时替代信号屏蔽集,然后阻塞当前进程,收到信号时调用信号处理函数函数对信号进行处理,处理后返回。
下面是函数的一个例子:
*******************************************
/*10_10.c*/
#i nclude <signal.h>
#i nclude <stdio.h>
#i nclude <stdlib.h>
static void sig_int(int);
int main(void)
{
sigset_t newmask,oldmask,zeromask;
if(signal(SIGINT,sig_int)==SIG_ERR) /*设置信号处理,调用信号处理函数*/
perror(“signal error”);
sigemtyset(&zeromask); /*初始化zeromask信号集*/
sigemtyset(&newmask); /*初始化newmask信号集*/
if(sigprocmask(SIG_BLICK,&newmask,&oldmask)<0) /*把信号集newmask设置为信号屏蔽集*/
perror(“SIG_BLOCK error”);
printf(“In critical region: SIGINT\n”);
if(sigsuspend(&zeromask)!=-1) /*用zeromask代替信号屏蔽集,然后阻塞信号,如果有信号来通过signal调用处理函数,最后返回*/
perror(“sigsuspend error”);
printf(“After return from sigsuspend: SIGINT\n”);
sleep(5);
exit(0);
}
static void sig_int(int signo) /*信号处理函数
{
printf(“In sig_int: SIGINT\n”);
}
*******************************************
程序先打印In critical region: SIGINT,然后执行sigsuspend阻塞信号,当用户按“ctrl+c”时进程通过signal调用信号处理函数sig_int打印In sig_int: SIGINT,返回后打印After return from sigsuspend: SIGINT。然后程序休眠5秒钟,在这5秒钟如果用户按“ctrl+c”进程是不会有反应的,因为调用完sigsuspend进程就恢复了原先的屏蔽集。而原先的屏蔽集屏蔽了SIGINT信号。
12.
名称: sigsetjmp/siglongjmp
功能: save stack context for non-local goto
头文件: #i nclude <setjmp.h>
函数原形:
int sigsetjmp(sigjmp_buf env,int savemask);
void siglongjmp(sigjmp_buf env,int val);
参数:
返回值: 若直接调用则为0,若从sigsetjmp调用返回则为非0。
这两个函数和setjmp,longjmp之间的唯一区别是sigsetjmp增加了一个参数。如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字。调用siglongjmp时。如果带非0 savemask的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。
附:内核相关原码
/*
* linux/kernel/signal.c
*
* (C) 1991 Linus Torvalds
*/
#i nclude <linux/sched.h>
#i nclude <linux/kernel.h>
#i nclude <asm/segment.h>
#i nclude <signal.h>
volatile void do_exit(int error_code);
int sys_sgetmask()
{
return current->blocked;
}
int sys_ssetmask(int newmask)
{
int old=current->blocked;
current->blocked = newmask & ~(1<<(SIGKILL-1));
return old;
}
static inline void save_old(char * from,char * to)
{
int i;
verify_area(to, sizeof(struct sigaction));
for (i=0 ; i< sizeof(struct sigaction) ; i++) {
put_fs_byte(*from,to);
from++;
to++;
}
}
static inline void get_new(char * from,char * to)
{
int i;
for (i=0 ; i< sizeof(struct sigaction) ; i++)
*(to++) = get_fs_byte(from++);
}
int sys_signal(int signum, long handler, long restorer)
{
struct sigaction tmp;
if (signum<1 || signum>32 || signum==SIGKILL)
return -1;
tmp.sa_handler = (void (*)(int)) handler;
tmp.sa_mask = 0;
tmp.sa_flags = SA_ONESHOT | SA_NOMASK;
tmp.sa_restorer = (void (*)(void)) restorer;
handler = (long) current->sigaction[signum-1].sa_handler;
current->sigaction[signum-1] = tmp;
return handler;
}
int sys_sigaction(int signum, const struct sigaction * action,
struct sigaction * oldaction)
{
struct sigaction tmp;
if (signum<1 || signum>32 || signum==SIGKILL)
return -1;
tmp = current->sigaction[signum-1];
get_new((char *) action,
(char *) (signum-1+current->sigaction));
if (oldaction)
save_old((char *) &tmp,(char *) oldaction);
if (current->sigaction[signum-1].sa_flags & SA_NOMASK)
current->sigaction[signum-1].sa_mask = 0;
else
current->sigaction[signum-1].sa_mask |= (1<<(signum-1));
return 0;
}
void do_signal(long signr,long eax, long ebx, long ecx, long edx,
long fs, long es, long ds,
long eip, long cs, long eflags,
unsigned long * esp, long ss)
{
unsigned long sa_handler;
long old_eip=eip;
struct sigaction * sa = current->sigaction + signr - 1;
int longs;
unsigned long * tmp_esp;
sa_handler = (unsigned long) sa->sa_handler;
if (sa_handler==1)
return;
if (!sa_handler) {
if (signr==SIGCHLD)
return;
else
do_exit(1<<(signr-1));
}
if (sa->sa_flags & SA_ONESHOT)
sa->sa_handler = NULL;
*(&eip) = sa_handler;
longs = (sa->sa_flags & SA_NOMASK)?7:8;
*(&esp) -= longs;
verify_area(esp,longs*4);
tmp_esp=esp;
put_fs_long((long) sa->sa_restorer,tmp_esp++);
put_fs_long(signr,tmp_esp++);
if (!(sa->sa_flags & SA_NOMASK))
put_fs_long(current->blocked,tmp_esp++);
put_fs_long(eax,tmp_esp++);
put_fs_long(ecx,tmp_esp++);
put_fs_long(edx,tmp_esp++);
put_fs_long(eflags,tmp_esp++);
put_fs_long(old_eip,tmp_esp++);
current->blocked |= sa->sa_mask;
}