信号
1、产生信号
-
如何产生信号呢?
<1> 键盘。当某个进程正在运行时,按下Ctrl_C便可终止进程。
<2> 命令。如kill -9 3212 将id为3212的进程终止。
<3> 函数。如kill,alarm,abort函数。
<4> 操作系统捕捉。软件,硬件异常
2、如何处理信号 ?
<1> 忽略信号
<2> 执行该信号的默认动作,一般是将该进程终止
<3> 捕捉。自定义函数。
3、进程捕捉到信号时,并不是立即处理,而是在合适的时候处理。应先将所接受到的信号保存在自己的PCB中。等待合适的时机去处理(具体的处理时机,下面会有专门的讲解)。
2、信号阻塞
1、基本概念:
信号递达:实际执行信号的处理动作称为信号递达。忽略是递达的一种。
信号未决:信号从产生到递达之间的状态称为信号未决。
信号阻塞 进程可以选择阻塞某一信号。被阻塞的信号将保持在未决状态,直到进程解除对此信号的阻塞,才能执行递达的动作。
2、 信号在内核中的表示示意图(图片来源于网络):
![](https://img.haomeiwen.com/i2419009/77cd90b586eac607.png)
3、每个信号都有两个标志位分别为block(阻塞)和pending(未决) + 一个函数指针表示处理动作。
1、SIGUP:信号未产生也为阻塞,当它递达时执行默认处理动作
2、SIGINT:信号产生,但是阻塞。它的处理动作是忽略。阻塞不解除,都不会执行处理动作。
3、SIGQUIT:没有信号产生,一旦产生就会被阻塞,处理动作是用户自定义函数sighandler。
1)每个信号只有一个bit的 “未决标志” 和 “阻塞标志”,非0即1,不记录该信号产生的次数。因此,未决和阻塞标志可以用相同的数据类型sigset_t储存,sigset_t称为信号集。这个类型表示信号的有效和无效。
2)在阻塞信号集(信号屏蔽字)中即block表,1表示信号阻塞,0表示不阻塞。在未决信号集即pending表,1表示信号产生未决状态,0表示没有产生信号。总而言之,信号集为能够表示信号类型的0,1序列。
注:不可使用位操作
操作信号集,有专有的函数操作
4、信号集操作函数:
缩略图(信号集操作函数包含在signal.h中,需要导入该头文件):
![](https://img.haomeiwen.com/i2419009/016aa143959b1ea4.png)
1、sigemptyset: 初始化set所指向的信号集,使其中所有的信号对应的标志位置为0。
2、sigfillset: 初始化set所指向的信号集,使其中所有的信号对应的bit置1。
3、sigaddset: 在该信号集中 添加 某一信号。
4、sigdelset: 在该信号集中删除某一信号。
5、sigismember: 判断一个信号集的有效信号中 是否包含某种信号,若包含返回1,不包含返回0,出错返回-1。
6、sigprocmask函数: 读取或更改进程的信号屏蔽字(阻塞信号集)
返回值:成功为0,失败为-1.
sigprocmask函数原型.png
(1)若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。
(2)若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
(3)下图说明了how参数可以取用的值:
how参数可选值及作用.png
如果set是空指针,则不改变该进程的信号屏蔽字,how的值也无意义。
关于sigpromask()函数的详细使用,这有一篇讲的比较细的文章:
http://blog.csdn.net/big_bit/article/details/51338523
7、sigpending函数:读取当前进程的未决信号集
int sigpending(sigset_t *set);
返回值:成功为0,出错为-1
3、信号捕捉
信号捕捉过程<4次权限转换>:
![](https://img.haomeiwen.com/i2419009/3460b2f56005619d.png)
前面提到:当进程捕捉到信号后,并不是立即处理,而是在合适的时候进行处理,这个合适的时机就是:从内核态返回到用户态时。
1、内核处理完异常或者中断时,先检查当前进程中是否有可以被递达的信号。如果有则处理。(一般产生异常或者中断时都会随之产生相应的信号)
2、如果信号的处理方式为自定义的函数:则返回用户态(第二次权限变更),执行自定义的信号处理函数(注:不是回到主控制流程)。执行完处理函数后,通过调用sigreturn再次进入内核(第三次权限变更),最后从内核态返回主控制流程中上次被中断的地方执行。(第四次权限变更)。
为什么要有第四次权限变更?笔者认为,在异常或者中断产生的时候,直接进入了内核态,
那么再次回到中断处时,自然应该也是从内核态回去,而不是从用户态回去
。这样相当于把信号的处理操作
封装成黑盒操作。
3、如果信号的处理方式为默认:则终止进程。
4、如果信号的处理方式为忽略:从未决表(pending表)中删除该信号,即将1变为0,直接跳到用户态。
4、两个特殊的函数及区别:
1、signal函数:指定某一信号的处理函数。
函数原型:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(in signum,sighanler_t handler);
2、sigaction函数
函数原型:
intsigaction(int signo, conststruct sigaction*restrict act,
struct sigaction*restrict oact);
//结构体sigaction定义如下:
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flag;
void (*sa_sigaction)(int,siginfo_t*,void*);
};
作用:读取和修改与指定信号相关联的处理动作(处理函数)。
返回值:成功为0,失败为-1
实际使用:
在构造sigaction这个结构体时,需要注意一下几点:
1、sa_mask成员的赋值:必须用sigemptyset函数初始化act结构的sa_mask成员。不能保证:act.sa_mask = 0;会做同样的事情。
2、sa_flags成员值:设置不同的值系统会有不同的处理:
1、SA_ONSTACK:捕获在信号调用栈中的信号。
2、SA_RESTART:由此信号中断的系统调用会自动重启。
3、SA_NODEFER: 一般情况下, 当信号处理函数运行时,内核将阻塞<该给定信号 -- SIGINT>。但是如果设置了SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号。
4、SA_RESETHAND: 当调用信号处理函数时,将信号的处理函数重置为缺省值。(一般我们不需要在处理函数被调用后,立马设置为缺省值)
这两个标志对应早期的不可靠信号机制,除非明确要求使用早期的不可靠信号,否则不应该使用。这里也不多做介绍。
5、SA_NOCLDSTOP: 一般当进程终止或停止时都会产生SIGCHLD信号,但若对SIGCHLD信号设置了该标志,当**子进程停止**时不产生此信号。当子进程终止时才产生此信号。
6、SA_NOCLDWAIT: (字面理解是:子进程不等待,也就是直接退出 ,这样就不会存在僵尸进城) 若信号是 SIGCHLD时,当使用此标志时,
1 )当调用进程的子进程终止时不创建僵尸进程。
2 )若调用进程在后面调用wait。则调用进程阻塞,直到其所有子进程都终止
7、SA_SIGINFO:简单讲就是,可是使我们定义的处理函数中,多一个info参数,这个参数中包含了,信号的相关信息。
在开头我们看到 struct sigacton结构有一个 void (*sa_tramp)(void *, int, int, siginfo_t *, void *); 字段,该字段是一个 替代的信号处理函数。
当我们没有使用 SA_SIGINFO 标志时,调用的是 sa_handler指定的信号处理函数。
当指定了该标志后,该标志对信号处理函数提供了附加的信息,一个指向siginfo结构的消息和一个指向进程上下文标识符
的指针这时我们就能调用sa_sigaction指定的信号处理函数
8、SA_USERTRAMP:字面意思理解:不从内核态跳出。(信号捕捉过程中,有四次权限变更,该标志位的具体作用,暂时还不清楚....)
以上是对sa_flags设置不同值,会带来的影响到简单总结,这里涉及到内核编程,一定注意在不同平台上会有不同的影响,这里只是基于苹果的signal.h文件来编写。
笔者在调研过程中,发现有一篇不错的学习文档,可更参考:
http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28852942&id=3754478/
3、附上sigaction的使用:使用sigaction来模仿signal的实现:
signal(int signo, Sigfunc *func) {
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask); //这里需要注意
act.sa_flags = 0; //flags值的设置,参考上面的讲解
if(signo == SIGALRM) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
}
else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART;
#endif
}
if(sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
4、关于signal 和sigaction 二者区别:简单提一下,二者实际上都是用来指定信号的处理函数的,只不过前者是较早版本中的提供一种方式。此处可参考下文:http://blog.csdn.net/wangzuxi/article/details/44814825
所以这里笔者只重点对sigaction函数讲解。
5、笔者之所以会对这里做深入调研,是因为在做iOS平台的crash捕捉以及防护时,需要用到signal相关知识,因此做了系统学习并总结出来分享给大家,当然对于这块知识,如有理解不准确,还希望指正。