Linux——进程信号(一):信号的种类,信号的产生,信号的注册,信号的注销,信号的处理方式


1. 信号的概念

信号是一个软件中断;

接下来我们举个例子说明什么是软件中断:中断就是打断的意思,相当于我们过马路看见的红绿灯,当红灯亮时,会给我们传递一个暂停的信号,传递给我们的红灯信号就相当于一个软件中断,因为信号传递过来了,至于执行不执行还是看我们自己

2. 如何查看信号的信息以及查看信号的默认处理动作

可以通过man 7 signal查看

2.1 操作系统对信号的处理动作

通过man手册查看如下:

在这里插入图片描述

  • Term :终止进程
  • Ign :忽略信号
  • Core :终止进程并产生coredump文件
  • Stop :停止进程
  • Cont :继续运行
     
    如果某一个信号的处理动作是“Core”
    (1)默认是需要完成终止进程+产生coredump文件
    (2)产生coredump文件,依赖ulimit -a ==> "core file size"和磁盘大小,把core file size 设置成unlimited

2.2 信号具体的信息

信号名称+信号的值(整数)+action+描述

在这里插入图片描述

3. 信号的种类

目前Linux的信号数量为62个,分为如下两种类型:

可通过 kill -l 命令罗列具体的信号值

在这里插入图片描述

3.1 非实时信号(非可靠信号)

1~31号信号

特点:有可能信号会丢失

3.2 实时信号(可靠信号)

34~64号信号

特点:信号不会丢失

4. 信号的产生

4.1 硬件产生

  • ctrl+c :SIGINT(2)
  • ctrl+z :SIGTSTP(20)
  • ctrl+| :SIGQUIT(3)

4.2 软件产生

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 }

验证结果如下符合我们的预期

在这里插入图片描述

5. 信号的注册

5.1 从PCB的角度理解信号的注册

5.1.1 查看源码

查看源码,在/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“结构体如下图所示:
在这里插入图片描述

我们在上图可以看到在”struct sigpending“结构体中有一个”sigset_t“,我们cd ..到上级目录中,grep递归搜索"sigset_t;"查看”sigset_t“

在这里插入图片描述
在这里插入图片描述

如上过程转化为图解如下:

在这里插入图片描述

  • (1)在操作系统内核”struct task_struct“结构体内部有一个变量"struct sigpending pending;"
  • (2)内核定义的结构体"struct sigpending"当中有两个变量:一个是内核定义的双向链表;一个是:”sigset_t signal“
  • (3)内核定义的类型”sigset_t“为一个结构体,在结构体内部有一个变量,该变量为一个数组(无符号长整型的数组)

5.1.2 信号注册的原理

在这里插入图片描述

  • (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节点
    在这里插入图片描述

5.2 信号的注册

通过注册同一个信号两次,来区分可靠信号和非可靠信号的注册逻辑

5.2.1 非可靠信号的注册

如果同一个信号多次注册,那么对于非可靠信号而言,只会添加一次sigqueue节点,即只注册一次

第一次:
(1)更改信号对应的sig位图当中的比特位(0—>1,比特位从0改为1)
(2)在sigqueue队列当中添加sigqueue节点

第二次:
(1)更改信号对应的sig位图当中的比特位(1—>1,比特位从1改为1)
(2)对于第二次信号,不添加sigqueue节点到sigqueue队列当中

5.2.2 可靠信号的注册

如果同一个可靠信号多次注册,那么对于可靠信号而言,会添加多次sigqueue节点,即会注册多次

第一次:
(1)更改信号对应的sig位图当中的比特位(0—>1,比特位从0改为1)
(2)在sigqueue队列当中添加sigqueue节点

第二次:
(1)更改sig位图(1—>1,比特位从1改为1)
(2)在sigqueue队列当中添加sigqueue节点

6. 信号的注销

6.1 非可靠消息:

(1)将信号对应的sig位图当中的比特位置为0

(2)将对应的非可靠信号的sigqueue节点进行出队操作

6.2 可靠信号:

(1)先将可靠信号对应的sigqueue进行出队操作

(2)判断sigqueue队列当中是否有同类的可靠信号的sigqueue节点

  • 有:不会将sig位图当中对应的比特位置为0

  • 没有:将sig位图当中对应的比特位置为0

7. 信号的处理方式

7.1 默认处理方式

默认处理方式:在操作系统内核当中已经定义好了

定义为一个宏:SIG_DFL

7.2 忽略处理方式

忽略处理方式:操作系统定义进程收到某一个信号之后,忽略掉(进程即使收到了某个信号,进程也不会做任何事情)

定义为一个宏:SIG_IGN

此时我们可以对僵尸进程的产生进行一个新的理解:子进程先于父进程退出,子进程会向父进程发送SIGCHLD(17)信号,父进程对SIGCHLD(17)信号是忽略处理的,所以父进程并不会做任何事情,导致子进程的资源没有进程进行回收,从而导致子进程变成僵尸进程

7.3 自定义处理方式

(1)程序员可以定义某一个信号的处理方式
(2)函数==>sighandler_t signal(int signum, sighandler_t handler);

  • signum:待更改的信号的值
  • handler:函数指针,接收了一个函数的地址,这个函数没有返回值,有一个int类型的参数;typedef void (*sighandler_t)(int);
  • 自定义signum这个信号的处理方式,定义为handler这个函数指针保存的函数地址对应的函数,也就是说,当进程收到signum这个信号时,就会调用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函数功能相同

  • signum:待要自定义处理的信号
  • act:要将信号处理方式改为act
  • oldact:原来的处理方式
  • struct aigaction结构体
    在这里插入图片描述
    struct sigaction {
     
    //保存信号默认函数处理方式的函数指针
    void (*sa_handler)(int);
     
    //函数指针,但这个函数指针需要配合sa_flags一起使用,当sa_flags
    //当中的值为SA_SIGINFO的时候,信号处理是按照"sa_sigaction"当中
    //保存到函数地址来处理的
    void (*sa_sigaction)(int, siginfo_t *, void *);
     
    //当进程在处理某一个信号的时候,有可能还会收到其他的信号,此时,
    //其他的信号就暂时存在sa_mask当中
    sigset_t sa_mask;
     
    int sa_flags;
     
    void (*sa_restorer)(void);//保存字段
    };

验证代码如下:

  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 }

验证结果如下:
在这里插入图片描述

8. 从内核角度分析信号自定义处理方式

在/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 这个结构体

图解如下:

在这里插入图片描述

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值