栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

深度解析linux下信号的注册和注销原理详解及配合信号更好解决僵尸进程

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

深度解析linux下信号的注册和注销原理详解及配合信号更好解决僵尸进程

目录

1.信号的概念:

2.信号的产生

2.1硬件产生(按键盘中的按键):

2.2软件产生:

        1.kill函数

        2.raise函数:

        3.kill- [num] [pid] 可以给进程发送信号

        4.当常见的错误导致程序崩溃时进程收到信号

                1.解引用空指针

                2.除0:

                3.double free:多次释放同一个地址

3.信号的种类

kill -l 可以罗列信号

4.信号的处理方式

5.信号的注册

5.1基础概念了解:

5.2信号的注册:

6.信号的注销:

6.1非可靠信号的注销

6.2可靠信号的注销

7.信号的自定义处理方式

7.1 概念:

7.2 函数

7.3原理:

8.信号的捕捉流程

8.1信号的处理时机:

8.2处理信号的时候:不同的处理方式

8.3常见的进入到内核的方式:

9.信号的阻塞

9.1信号阻塞的特性:

9.2内核代码:

9.3加上信号阻塞之后,理解信号的处理;

9.4接口:

10.配合信号解决僵尸进程:

11.volatile关键字:


1.信号的概念:

信号是一个软中断

软中断:例如:看到绿灯你可以选择走或选择不走。红绿灯只是一个提醒你可以选择走或不走1.1只是告诉有这样一个信号,但是具体这个信号怎么处理,什么时候处理由进程决定,所以是软中断。

2.信号的产生

2.1硬件产生(按键盘中的按键):

ctrl+c:2号信号 SIGINT,按下ctrl+c其实是进程收到了2号信号,2号信号导致进程的退出。ctrl+z :20号信号SIGTSTP,

ctrl+|:3号信号SIGQUIT

2.2软件产生:

1.kill函数

参数:

pid_t:要给那个进程发,就填那个进程的pidsig:要给进程发送的信号使用:我们用getpid获取当前进程pid然后给当前进程发送一个3号信号

我们发现一运行直接终止了

2.raise函数:

作用:给当前进程发送一个信号参数:给自己发送信号的信号值使用:给自己发送一个3号信号,退出信号。

可以发现一运行直接退出了。

底层实现原理:

调用kill函数实现将kill函数封装。用getpid获取当前进程的进程号传给kill函数,然后自己只需要获取用户传入的信号值,然后传递给kill函数即可。

3.kill- [num] [pid] 可以给进程发送信号

可以看到进程暂停了

4.当常见的错误导致程序崩溃时进程收到信号

1.解引用空指针

2.除0:

运行一下我们可以看到、

3.double free:多次释放同一个地址

我们gdb调试coredump文件可以发现返回6号信号

3.信号的种类

kill -l 可以罗列信号

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

特点:信号可能会丢失(1-31)实时信号(可靠信号):

特点:信号不会丢失(33-64)

4.信号的处理方式

操作系统对信号的处理方式(man 7 signal)

默认处理方式:

SIG_ DFL,操作系统当中已经定义号信号的处理方式了例如2号信号->终止进程11->终止进程,并且产生核心转储文件忽略处理方式:

SIG_ IGN, 该信号为忽略处理(僵尸进程)进程收到忽略处理的方式的信号后,是不进行处理的,例如当子进程先于父进程退出时,就会给父进程发送SIGCHLD信号,而父进程收到这个信号之后,就会忽略处理,导致父进程没有回收子进程的退出状态信息,从而子进程变成了僵尸进程。自定义处理方式:

程序员可以更改信号的处理方式,定义一 个函数,当进程收到该信号的时候, 调用程序猿自己写的函数。(第7个小点涉及到)

5.信号的注册

5.1基础概念了解:

一个进程收到一个信号,这个过程称之为注册信号的注册和注销并不是一个过程,是两个独立的过程

内核中信号注册位图以及s igqueue队列的的了解

task_ struct结构体内部struct S igpending pending;siget_ t

5.2信号的注册:

位图更改为1,添加Sigqueue节点到sigqueue队列:信号在注册的时候,会将信号对应的比特位从0修改为1,表示当前进程收到了该信号。还需要哎sigqueue队列中添加一个sigqueue节点,队列在操作系统内核当中本质上是一个双向链表(先进先出的特性)

实时信号和非实时信号在注册时的区别: 非实时信号(非可靠信号)的注册

第一次注册:修改sig位图(0-1),修改sigqueue队列。第二次注册:相同信号值的信号:修改sig位图(1->1),并不会添加sigqueue节点。总结:再次添加,不会添加sigqueue节点实时信号(可靠信号)的注册

第一次注册:修改sig位图(0-1),修改sigqueue队列。第二次注册:相同信号值的信号:修改sig位图(1->1),添加sigqueue节点到sigqueue队列中。再次添加,会再次添加siquque节点

6.信号的注销:

6.1非可靠信号的注销

1.将信号对应的s ig位图当中的比特位置为0(1-0)2.将对应的信号的sigqueue节点进行出队操作

6.2可靠信号的注销

1.将对应的信号的sigqueue节点进行出队操作2.判断s igqueue队列当中还有相同信号的S igqueue节点吗

如果有:则比特位不变如果没有:则比特位改变位0

7.信号的自定义处理方式

7.1 概念: 自定义处理方式, 就是让程序猿自己定义某一个信号的处理方式,例如原来我们的2号信号是一个终止命令我们可以通过自己定义来让他做其他的事,例如打印一句话。

7.2 函数

1.sighandler_t signal(int signum, sighandler_t handler);

作用:在调用signal函数的时候,我们给函数的第二个参数传递一个回调函数的地址,当我们收到第一个参数所定义的信号值时,就会调用回调函数,执行回调函数的功能。参数:signum:信号值handler:更改为哪一 个函数处理,接受一 个函数地址,函数指针(回调函数)。typedef void (*sighandler_ t)(int);代码验证:我们写一个代码测试当进程收到2号信号和9号信号的时候,是否会调用回调函数

我们运行代码按下ctrl+c向进程发送2号信号,可以发现执行回调函数将收到的2号信号打印出来了。

我们向信号传递9号信号

可以发现函数并不会执行回调函数,而是直接将进程强杀了,所以可以得知9号信号是无法被用户更改处理方式的。

2.int sigemptyset(sigset_t *set);(将信号位图初始化为全0)

3.int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

参数:signum:要更改的信号值act:要将信号的处理方式更改为actoldact:原来信号的处理方式,要依赖于函数填充。struct sigaction使用方式:void (*sa_ handler)(int); //保存信号处理方式(默认)的函数指针void (*sa_ sigaction)(int, siginfo _t *,void *);// (也是保存原来信号的处理方式的函数指针),但是没有使用。当要使用的时候,配合sa_ flags- 起使用。当sa_ falgs的值为SA_ SIGINF0的时候,信号按照sa_ sigaction保存的函数地址进行处理sigset_ t a_ mask;//当进程在处理信号的时候,如果还在收到信号, 则放到该信号位图当中,后续再放到进程的信号位图当中使用时需要用int sigemptyset(sigset_t *set);函数将位图每一位置为0int sa_ f lags;void (*sa_ restorer)(void); //保留字段代码测试:

我们运行代码:

7.3原理:

内核当中的结构体

8.信号的捕捉流程

当我们的进程从内核态切换回用户态时,会调用do_signal,检查进程是否收到了信号。

8.1信号的处理时机: 当从内核态切换会用户态的时候,会调用do_ S igna l函数处理信号

有,就处理信号(信号的处理方式(默认,“ 忽略, , 自定义) )没有,就直接返回会用户态

8.2处理信号的时候:不同的处理方式 默认:忽略:直接在内核就处理结束。自定义处理:调用程序猿自己定义的处理函数进行处理执行用户自定义的处理函数(用户空间)调用sigreturn( )再次回到操作系统内核(内核空间)再次调用会调用do_ signa L函数处理信号调用sys_ sigreturn函数回到用户空间, 继续执行代码

8.3常见的进入到内核的方式:

调用系统调用函数,内存访问越界,”访问空指针调用库函数

9.信号的阻塞

9.1信号阻塞的特性: 信号的注册是信号注册, 信号阻塞是信号阻塞。信号的阻塞并不会干扰信号的注册,而是说进程收到这个信号之后,由于阻塞, 暂时不处理该信号 。

9.2内核代码:

struct task_ struct{sigset_ t blocked; (位图)它和前面的sig位图一样都是当信号阻塞时将相关的bit位置为1。

9.3加上信号阻塞之后,理解信号的处理;

进入内核,返回之前, 会调用do_ signa 1函数处理信号有信号要处理,则先判断该信号是否阻塞,如果没阻塞, 在处理信号。 如果阻塞,则不处理 。当不阻塞再处理。

9.4接口: int sigprocmask(int how, const sigset_ t *Set, sigset_ t *oldset);(无法阻塞9号和19号信号)参数:

how:想让s igp rocmask做什么事情SIG_ BLOCK: 设置某个信号为阻塞状态SIG_ UNBL.OCK :设置 某个信号为非阻寒状态SIG_ SETMASK :用第二个参数“set”,替换原来的阻寨位图。 ( 替换的意思)set :新设置的阻塞位图根据传递进的函数变量计算新的阻塞位图:

1.阻塞单个信号,阻塞多个信号(只要将相应的信号位图设置为1即可)2.接触阻塞单个信号/解除阻塞多个信号。oldset :原来老的阻塞位图原理解析:

当how为SIG_ _BLOCK时,函 数会根据set,计算新的阻塞位图, 方式为:block(new) = block(old) | set;新的block位图和旧的block位图按位或运算。当how为为SIG_ _UNBLOCK时,函数会根据set,计算新的阻塞位图, 方式为:block(new) = block(old) & (^ 'set);将传入新的位图先取反,然后和老的位图进行按位与操作。当how为为SIG_ SETMASK时,函 数会根据set,计算新的阻塞位图,方式为:block (new) = set;测试代码:

运行结果我们发现给当前进程无论发送什么信号都没有作用。

我们给进程发送2号信号发现没有反应

发送9号信号

我们接下来验证实时信号和非实时信号这里

我们运行之后看结果

10.配合信号解决僵尸进程:

在我们之前解决僵尸进程只能调用wait函数或者waitpid函数,但是在调用这两个函数都面临一个问题那就是在调用时,父进程要不一直处于阻塞等待子进程退出状态,要不一直要配合循环和waitpid的非阻塞状态使用,我们的父进程就无法做任何的事情。这里我们可以运用信号,对子进程进行回收。

我们看运行结果

11.volatile关键字:

作用:保证内存可见性每次CPU要计算的数据都是从内存中获取,拒绝编译时优化的方案(从寄存器当中获取)gcc/gt+的编译选项“-00, 01, -02,-03,优化级别时越来越高。(理解优化级别越高,程序可能执行的越快)优化级别越高就从寄存器中取值的可能性越大我们写一个代码,验证一下

我们运行发现按下ctrl+c向进程输入2号信号,进程直接结束了,这是因为我们在编译时没有对程序进行优化

我们优化为O1级别

运行发现仍然可以退出也就是从内存中读取数据

但是当我们优化到一定级别CPU在计算时就为了提高运行效率不从内存中取数据了,就直接从寄存器中取数据,导致我们更改的g_val的数值在程序运行时显示不改变。这样就会导致一定错误产生。我们在变量前边加上volatile关键字就会保证内存可见性,CPU在处理数据时每次从内存中读取。

看到这里如果觉得有用不如点个赞再走吧!!!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/778979.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号