信号:在生活中,我们遇到过不同种类的信号,比如:(交通信号,乃至某个人的表情,动作等带给你不同的信号)然而,在我们的linux下,我们最熟悉的就是,当遇到一个死循环的程序时,我们第一想到的就是按ctrl+c,此时这个进程立马终止,这是一种通过键盘产生的信号。而说起ctrl+c,就引出了前台进程和后台进程。当ctrl+c产生的信号只能发给前台进程。
用kill -l命令就可以查看信号了;
产生信号的另一种方式是:信号异常触发系统使该进程终止:
例子:
运行结果:
还有一种方式,通过指令来使该进程终止:
然后直接运行./test:
还有一种方式,通过alarm使进程终止;
说起alarm给大家举个例子吧,alarm意思是闹钟,在这里也同样代表着当你执行某个进程时,突然用alarm定时的时间到了,这时闹钟发挥了作用,使得你的进程被迫停止,比如:
这是一个统计1秒钟计数的程序,当一秒钟到时,就会被SIGALRM信号终止。
运行结果:
下面我们来说一下处理信号的几种方式吧!
忽略信号;执行默认;执行自定义(信号的捕捉)三种方式。(那么问题来了,什么时候处理呢?答案是适当的时候处理)
执行信号的处理动作称为信号递达,信号从产生到递达之间的状态,称为信号未决。进程可以选择阻塞某个信号,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
阻塞和忽略是不同,只要信号被阻塞就不会递达,而忽略则是在递达之后可选的一种处理动作。
信号在内核中是这样表示的:
block:代表屏蔽状态字(1表示阻塞,0表示不阻塞)
pending:代表未决(1表示未决,0表示可以递达)
handler:代表信号的处理方式(默认,忽略,自定义)
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。
上面那张表说明:SIGHUP是没有阻塞也没有产生,所以处理动作为默认处理动作
SIGINT的block为1,pending也为1,表示正在阻塞,无法递达。它的处理动作为忽略,但是在没有解除阻塞之前不能忽略该信号,很可能在解除阻塞前改变为其他的处理动作。
对于上述的三张表,操作系统中的每个进程运行时都会存在。
SIGQUIT的block为1,pending为0,说明正在被阻塞,解除阻塞后就可以递达。处理动作为用户自定义的处理动作。
还有一种现象是,对于解除阻塞之前可能会发送多次信号,这时操作系统该作何处理呢。这里主要是分为普通信号和实时信号,普通信号出现发送多次的情况,会当做是一次信号进行处理。而实时信号发送多次的情况,会将这多个信号存在一个队列中,分别处理各个信号。我们一般讨论的是普通信号,因此只记录一次,我们将未决和阻塞状态用同一个数据类型来存储sigset_t,信号集为sigset_t。还需要注意的一点是,阻塞信号集也叫做当前进程的信号屏蔽字。
说了信号集,我们之前说过这些普通信号是以位图的形式存放的。每一个bit位表示一种信号是否存在。
对于信号的操作,我们有一组信号集操作函数,如下:
函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。
函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表⽰示 该信号集的有效信号包括系统支
持的所有信号。
在使⽤用sigset_t类型的变量之前,⼀一定要调 ⽤用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。
做完初始化之后调用sigaddset和sigdelset来添加或者删除信号。
这四个函数成功返回0,失败返回-1;
sigismember用来表示某种信号是否出现在有效信号集中。出现返回1,不出现返回0.
*还有函数sigprocmask:(读取或更改进程的信号屏蔽字)
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
成功返回0,出错返回-1
这里的oset为输出型参数,如果oset为非空,则输出当前进程的信号屏蔽字,通过oset输出。如果set为非空,则更改信号屏蔽字,how指示如何更改。如果set和oset都为非空,则将当前进程的信号屏蔽字备份在oset中,然后再通过how参数更改信号屏蔽字。
how主要有三种表示:SIG_BLOCK(包含了我们希望添加的信号屏蔽字),SIG_UNBLOCK(包含了我们希望从信号屏蔽字中阻塞的信号),SIG_SETMASK(设置了当前的信号屏蔽字的值);
*函数sigpending:(读取当前进程的未决信号集)
#include <signal.h>
int sigpending(sigset_t *set);
set为输出型参数,将信号通过set输出
小栗子:
#include<stdio.h>
#include<signal.h>void PrintSigset(sigset_t *sig)
{int i = 0;for(i = 1; i < 32; i++){if(sigismember(sig,i)){printf("1 ");}else{printf("0 ");}}printf("\n");
}int main()
{sigset_t sigset,osigset;sigemptyset(&sigset);sigemptyset(&osigset);sigaddset(&sigset,SIGINT);sigprocmask(SIG_BLOCK,&sigset,&osigset);while(1){sigpending(&sigset);PrintSigset(&sigset);sleep(1);}return 0;
}
这个程序实现了将2号信号设置为阻塞信号。所以一直无法递达。
程序运行时,每秒钟把各信号的未决状态打印一遍,由于我们阻塞了SIGINT信号,按Ctrl-C将会使SIGINT信号处于未决
状态,按Ctrl-\和ctrl+z仍然可以终止程序,因为SIGQUIT信号没有阻塞。
运行结果:
还有一个小栗子,我们将2号信号阻塞之后,5秒后解除阻塞。并打印原来信号的状态。
#include<stdio.h>
#include<signal.h>void PrintSigset(sigset_t *sig)
{int i = 0;for(i = 1; i < 32; i++){if(sigismember(sig,i)){printf("1 ");}else{printf("0 ");}}printf("\n");
}void handler(int sig)
{printf("pid: %d sig:%d \n",getpid(),sig);
}
int main()
{sigset_t sigset,osigset;sigemptyset(&sigset);sigemptyset(&osigset);sigaddset(&sigset,SIGINT);sigprocmask(SIG_BLOCK,&sigset,&osigset);int count = 0;signal(2,handler);//signal(2,SIG_DFL);while(1){sigpending(&sigset);PrintSigset(&sigset);sleep(1);if(count++ > 5){sigprocmask(SIG_SETMASK,&osigset,NULL);count = 0;}}return 0;
}
运行结果: