信号是一个软件中断;
接下来我们举个例子说明什么是软件中断:中断就是打断的意思,相当于我们过马路看见的红绿灯,当红灯亮时,会给我们传递一个暂停的信号,传递给我们的红灯信号就相当于一个软件中断,因为信号传递过来了,至于执行不执行还是看我们自己
可以通过man 7 signal查看
通过man手册查看如下:
- Term :终止进程
- Ign :忽略信号
- Core :终止进程并产生coredump文件
- Stop :停止进程
- Cont :继续运行
如果某一个信号的处理动作是“Core”
(1)默认是需要完成终止进程+产生coredump文件
(2)产生coredump文件,依赖ulimit -a ==> "core file size"和磁盘大小,把core file size 设置成unlimited
信号名称+信号的值(整数)+action+描述
目前Linux的信号数量为62个,分为如下两种类型:
可通过 kill -l 命令罗列具体的信号值
1~31号信号
特点:有可能信号会丢失
34~64号信号
特点:信号不会丢失
- ctrl+c :SIGINT(2)
- ctrl+z :SIGTSTP(20)
- ctrl+| :SIGQUIT(3)
kill命令:
- kill [pid] :可以终止一个进程
- kill -[num] [pid] :给进程号为pid的进程发送一个信号值为num的信号
eg:kill -9 [pid]
kill函数:
- int kill(pid_t,int sig);
功能:给pid进程发送sig信号
eg:kill(getpid(),9);
验证如下,我们分别打印begin和end并在两者中加上kill函数并给自己传递9号信号,此时我们应该看到的效果是打印完begin后进程接收到9号信号,然后被杀死,代码如下:
1 #include<stdio.h>
2 #include<signal.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 printf("----------begin----------\n");
8
9 kill(getpid(),9);
10
11 printf("----------end------------\n");
12 return 0;
13 }
让程序跑起来,我们会看到begin被打印出来,然后打印一个killed,证明刚才的进程接收到了9好信号,并执行了
- int raise(int sig);
功能 :谁调用给谁发送sig信号
验证如下,我们在刚才的kill函数前加上一个raise函数,并给它传入2好信号,此时我们应该看到的效果是打印完begin后进程接收到2好信号,然后进程终止,不会在执行后面的,所以我们看不到killed:
1 #include<stdio.h>
2 #include<signal.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 printf("----------begin----------\n");
8
9 raise(2);
10 kill(getpid(),9);
11
12 printf("----------end------------\n");
13 return 0;
14 }
验证结果如下符合我们的预期
查看源码,在/root/rpmbuild/BUILD/kernel-3.10.0-957.el7/linux-3.10.0-957.el7.x86_64/include/linux路径下,vim 打开sched.h,我们可以看到一个"struct sigpending pending;"结构体,如下图所示:
还在刚才哪个路径下,vim打开signal.h,我们可以找到一个”struct sigpending“结构体如下图所示:
- (1)在操作系统内核”struct task_struct“结构体内部有一个变量"struct sigpending pending;"
- (2)内核定义的结构体"struct sigpending"当中有两个变量:一个是内核定义的双向链表;一个是:”sigset_t signal“
- (3)内核定义的类型”sigset_t“为一个结构体,在结构体内部有一个变量,该变量为一个数组(无符号长整型的数组)
- (1)信号注册的本质就是在使用sig数组,但并不是按照数组类型的方式在使用,而是按照位图(比特位)的方式在使用
eg:某一个信号注册,则将某一个信号对应的比特位置为1- (2)sig数组的比特位个数远远大于62(64为操作系统下一个long是64个比特位,一个sig数组远远大于62(信号数量)),剩余的比特位为保留位
- (3)"struct sigpending"结构体当中有一个”sigset_t signal“当中有一个sig[xxx]数组,一般在信号注册的时候,称这个sig数组为操作sig位图
- (4)内核当中对于注册的时候,还有一个sigqueue队列,信号的注册逻辑为,将信号对应的sig位图当中的比特位置为1,并且在sigqueue队列当中添加一个sigqueue节点
通过注册同一个信号两次,来区分可靠信号和非可靠信号的注册逻辑
如果同一个信号多次注册,那么对于非可靠信号而言,只会添加一次sigqueue节点,即只注册一次
第一次:
(1)更改信号对应的sig位图当中的比特位(0—>1,比特位从0改为1)
(2)在sigqueue队列当中添加sigqueue节点
第二次:
(1)更改信号对应的sig位图当中的比特位(1—>1,比特位从1改为1)
(2)对于第二次信号,不添加sigqueue节点到sigqueue队列当中
如果同一个可靠信号多次注册,那么对于可靠信号而言,会添加多次sigqueue节点,即会注册多次
第一次:
(1)更改信号对应的sig位图当中的比特位(0—>1,比特位从0改为1)
(2)在sigqueue队列当中添加sigqueue节点
第二次:
(1)更改sig位图(1—>1,比特位从1改为1)
(2)在sigqueue队列当中添加sigqueue节点
(1)将信号对应的sig位图当中的比特位置为0
(2)将对应的非可靠信号的sigqueue节点进行出队操作
(1)先将可靠信号对应的sigqueue进行出队操作
(2)判断sigqueue队列当中是否有同类的可靠信号的sigqueue节点
有:不会将sig位图当中对应的比特位置为0
没有:将sig位图当中对应的比特位置为0
默认处理方式:在操作系统内核当中已经定义好了
定义为一个宏:SIG_DFL
忽略处理方式:操作系统定义进程收到某一个信号之后,忽略掉(进程即使收到了某个信号,进程也不会做任何事情)
定义为一个宏:SIG_IGN
此时我们可以对僵尸进程的产生进行一个新的理解:子进程先于父进程退出,子进程会向父进程发送SIGCHLD(17)信号,父进程对SIGCHLD(17)信号是忽略处理的,所以父进程并不会做任何事情,导致子进程的资源没有进程进行回收,从而导致子进程变成僵尸进程
(1)程序员可以定义某一个信号的处理方式
(2)函数==>sighandler_t signal(int signum, sighandler_t handler);
如上理解转换为图解如下:
我们将2好信号改为打印一句话,验证代码如下:
1 #include<stdio.h>
2 #include<signal.h>
3 #include<unistd.h>
4
5 void sigcallback(int signo)
6 {
7 printf("i am sigcallback,i am signo is %d\n",signo);
8 }
9
10 int main()
11 {
12 signal(2,sigcallback);
13
14 while(1)
15 {
16 printf("i am main func\n");
17 sleep(1);
18 }
19 return 0;
20 }
当程序运行起来,预期效果为当我们传入2号信号(ctrl+c)不会终止进程,而是会打印一句话,测试结果如下:
signal函数向内核注册了一个信号处理函数,调用signal函数的时候,并没有调用注册函数(注册函数在进程收到信号之后才调用),将这种方式称之为“回调”,内核在调用
(3)int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);此函数和signal函数功能相同
验证代码如下:
1 #include<stdio.h>
2 #include<signal.h>
3 #include<unistd.h>
4
5 void sigcallback(int signo)
6 {
7 printf("i am sigcallback,i am signo is %d\n",signo);
8 }
9
10 int main()
11 {
12 struct sigaction sa;
13 sa.sa_handler = sigcallback;
14 //int sigemptyset(sigset_t *set);
15 //初始化
16 sigemptyset(&sa.sa_mask);
17 sa.sa_flags = 0;
18 sigaction(2,&sa,NULL);
19
20 while(1)
21 {
22 printf("i am main func\n");
23 sleep(1);
24 }
25 return 0;
26 }
在/root/rpmbuild/BUILD/kernel-3.10.0-957.el7/linux-3.10.0-957.el7.x86_64/include/linux路径下,vim打开sched.h,我们可以看到一个“struct sighand_struct *sighand;”,如下图所示:
返回上级目录,grep查找"struct sighand_struct",并打开查看"struct sighand_struct",如下图所示,我们可以看到这个结构体中有一个action数组,数组的每一个元素都是一个”struct k_sigaction“
cd…返回上级目录后,使用grep查找“struct k_sigaction {”,并打开,我们会在这个结构体中看到一个“struct sigaction sa;”,如下图所示:
继续查看“struct sigaction“这个结构体,此时我们可以看到此结构体和sigaction函数的参数 struct aigaction 结构体相似,此结构体中有个”__sighandler_t sa_handler;“
而上面的”__sighandler_t“实际是”typedef void (*sighandler_t)(int)“ 是typedef出来的
- signal函数的内部也是在调用sigaction函数
- signal函数修改的是__sighandler_t保存的地址
- sigaction函数修改的是struct sigaction 这个结构体
图解如下: