文章目录:
1. 信号的捕捉流程
1.1 前言
信号的注册,是不是和操作系统维护的进程的PCB有关系?
因为struct task_struc中有一个struct sigpending中有一个sigset_t signal在sigset_t结构体中有一个sig数组(sig位图),信号注册时操作
答:信号的注册,注册在进程的PCB当中,是在和操作系统维护的的进程PCB打交道
1.2 捕捉流程:信号什么时候进行处理?(信号的处理在内核态完成)
进入到操作系统内核才去处理信号
- (1)从内核态切换回用户态时,一定会调用do_signal函数,来处理进程收到的信号
1.1 sig位图当中有信号注册,则执行信号注册的逻辑
1.2 sig位图当中没有信号注册,则直接返回用户态- (2)处理信号(信号注销)–>针对上面1.1
2.1 默认处理方式(直接在操作系统内核的代码当中完成)
2.2 忽略处理方式(直接在操作系统内核的代码当中完成)
2.3 自定义处理方式(调用程序员自己定义的函数)- (3)自定义处理方式–>针对上面 2.3
3.1 在用户态执行程序员自己定义的函数
3.2 调用sigreturn函数再次回到操作系统内核
3.3 再次调用do_signal函数判断是否有信号注册(若有执行(2),若没有执行(3.4))
3.4 调用sys_sigreturn函数回到用户态继续执行代码
如上内容图解如下:
1.3 什么时候进入操作系统内核
- (1)调用系统调用函数的时候
- (2) 调用C库函数的时候,因为C库函数的内部也是调用系统调用函数
- (3) 内存访问越界,访问空指针
2. 信号的阻塞
2.1 信号的阻塞,不会影响信号的注册
2.2 从内核源码角度分析信号阻塞
查看源码,在 /root/rpmbuild/BUILD/kernel-3.10.0-957.el7/linux-3.10.0-957.el7.x86_64/include/linux 路径下,我们可看到一个 “sigset_t blocked”,block实则是一个位图,在 task_strcut 结构体中,如下图所示
那么信号的阻塞过程究竟是什么样呢?如下图所示:
如上图所示,假设在sig位图中注册了2号信号,则其在sig位图中对应的位置的比特位就被置为1
block位图
- 作用: 阻塞某一个信号的处理,也就是,执行流进入到内核之后,调用了do_signal函数判断是否有信号需要被处理的时候,发现某一个信号注册了,同时也需要判断block位图当中该信号对应的比特位是否为1
如果为1:则暂时不处理该信号(暂时不信号注销)
如果为0:则进行处理
2.3 信号阻塞的接口
- int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- how:想让sigprocmask函数做什么
(1)SIG_BLOCK:设置某个信号为阻塞状态
(2)SIG_UNBLOCK:设置某个信号为非阻塞状态
(3)SIG_SETMASK:设置新位图- set:使用set去设置block位图
(1)SIG_BLOCK:设置某个信号为阻塞状态
block(new) = block(old) | set
eg:block(old):0101 0000
set :0000 1000 -->0101 1000
(2)SIG_UNBLOCK:设置某个信号为非阻塞状态
block(new) = block(old) & (~set)
eg:block(old):0101 0000
(~set) :1011 1111 -->0001 0000
(3)SIG_SETMASK:设置新位图
block(new) = set- 注意:信号的阻塞不会将9好信号和19号信号阻塞的
我们让所有的信号都阻塞掉,此时ctel+c等信号将不起作用,验证代码如下:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<signal.h>
4
5 int main()
6 {
7 sigset_t set;
8 //把传进来的set的所以比特位都设为1
9 sigfillset(&set);
10 sigprocmask(SIG_SETMASK,&set,NULL);
11 while(1)
12 {
13 sleep(1);
14 }
15 return 0;
16 }
此时这个进程没办法退出,但9号信号和19号信号不会阻塞,可以使用kill -9 [pid]或kill -19 [pid]退出进程
2.4 验证可靠信号的注册和非可靠信号的注册
我们首先保存之前的位图然后将所有信号进行屏蔽,在获得一个字符串后,将位图设置为之前的位图oldset,此时我们相当于没有屏蔽任何信号,代码如下:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<signal.h>
4
5 int main()
6 {
7 sigset_t set;
8 //把传进来的set的所以比特位都设为1
9 sigfillset(&set);
10 sigset_t oldset;
11 sigprocmask(SIG_SETMASK,&set,&oldset);
12
13 getchar();
14
15 sigprocmask(SIG_SETMASK,&oldset,NULL);
16
17
18
19 while(1)
20 {
21 sleep(1);
22 }
23 return 0;
24 }
让程序跑起来,此时程序阻塞在getchar()位置
此时我们向进程发送5次2号信号(非可靠信号)和5次40号信号(可靠信号)
然后随便输入一个字符后,看到程序直接结束了,我们并没有看到任何现象,此时只能传递的某一个信号结束了进程
为了方便我们看到现象,我们将信号的处理方式改为自定义处理方式,改进后代码如下:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<signal.h>
4
5 void sigcallback(int signo)
6 {
7 printf("signal:%d\n",signo);
8 }
9
10 int main()
11 {
12 signal(2,sigcallback);
13 signal(40,sigcallback);
14 sigset_t set;
15 //把传进来的set的所以比特位都设为1
16 sigfillset(&set);
17 sigset_t oldset;
18 sigprocmask(SIG_SETMASK,&set,&oldset);
19
20 getchar();
21
22 sigprocmask(SIG_SETMASK,&oldset,NULL);
23
24
25
26 while(1)
27 {
28 sleep(1);
29 }
30 return 0;
31 }
让程序跑起来,此时程序阻塞在getchar()位置
此时我们向进程发送5次2号信号(非可靠信号)和5次40号信号(可靠信号)
然后随便输入一个字符后,此时我们可以看到程序并没有结束,40号信号(可靠信号)处理了5次,2号信号(非可靠信号)处理了1次,此时可以证明同一个可靠信号在多次注册时,会会添加多次sigqueue节点,会注册多次,而同一个非可靠信号在多次注册时,只会添加一次sigqueue节点,注册一次
3. volatile关键字
- 作用:使变量保证内存的可见性(加上这个关键字,只会从内存中读取数据)
- 解释:内存–>缓存–>寄存器–>CPU
gcc/g++都有编译代码的优化选项,CPU在计算数据的时候,为了快,对已经从内存当中拿回来的数据,不会再从内存中获取,而是直接从寄存器当中获取,而加上volatile关键字只会从内存当中获取数据