Linux下的信号
信号是操作系统发给进程的一种信息,进程会针对接收到的信息做出相应的处理。
前面谈到一个概念,叫做信号量,这里所说的信号量和我们今天谈到的信号,除了名字相似,事实上并没有任何联系,是两个完全不相关的概念,故不可混为一谈。
信号是如何产生的呢?先来说说熟悉的场景:
用户输入命令,在前台启动一个进程,然后按下Ctri+C这个组合键,键盘产生一个硬件中断,终端驱动将Ctrl+C解释成一个SIGINT信号,写入该进程的PCB中,这个过程也可以认为操作系统发送了一个SIGINT信号给该进程。
这里提到一个概念,前台。一个命令后面加&就可以放到后台运行,这样Shell不必等待进程运行结束就可以接收新的命令,启动新进程。Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到组合键产生的信号。
在前台进程运行的任意时刻都有可能接收到一个SIGINT信号,所以信号相对于进程来说是异步的。
与异步相对的还有一个概念叫做同步。
简单的可以这么理解,异步就是临时起意想要做的事情,同步就是两者提前商量好到某一时刻干某件事情。
那么如何我们操作系统中到底有多少信号呢?用kill -l
命令就可以查看。
这些编号后面对应的就是每一个信号,从我们系统中的编写习惯我们可以猜测这可能是一个宏,事实上也的确是这样的,这些定义可以在signal.h中找到。
从图片中观察34-64的信号都很类似,这些都是实时信号。又被认为是可靠信号,信号不会丢失,执行完处理动作后,不会恢复成缺省处理动作。我们这里主要讨论非实时信号。
刚才只说了一种信号产生的方式,这里我们来把信号产生的方式都总结一下:
- 组合键:Ctrl+C产生SIGINT信号,Ctrl+\产生SIGQUIT信号,Ctrl+Z产生SIGTSTP信号。
- 硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。如除零异常,就会发送SIGFPE信号。
- 一个进程调用kill(2)函数可以发送信号给另一个进程。
- 软件条件产生,如管道破裂。
信号常见的处理方式有三种:
- 忽略
- 执行默认处理
- 执行自定义处理
忽略就是我们收到了信号,但是处理方式就是不做任何处理。那么如果有进程在操作系统中出了异常,那是不是就无法终止了呢?并非如此,因为SIGQUIT(3)和SIGKILL(9)号信号是无法被忽略的。
默认处理就是原本在系统中内置的处理方式。
自定义处理我们在后面做出解释。
然后来谈谈其他一些相关概念:
- 实际执行信号的处理动作被称为信号递达(忽略处理也是一种递达)
- 信号从产生到递达之间的状态,称为信号未决
- 进程可以选择阻塞某个信号
-
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达动作
每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。在系统中大概和下图类似:
在每个进程的PCB中分别都维护了这样一个数据结构,信号产生时,内核在控制块中设置该信号的未决标志,直到信号递达才清除该标志。上图SIGHUP信号未阻塞也未产生,当它递达时执行默认处理动作。SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有集合改变处理动作之后再解除阻塞。如果某一信号未产生过,但是block表中被修改为1,那么一旦产生将被阻塞,再未解除阻塞之前,这个信号将始终不能递达。如果将这个信号函数指针的指向改为自定义函数的地址,那么它的处理动作就是自定义了。
POSIX允许系统递达某一信号一次或多次,Linux下是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。
捕捉信号:
如果信号的处理动作是用户自定义函数,在信号递达时候就调用这个函数,这称之为捕捉信号。由于信号处理函数的代码是在用户空间实现的,所以执行对信号的处理时会陷入内核,其流程图大致如下:
从中我们可以看到,信号在用户和内核之间的切换可多达四次,每个用蓝色圈出来的部分就是一次上下文切换。这就是信号捕捉时在操作系统中调用的大致过程。
然后我们用一段代码来演示信号捕捉的过程。
模拟实现sleep函数
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sig_alrm(int s)
{
//printf("signal:%d,兄弟,你弄不死我\n",s);
}
unsigned int mysleep(unsigned int nsecs)
{
struct sigaction new, old;
sigset_t newmask, oldmask, suspmask;
unsigned int unslept = 0;
new.sa_handler = sig_alrm;
sigemptyset(&new.sa_mask);
new.sa_flags = 0;
sigaction(SIGALRM, &new, &old);
//阻塞
sigemptyset(&newmask);
sigaddset(&newmask,SIGALRM);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
alarm(nsecs);
suspmask = oldmask;
sigdelset(&suspmask, SIGALRM);
sigsuspend(&suspmask);
unslept = alarm(0);
sigaction(SIGALRM, &old, NULL);
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return unslept;
}
int main()
{
mysleep(5);
printf("5 seconds passed\n");
//signal(2,hander);
//alarm(5);
// while(1)
// {
// printf("I am a Running proggram\n");
// sleep(1);
// }
return 0;
}
打印出信号中的位图:
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
void printsigset(sigset_t *set)
{
int i = 0;
for(; i<32; ++i)
{
if( sigismember( set,i ) ){
putchar('1');
}
else{
putchar('0');
}
}
puts("");
}
int main()
{
sigset_t s,p;
sigemptyset(&s);
sigaddset(&s, SIGINT);
sigprocmask(SIG_BLOCK, &s, NULL);
while(1)
{
sigpending(&p);
printsigset(&p);
sleep(1);
}
return 0;
}
上一篇: DRF 序列化器关联多表后提交数据
下一篇: 进程信号的捕捉