初识信号
生活上的信号
你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“识别快递”(操作系统可以根据你发出的信号你选择不同的动作) 当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行,可以理解成“在合适的时候去取”。 在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取”当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:1. 执行默认动作(幸福的打开快递,使用商品)2. 执行自定义动作(快递是零食,你要送给你你的女朋友)3. 忽略快递(快递拿上来之后,扔掉床头,继续开一把游戏) --对应的是信号处理的三个分类。 快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话
技术层面上的信号
信号就是程序要执行某件事情之前发送的信号,通知进程要做什么事情,是一个软件中断,信号是进程之间事件异步通知的一种方式。 举个例子:就比如你在一个死循环中按下ctrl+c;,此时操作系统会接收到一个2号信号,发送给目标前台进程,终止该程序。 注:ctrl+c 只可以终止掉前台进程,不可以终止后台进程。 软中断是软件实现的中断,也就是程序运行时其他程序对它的中断;而硬中断是硬件实现的中断,是程序运行时设备对它的中断。
信号的分类
使用kill -l 命令查看信号 在linux中,共有62个信号。 分类: 非可靠信号(非实时信号): 1-31号信号,为不可靠信号。其如何传递多个相同的信号的话,有可能会造成信号的丢失。 深得来说:就是底层采用位图的方式来保存接收的信号,当收到多个相同信号时,操作系统不会将其进行额外处理,就是存储信号的位图中假如标识当前信号的比特位已经被置‘1’了,则再接收到该信号,则保持原来位图的样子即可。 可靠信号(实时信号) 34-64号信号被称为可靠信号,信号绝对不会丢失(有一个专门的结构体去保存该信号,底层通过链表形式将这些信号依次连接起来,遇见一个就加一个,哪怕是一样的)。
信号的常见处理方式
- 忽略此信号。
- 执行该信号的默认处理动作。
- 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。
了解信号的几大步骤
信号的产生
1.通过按键产生。 比如ctrl+c : 2号信号SIGINT ctrl+\ :3号信号 SIGQUIT 相比较2号信号来说,这个信号默认会产生core dump(核心转储) 文件 2.调用系统函数向进程发信号
#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
这两个函数都是成功返回0,错误返回-1
测试kill
1 #include<iostream>
2 #include<unistd.h>
3 #include<signal.h>
4 using namespace std;
5
6 void hander(int arg)
7 {
8 cout<<arg<<"号信号在此处被处理"<<endl;
9 }
10 int main()
11 {
12 signal(2,hander);
13
14 while(1)
15 {
16 kill(getpid(),2);
17 sleep(1);
18 }
19 return 0;
20 }
raise(int signo)
1 #include<iostream>
2 #include<unistd.h>
3 #include<signal.h>
4 using namespace std;
5
6 void hander(int arg)
7 {
8 cout<<arg<<"号信号在此处被处理"<<endl;
9 }
10 int main()
11 {
12 signal(2,hander);
13
14 while(1)
15 {
16
17 raise(2);
18 sleep(1);
19 }
20 return 0;
21 }
abort函数使当前进程接收到6号信号而异常终止 相比较之前的2号信号,6号必然要生效。就算你更改了函数的默认处理动作。
1 #include<iostream>
2 #include<unistd.h>
3 #include<signal.h>
4 #include<stdlib.h>
5 using namespace std;
6
7 void hander(int arg)
8 {
9 cout<<arg<<"号信号在此处被处理"<<endl;
10 }
11 int main()
12 {
13 signal(6,hander);
14
15 abort();
16 while(1)
17 {
18
19
20
21 sleep(1);
22 }
23 return 0;
24 }
注:9号信号是不可以被屏蔽掉的。
1 #include<iostream>
2 #include<unistd.h>
3 #include<signal.h>
4 #include<stdlib.h>
5 using namespace std;
6
7 void hander(int arg)
8 {
9 cout<<arg<<"号信号在此处被处理"<<endl;
10 }
11 int main()
12 {
13 signal(9,hander);
14
15 abort();
16 while(1)
17 {
18
19 raise(9);
20
21 sleep(1);
22 }
23 return 0;
24 }
3.由软件条件产生的信号。
比如在管道中,如果将读端关闭的话,此时写端如果要再想写的话就会收到SIGPIPE 13 号信号。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号14号信号, 该信号的默认处理动
作是终止当前进程
#include<iostream>
2 #include<unistd.h>
3 #include<signal.h>
4 #include<stdlib.h>
5 using namespace std;
6
7 void hander(int arg)
8 {
9 cout<<arg<<"号信号在此处被处理"<<endl;
10 }
11 int main()
12 {
13 alarm(10);
14
15
16 while(1)
17 {
18
19
20
21
22 sleep(1);
23 }
24 return 0;
25 }
~
4.硬件错误产生信号。 硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 **为SIGFPE信号(8号信号)发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号(11号信号)**发送给进程。
注:所有的信号,都必须经过操作系统的手发出。 原因:因为信号会影响进程,进程被操作系统所管理。
信号的保存
信号的处理
实际执行信号的处理动作称为信号递达(Delivery) 信号从产生到递达之间的状态,称为信号未决(Pending)。 进程可以选择阻塞 (Block )某个信号。 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作. 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作
修改/操作信号
//下面这些函数是设置的, 真正的写入在sigprocmask() 中
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
了解sigprocmask()函数 本质上:对信号屏蔽字(block位图)操作
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
how的取值:
//获取当前pending位图的值 sigpending(sigset_t set);
1 #include<stdio.h>
2 #include<signal.h>
3 #include<unistd.h>
4 void show(sigset_t pending)
5 {
6 int i=0;
7 for(i=0;i<=31;++i)
8 {
9 if(sigismember(&pending,i)==1)
10 {
11 printf("1");
12 }
13 else
14 {
15 printf("0");
16 }
17 }
18 printf("\n");
19 }
20 void hander(int arg)
21 {
22 printf("i get signal %d\n",arg);
23 }
24 int main()
25 {
26 signal(2,hander);
27 sigset_t newpending,oldpending;
28 sigset_t pending;
29
30 sigemptyset(&newpending);
31 sigemptyset(&oldpending);
32
33 sigaddset(&newpending,2);
34
35 sigprocmask(SIG_SETMASK,&newpending,&oldpending);
36 int count=0;
37 while(1)
38 {
39 sigemptyset(&pending);
40 sigpending(&pending);
41 show(pending);
42 sleep(1);
43 count++;
44
45 if(count==20)
46 {
47 printf("%s","将block位图中对应的2号信号未置0,取消屏蔽\n");
48 sigdelset(&newpending,2);
49 sigprocmask(SIG_SETMASK,&newpending,NULL);
50 }
51 }
52 }
信号的捕捉
函数1 :signal(int signo ,函数);
1 #include<stdio.h>
2 #include<signal.h>
3 #include<unistd.h>
4 void hander(int arg)
5 {
6 printf("i got a signal%d\n",arg);
7 }
8 int main()
9 {
10 signal(2,hander);
11 while(1)
12 {
13 sleep(1);
14 }
15 return 0;
16
17 }
函数2:
1 #include<stdio.h>
2 #include<signal.h>
3 #include<unistd.h>
4 void hander(int arg)
5 {
6 printf("i got a signal%d\n",arg);
7 }
8 int main()
9 {
10
11
12
13
14
15 struct sigaction t1,t2;
16 t1.sa_handler=hander;
17 t1.sa_flags=0;
18 sigemptyset(&t1.sa_mask);
19 sigaction(2,&t1,&t2);
20 while(1)
21 {
22 sleep(1);
23 }
24 return 0;
25
26 }
~
信号的注销
volatile关键字的作用
意义:禁止编译器对该语句做出优化。就比如定义一个全局变量,此时如果优化做的比较狠的话,则这个值会被存到寄存器中,此时你如果对该全局变量进行修改的话,只是对内存中的数据进行修改,寄存器的值是不变的。定义该关键字意义便是 告诉编译器,想获取我这个值必须要去内存中去取。 禁止将我这个值优化到寄存器中。
|