经过了漫长的间歇,对于c语言的学习也被中断了很久,现实确实有很多的无耐,计划中的事情总会被打乱,但不管怎样,学习的道路是不能休止的,所以经过了一断温习后现在继续学习C语言,话不多说,进入正题:
信号分类
![](https://i-blog.csdnimg.cn/blog_migrate/c1938f2bac6965c1560ba77b34a0ec0e.png)
可靠信号与不可靠信号
不可靠信号:
linux信号机制基本上是从unix系统中继承过来的。早期unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,它的主要问题是:
![](https://i-blog.csdnimg.cn/blog_migrate/e683b98dca2a58ddb521bdb30d89cfba.png)
我们用上节课的例子来解释上面的这断话:
![](https://i-blog.csdnimg.cn/blog_migrate/f831cf1a8cd115106787f972abeaa2bb.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6c2cb9c4368abed9c92e75265c3edcaa.png)
运行来看下:
![](https://i-blog.csdnimg.cn/blog_migrate/68e1a2b2fbd70cc1b5d5955683228077.png)
而第二次再按ctrl+c,还是会输出我们的处理程序:
![](https://i-blog.csdnimg.cn/blog_migrate/adfd77da05a555eb7aa686528d90aa38.png)
所以与之对比,上面文字中提到的
不可靠信号的默认动作就能理解了,就是早期的unix当我们注册完了信号,并处理信号时,则会恢复到默认动作,表现形式也就是这样(模拟):
![](https://i-blog.csdnimg.cn/blog_migrate/957c95181a5a6139f6bf87f0ae2cc4b7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ad004746c09e5c61e936cefd2e829f6b.png)
"用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。",也就是这样做:
![](https://i-blog.csdnimg.cn/blog_migrate/09445f09279bacc936bc053b160636ef.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a7f7ac3856bb250c12c805530875d110.png)
从上面这段程序可以看出,当再次注册信号时,如果新的SIGINT信号过来了,但是信号还没注册完,那么还是会响应ctrl+c的默认动作,也就是程序退出了,这就说明是
做出了一个错误的反应,另外,关于
信号可能会丢失说的是哪方面呢?是指当来了多个SIGINT信号时,
不可靠信号是不会排队的,只会保留一个,其它的都被丢弃掉。
![](https://i-blog.csdnimg.cn/blog_migrate/654e7f94635bf9ed5a71ef2000e7d955.png)
也就是说,如今的linux的不可靠信号在处理完之后,是不会被恢复成默认动作的,而不可靠信号同样还有这样的特征:"不可靠信号是不会排队的,只会保留一个,其它的都被丢弃掉",也就是存在信号的丢失。
总结一下:早期unix的不可靠信号在执行完之后会被恢复成默认动作,也就是会做出错误的反应,并且信号不会排队,存在信号丢失问题;而如今的linux的不可靠信号在执行完之后是不会被恢复的,也就是不会做出错误的反应,但是还是存在信号丢失的问题,所以基于这个原因,就出现了下面要介绍的可靠信号了。
可靠信号:
![](https://i-blog.csdnimg.cn/blog_migrate/92df767db81f966879d85db38be2fa7d.png)
那新增的可靠信号是哪些呢?
![](https://i-blog.csdnimg.cn/blog_migrate/2cda7996310f9b13444f3b39546f878d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a1eccbfa4b410b911440f124e3df6856.png)
在之前介绍不可靠信号时,其中说到“linux信号安装函数(signal)是在可靠机制上的实现”,也就是说
signal和sigaction是一样的,都是可靠信号的安装,实际上它们都调用了更加底层的
dosigaction内核函数,只能内核才能调用到,做一个了解。
实时信号:
![](https://i-blog.csdnimg.cn/blog_migrate/c3a4867d3d6c8cebfed3a1d584fd99c8.png)
其中后32个信号是没有具体含义的,可供应用程序进行使用, 另外SIGRTMIN不一定都是从34开始,先查看一下目前它是从哪开始的:
![](https://i-blog.csdnimg.cn/blog_migrate/88035f7cd2bf3f301bbdae6f05b65fb6.png)
可以从signal的帮助文档中可以阅读到:
![](https://i-blog.csdnimg.cn/blog_migrate/4108042924ea10355dae185bd5b8a686.png)
这个做为了一个了解。
信号发送
![](https://i-blog.csdnimg.cn/blog_migrate/6d9ab122b574cd1fefd70fe529f7c359.png)
关于kill函数,我们经常会用到:
![](https://i-blog.csdnimg.cn/blog_migrate/2d3a0310a029c13db37f37eda8b29cff.png)
实际上更准确的说法是向pid进程发送9号信号,由于9号信号不能被忽略,也不能被捕获的,而它默认动作就是将进程给杀掉,所以我们经常用这个命令来杀死进程。
实际上kill命令的实现是靠kill系统函数,可以man查看一下:
![](https://i-blog.csdnimg.cn/blog_migrate/f84db96b7b54d07b063ded182767cfb7.png)
下面对该函数的描述进行认识,之后会用到:
![](https://i-blog.csdnimg.cn/blog_migrate/6d434d23d42cf65c7b63cbd490627764.png)
![](https://i-blog.csdnimg.cn/blog_migrate/91f4e64da34eb0ae455219c9ab421ace.png)
![](https://i-blog.csdnimg.cn/blog_migrate/09dfc09a7175a7a82eac3c1bf09a3a8a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/78318a385a80996cfe70cb6d28c7f8ff.png)
当pid=-1时,信号sig将发送给调用者进程有权限发送的每一个进程,除了1号进程和自身之外。
![](https://i-blog.csdnimg.cn/blog_migrate/c27a01acafa352738e3b528f411a9bfe.png)
好了,上面理论较多,先简单看下,下面开始用代码来进行说明,举一个子进行程父进程发送信号的例子:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void handler(int sig);
int main(int argc, char *argv[])
{
if (signal(SIGUSR1, handler) == SIG_ERR)//注册了一个可靠信号
ERR_EXIT("signal error");
pid_t pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid == 0)
{
kill(getppid(), SIGUSR1);//向父进程发送一个信号
exit(EXIT_SUCCESS);
}
sleep(5);//父进程睡眠五秒
return 0;
}
void handler(int sig)
{
printf("recv a sig=%d\n", sig);
}
编译运行:
![](https://i-blog.csdnimg.cn/blog_migrate/d8c9d6871ba060aaf9f6d21154c7c293.png)
原因是由于sleep函数会被信号所打断而返回,在打断返回之前会先处理信号,所以就没有出现了我们的预期,那如果要实现真正睡眠5秒怎么做呢?可以查看sleep函数的man帮助:
![](https://i-blog.csdnimg.cn/blog_migrate/10b570bcc602cf8cdd876416667f2dad.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f979c9c6fbcf36bd0f09b927e971c807.png)
这时再看效果:
![](https://i-blog.csdnimg.cn/blog_migrate/c61f3742c049ff9cd34e449b5426457f.gif)
可见通过这种取巧的办法就解决了我们所遇到的问题。
下面来看一下这个情况,也就是给进程组发送信号:
![](https://i-blog.csdnimg.cn/blog_migrate/26768cc9fec348e3b842235d9b065e74.png)
首先查看一下怎么得到进程组ID:
![](https://i-blog.csdnimg.cn/blog_migrate/5b3e1c978e3858dc530a17da17b7a704.png)
下面看下具体代码:
![](https://i-blog.csdnimg.cn/blog_migrate/421413cd5c6dc3c88d1ec02c455b34e8.png)
运行效果:
![](https://i-blog.csdnimg.cn/blog_migrate/39cedce38a4ef4928122cdf28b393a67.gif)
解释一下程序,之所以打印出两条语句,是由于注册信号是在fork()之前,所以子进程会继承父进程的信号所安装的程序,也就是子进程中也安装了这个信号,而子进程向进程组发送了一个信号,则每个进程都会收到信号,当子进程收到时,会打印一条语句,然后立马退出了,而父进程同样也会收到,但是它会sleep五秒后才退出,所以才出现了如上效果。
另外向父进程发送信号还有另外一种等价的使用方法:
![](https://i-blog.csdnimg.cn/blog_migrate/9b60f78942d83f73cb0d3cce0d3bb21a.png)
代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/17ba47468e019ee76de720ea38cdf373.png)
具体效果这就不演示了,一样。
![](https://i-blog.csdnimg.cn/blog_migrate/67dbd591121fad640e2834c65392a36c.png)
查看一下帮助:
![](https://i-blog.csdnimg.cn/blog_migrate/f2ccc953f64fa76d9ce9061502dbeef1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6fe86d98dd913410304ac8b18285901a.png)
这个函数会在之后学到,先认识一下。
pause
![](https://i-blog.csdnimg.cn/blog_migrate/ea74b8bb85b5f1d4033c2ea22634fbf1.png)
直接用代码来进行说明,这样就会很容易理解了:
先回顾一下之前的一个代码,然后利用pause进程改装:
![](https://i-blog.csdnimg.cn/blog_migrate/b9f3e86c90a4252e02961a0c607a0c6c.png)
运行效果:
![](https://i-blog.csdnimg.cn/blog_migrate/c2549cb8237a396751f20589447dfe91.gif)
而比较好的方式是采用我们要学的这个pause函数来让进程挂起,直到一个信号被捕获了,代码调整如下:
![](https://i-blog.csdnimg.cn/blog_migrate/1a9729d32eae5ef2a169e32f1310650f.png)
看下效果:
![](https://i-blog.csdnimg.cn/blog_migrate/21142768e831c590a1893026f7ee4417.gif)
可以很清楚的看到,当收到信号时,则pause函数就被返回了,这样的做法就会比较好,在信号没发送之前让进程挂起,信号处理完,则就返回了。
好了,今天的内容学到这,下节再见!!