| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 系统运维 -> 【Linux】进程信号(学习复习兼顾) -> 正文阅读 |
|
[系统运维]【Linux】进程信号(学习复习兼顾) |
目录 0. 前言????????此博客为博主以后复习的资料,所以大家放心学习,总结的很全面,每段代码都给大家发了出来,大家如果有疑问可以尝试去调试。 ????????大家一定要认真看图,图里的文字都是精华,好多的细节都在图中展示、写出来了,所以大家一定要仔细哦~ ? ? ? ? 感谢大家对我的支持,感谢大家的喜欢, 兔7 祝大家在学习的路上一路顺利,生活的路上顺心顺意~! 1.?信号入门1.1?生活角度的信号
接下来先说一下理解信号的前提知识:
? ? ? ? 这个是通过自然世界中人对信号的基本理解。 ? ? ? ? 接下来就是对进程进行分析了。
? ? ? ? 那么此时虽然我们还不是很理解信号,但是我们通过对信号产生的生命周期的解释,也对信号有了一个大体的认识。 ? ? ? ? 首先带大家看一下信号:
? ? ? ? 我们通过这种方式可以找到,而且看到信号都是宏。 ? ? ? ? 在正式讲解前先回答一个问题:信号是如何发送的以及如何记录的?
1.2 技术应用角度的信号
? ? ? ? 接下里就通过例子,让大家更容易的去理解:
? ? ? ? 当我们用 ctrl+c 这个组合键结束这个进程的本质是,操作系统识别到 ctrl+c 这个组合键,操作系统将?ctrl+c 解释成了 2号 信号,也就是 SIGINT 。 下面来证明一下: ? ? ? ? 我们知道处理信号有三种方案
? ? ? ? 我们刚来处理? ctrl+c ,我们的默认动作叫做 终止进程 。那么我们为了能够让信号自定义,那么就先来看一个接口:
? ? ? ? 我们可以看到,第一个参数就是信号编号,也就是信号中的 1-31 。 ????????第二个参数的类型可以看到是一个函数指针,而且是一个回调函数,就是相当于我们可以通过 signal ,提前向进程注册一个对信号的处理方法。 ? ? ? ? 所以我们要想自定义信号就必须要设置一个返回这为 void ,一个参数为 int 的函数,然后将函数名为参数给 signal 当实参。
? ? ? ? 我们可以看到,当我们 ctrl+c 的时候,其实是操作系统识别到向目标进程发送 2号信号,本质就是修改 2号 信号内部的位图,将第二个位图由 0 制 1了。所以进程是在运行中的一瞬间就处理了这个信号,然后就打印出了那句话。 ? ? ? ? 默认 ctrl+c 不就是终止进程么,这次不终止了是因为我们将默认行为改成了自定义行为。?
? ? ? ? ?我们可以看到,发送二号信号都被进程捕捉了。(SIGINT就是 2号 信号,这里是为了证明输入宏也可以) ? ? ? ? 那么我们输入其他信号呢?
? ? ? ? 我们可以看到程序退出了,这是因为我们只捕捉了 2号 信号,没有捕捉 3号 信号。 1.3?注意
Ctrl+C 产生的信号只能发给前台进程:
? ? ? ? 我们可以看到,如果后面加上 & 后,进程就在后台进行了,当我们 CTRL+C 的时候程序本应该停止或者发送我们刚才的字符串,但是它什么都没干,所以我们只能通过用 kill 进行停止此进程,换句话说,?Ctrl+C 产生的信号只能发给前台进程。 ? ? ? ? 而且要说明一点,当我们在前台运行这个进程的时候:
? ? ? ? 我们会发现:我们在发送 ls pwd top 这种命令时,所有的命令都没有效果了,这时因为在 Linux 中只允许有一个前台进程。 ? ? ? ? bash 是默认的前台进程,入宫 myproc 变成前台进程了,那么其中的 bash 就不是了,那么也就没有办法解释这种命令了,但是... ...
? ? ? ? 我们将 myproc 放在后台进行运行后,我们就可以运行命令了,这是因为 bash 此时是前台进程,可以对命令进行解析。只不过后台运行的 myproc 打印的数据会对我们进行干扰, 1.4?信号概念????????信号是进程之间事件异步通知的一种方式,属于软中断。 1.5?用kill -l命令可以察看系统定义的信号列表
Term 是 terminal(终端) Ign 是?ignore(忽略) 1.6?信号处理常见方式概览(sigaction函数稍后详细介绍),可选的处理动作有以下三种:
1.7 总结? ? ? ? 通过上面我们已经知道信号是什么了,那么接下来就说一下: 为什么要有信号? ? ? ? ? 因为计算机大多是为了解决人的问题,所以大多部分计算机的处理逻辑也是从人的生活中来的,而且大家也可以发现我们人在生活中处理事件的时候,我们无外乎在处理两种事件:一种是常规事件(按部就班的),第二种是突发事件。那么人就要有能够处理突发事件的能力,因为我们必须要处理,因为事情永远是推着人走的。 ? ? ? ? 同样的,当一个进程正在运行时,它也可能会遇到突发状况,比方说突然收到了 CTRL+C ,突然 除 0 了,导致程序出现错误了。 ? ? ? ? 所以为什么要有信号,本质是要让程序具备处理突发事件的能力,人如此,进程也如此。 ? ? ? ? 那么此时下面就要进行说明怎么办了。 2.?产生信号2.1 通过终端按键产生信号????????SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump,现在我们来验证一下。
? ? ? ? 我们可以看到,我们用 CTRL+ \ 是对应的 3号 信号:SIGQUIT。我们通过上面给的图可以看到 3号 信号默认的动作是 Core ,表示的是在结束的时候它有一个动作叫做核心转储。 ? ? ? ? 因为我用的是云服务器,但是云服务器的核心转储是不明显的,默认是关掉的。 ? ? ? ? 接下来我们看一下:
? ? ? ? 我们要注意的是黄色围起来的这一项,我们可以看它的大小是 0? 。 ? ? ? ? 那么我们接下来来设置一下:
? ? ? ? 当我们设置完我们就可以看到 core file size 的大小就变成个了 10240,此时叫做将它的核心转储直接打开(默认是0的话就意味着它是关闭状态)。 ? ? ? ? 那么继续看:
? ? ? ? 我们可以看到,当我们再进行 CTRL+ \ 的时候也退出了,而且后面有一个 (core dumped) ,而且当我们查看当前目录下多了一个 core.31923 这个临时文件,这个 31923 数字叫做发生这次核心转储的进程的 id 。 ? ? ? ? 解释一下:一个进程在终止的时候有很多种终止方式,其中 Terminal 一般是直接退出,也可以理解成是我们手动的让它退出了,但不做任何转储文件的 dump(转储) ,而如果我们自己打开了核心转储,并且我们收到了信号(不同的信号有不同的作用,不同的信号是一种不同的错误类别),而有些信号是需要进行核心转储的。 ????????比方说,代码运行的时候出错了,我们关心的是代码因为什么出错了,我们之前讲的代码退出的三个方式:1. 代码跑完结果对,2. 代码跑完结果不对,3. 代码运行中的时候出错。前两个最起码是跑完了,最后根据退出码就能判断哪里有问题,那么当第三种:代码运行中的时候出错了,我们也要有办法判定是什么原因出错了。 ? ? ? ? 我们在平时出现第三种情况的时候,我们一般是通过调试来判断哪里出现了问题,但其实还有 Linux 中还有一种方法就是通过核心转储功能:把进程在内存中的核心数据转储到磁盘上,core.pid -> 核心转储文件。目的是为了调试、定位问题。一般云服务器是属于线上生产环境,默认是关闭的(有的小伙伴可能用的虚拟机,虚拟机是默认打开的)。 ? ? ? ? 打开的状况我们上面那也进程了演示。那么这里有一个问题: 为什么在云服务器上核心转储功能默认是关闭的呢? ? ? ? ? 比方说我们在服务器上写了一个网络服务或者定期执行的一个服务,这个服务可能因为某种异常而挂掉,如果你打开了核心转储,那么挂掉之后会在本地的磁盘文件中生成?corn 文件,这个无可厚非,但是一般大的互联网公司在服务挂掉的时候,最重要的事情不是在乎是因为什么原因挂掉的,重要的是想尽快的让它恢复正常。因为出 BUG 不是经常事件,而是偶尔的事情。所以重要的是先让服务跑起来,不要让公司受到太大的影响。当服务恢复之后再进行对故障的排除工作。 ? ? ? ? 如果是小问题的话那么就先让服务恢复出来,然后再进行检查,但是如果出现了大问题,而且有一个一崩就重启的功能,那么一重启起来就崩,崩了就重启,如此往复。就会出现大量的 core file 文件:
?? ? ? ? 而且我们可以看到,这种文件一个都要 1MB 多,每个都不小,要说重启很长事件,那么当我们去排查的时候会发现 core 文件将某个分区或者磁盘文件都沾满了,最终导致服务想重启都没法重启,甚至操作系统都挂了,所以默认是关闭的。
Core Dump????????首先解释什么是Core Dump。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存在PCB中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。 首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K:$ ulimit -c 1024 ? ? ? ? 那么我们就来使用一下 corn dumped 级别的调试: ? ? ? ? 注意我们在编译的时候一定要加上 -g 选项,因为core dumped 文件需要使用 gdb 。
?? ? ? ? 我们可以看到虽然有警告,但是没有事情,因为就是故意写错的,我们会看到,1 秒之后,会看到有一个浮点型异常,而且出现了一个 core.8881 这个 core dumped 文件。
? ? ? ? 当我们打开的时候会发现里面都是乱码,什么都看不到,因为它是直接把内存中的有效核心数据 dumped 到磁盘上(转储到磁盘上),这个是帮我们定位问题。 ? ? ? ? 接下来:
?? ? ? ? 我们通过 gdb 和 core.8881 文件找到了问题在 16 行,这样我们就快速的定位到了刚刚的代码是因为什么原因出错的,这个调试方法叫做事后调试,也就是当我们的程序崩溃了再进行调试。 ? ? ? ? 而且我们刚才看到的错误是 FLOATING POINT EXCEPTION :
? ? ? ? 其中将 FLOATING POINT EXCEPTION 的首字母提出来就是 FPE,也就是 8号 信号。 ? ? ? ? 我们发现 除0 错误是代码错误,然后我们进程终止了。 为什么C/C++进程会崩溃? ? ? ? ? 本质就是因为收到了信号! ? ? ? ? 那么接下来再实验一个:
?? ? ? ? ?我们可以看到出现的是 11号 信号,Segmentation fault 段错误。
那么为什么会受到信号? ? ? ? ? 首先我们要知道,信号都是由OS发送的,那么OS又怎么识别到有进程触发了问题呢? ????????OS在进行正常运行的时候发现CPU内有一个计算状态标志位发生了除0错误,然后操作系统就立马定位当前运行的哪个进程,所以就来进行终止。 ????????所以操作系统识别到了硬件错误,然后将这个硬件错误解释(包装)成信号发送给目标进程。 ????????其实本质就是找到这个进程的PCB,向目标的位图比特位由0制1,然后这个进程在合适的时候处理8号信号时默认就给"自己终止"了 ? ? ? ? ? ? 这下就能回答这个问题了:错误最终一定会在硬件层面上有所表表现,进而被OS识别到!所以进程最后才会崩溃。
? ? ? ? ?还记得之前的这幅图么,当时只用到了次低8位的退出码,和低7位的信号编号,但是没有用低8位这个 core dump 。 ? ? ? ? 接下来我们就看看这个 core dump :
? ? ? ? ?所以这里是 core dump 的意思是:进程崩溃的时候,是否 core dump ! ? ? ? ? ?但也不是所有信号退出的时候都会 core dump ,比如 2号 信号。
? ? ? ? 我们可以看到还有一些用过键盘可以组合的信号。 ? ? ? ? 然后当我们 CTRL+Z 之后,进程就被暂停了,但是我们可以查看:
? ? ? ? 我们 myproc 正在运行,状态是 T 。如果我们想让它再运行起来,我们可以发送 CONTINUE 信号:
? ? ? ? 这个不是重要,我想说的是这个进程一旦被暂停就会被放在后台,一旦放在后台我们若想查看后台任务可以用 jobs ,我们想让这个进程放在前台立马运行起来用 fg * 。
? ? ? ? 而且我们发现:
? ? ? ? 我们发送 2号 信号被捕捉了,但是发送 9号 信号的时候没有被捕捉。 ? ? ? ? 所以虽然代码中看起来是从 1 到 32 都捕捉,但实际上有些信号是不能被捕捉的,比如 9号 ,因为如果所有的信号都可以被捕捉,那么就可以把所有的信号忽略掉,那么这个进程就没有办法可以杀死了,即便是操作系统。 ????????所以操作系统允许捕捉,但不允许全部捕捉。 ? ? ? ? ? 还有一个问题就是当连续操作两次的时候会终止,可能是这个操作系统对这个操作做了特殊处理,其他的操作系统这么写代码是没有问题的,那么我们只要再捕捉信号的那个 handler 中再重新注册一下信号:
? ? ? ? ?这样就可以了,但是这样就没有办法退出了,我们就新建一个 SSH 渠道进行终止就好了。 2.2 调用系统函数向进程发信号? ? ? ? 我们前面学过:
? ? ? ? 当然最后一个正是要学的。 ? ? ? ? 我们要用到的接口是 kill :
? ? ? ? 而且我们要使用的方式是通过:
? ? ? ? 所以也就表明了,我们在函数实现的时候一定要传命令行参数。
? ? ? ? 为了更模拟 kill ,我们就将此文件目录添加到环境变量中。
? ? ? ? 我们写了一个重复打印的 myproc ,然后通过 mykill.c 去用系统调用 kill 掉 myproc 进程,可以看到已经成功的使 myproc 退出。
? ? ? ? 当然还有这个接口,这个接口是自己给自己发信号,那么接下来我们就来用用:
? ? ? ? 我们可以看到,每隔一秒都会发送一次 2号 信号,然后因为我们注册了 2号 信号,所以被进程接收然后进行自定义行为。 ? ? ? ? 当然还有一个调用:
? ? ? ? 这个也是自己给自己发信号,是发 6号 信号(SIGABRT)。
? ? ? ? ? 我们可以看到,运行起来后,接收的是 6号 信号,而且接收到一次之后就退出了,可是我们上面明明对 6号 信号进行了捕捉。 ? ? ? ? 因为有些信号是可以被捕捉,有些信号不可以被捕捉, 6号 信号即被捕捉了,也被终止了,这就是 6号 信号,abort 的作用很像我们一直用到的 exit(),但是exit()是正常终止,而 abort() 本质上是通过信号来终止,是自己终止自己。但是要说明的是,exit() 本质上是函数,只要是函数就说明它可能会失败,而 abort 函数总是会成功(函数无返回值)。 2.3 由软件条件产生信号? ? ? ? 我们之前的异常本质上是由软件上引起的,但最终引起的问题是在是在硬件上,也就是CPU的状态寄存器出了问题,MMU转化出了问题,所以最后我们就看到操作系统识别硬件出现了错误,然后转化成信号发送给进程。 ? ? ? ? 软件条件产生信号:在我们写管道那里的时候说,有一端是读端,有一端是写端,如果将读端关闭,写端一直写,那么写端就会被立刻终止。这样的原因就是写入的软件条件不满足,也就是当前管道是不允许你写入的,所以我们当时就受到了一个 SIGPIPE 这个信号,这个信号就是由于软件条件产生的信号,所以就是我们写入的条件不成熟,这就是软件条件。 ? ? ? ? 当然还有其他的软件条件,也就是我接下来要将的 alarm(闹钟) 函数。 ????????这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿, 于是重新设定闹钟为15分钟之后响,"以前设定的闹钟时间还余下的时间"就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。
? ? ? ? ?这个代码的意思是,1秒 后发送 14号 信号 SIGALRM ,然后在这 1秒 内看能进行多少次 count++ 并打印出来,我们可以看到,在六万左右,但是这其实不代表真实的速度,因为我们这里是在外设上打印了,就会慢很多。而且也有网络的原因,我们在网络上计算,然后再发送过来,就会慢。 ? ? ? ? 那么我们接下里再改一改:
? ? ? ? ?我们可以看到,我们直接让它累加,然后最后再打印,就可以看到会加到这么大的数,这就是因为在累加的时候没有进行 IO ,所以我们可以得知,如果计算机在进行 IO 的时候,效率是有多低。
? ? ? ? 我们要想每隔一段时间执行一次任务,那么这样就可以完成。 2.4?硬件异常产生信号????????硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。 ? ? ? ? 当然这段的操作我们在上面将信号的时候已经测试过了,所以这里就不进行测试了。 3. 总结思考一下
? 4.?阻塞信号4.1 信号其他相关常见概念
4.2?在内核中的表示信号在内核中的表示示意图:
4.3?sigset_t????????从上图来,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。后面会详细介绍信号集的各种操作。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。 4.4?信号集操作函数????????sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的。
????????这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。 ? ? ? ? 其实在 C90 版本是不存在布尔值的,只有宏,但是 C98 后就有了。
? ? ? ? 这个栈是用户栈,这个空间是用户空间定义的,与用户空间对应的是内核空间,我们下面调用的清空、设置、添加,不会影响进程的任何行为。 ? ? ? ? 有的人可能认为不对啊,前面不是讲了可以通过信号集来对进程的 pending 、 block 表进行相关设置么,那为什么不会影响任何进程的行为呢。 ????????原因是因为我们这里的设置并没有设置进进程相关的比方说PCB内。所以我们需要通过这种参数,通过系统调用设置进操作系统,这个行为才能够影响操作系统。 ? ? ? ? 所以我们需要调用 sigprocmask 。 4.4.1?sigprocmask????????调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)
????????如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
4.4.2?sigpending
? ? ? ? 这个函数主要是来获取当前进程的 pending 信号集,所以这个函数是一个输出型参数。
? ? ? ? 我们可以看到,没发送 2号 信号前为全 0 ,但是发送完 2号 信号后,第二个位置由 0 制 1 了。 ? ? ? ? 这说明操作系统向进程发送了信号,但是当前这个信号无法被立即递达,那么这个信号就处于 pending 状态。处于 pending 状态我们就可以通过打印获取到了。 ? ? ? ? 当然我们也可以通过某种方式恢复:
? ? ? ? 我们可以看到,开始没收到 2号 信号的时候,处于全 0 的状态,收到之后 第二位由 0 制 1 了,然后隔几秒后又恢复曾经的信号屏蔽字,也就是全 0 状态。 ? ? ? ? 所以我们看到的结果就是这样子。
5. 捕捉信号5.1?内核如何实现信号的捕捉????????如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册了 SIGQUIT 信号的处理函数 sighandler 。当前正在执行 main 函数,这时发生中断或异常切换到内核态。在中断处理完毕后要返回用户态的main函数之前检查到有信号 SIGQUIT 递达。内核决定返回用户态后不是恢复 main 函数的上下文继续执行,而是执行 sighandler 函数,sighandler 和 main 函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。 sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复 main 函数的上下文继续执行了。
? ? ? ? ? 这样就对内核态和用户态就有一个大致了解了。 ? ? ? ? ? 当然上面那幅图也可以用高数中的无穷来看:
5.2?sigaction
????????当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处
理函数返回时自动恢复原来的信号屏蔽字。
?
? ? ? ? ? ?我们可以看到,我们已经对 2号 信号进行了捕捉。 ? ? ? ? 如果我想恢复出来呢:
? ? ? ? 我们可以看到,我们这要这样恢复一下就可以了。 6.?可重入函数? ? ? ? 我们用到的各种STL,全部都是单进程,也就是一个执行流,如果是多执行流那么代码就有可能重复调用。
? ? ? ? 最后我们可以看到 node2 这个节点丢失了,这个现象就叫做内存泄漏。 ? ? ? ? 而且STL里的大多也都是不可重入的。
如果一个函数符合以下条件之一则是不可重入的:
7.?volatile
?? ? ? ? ?我们可以看到,当我们运行起来的时候是不会停止的,然后我们通过键盘发送 2号 信号后,才会正常退出。没有问题。
? ? ? ? 通过我们上面的学习,我们知道信号捕捉和 main 函数是两种执行流,有可能我们这个进程跑起来永远都不会收到二号信号,只有我们 CTRL+C 发送它才能收到二号信号。 ? ? ? ? 也就是说 main 执行流永远执行,而信号捕捉函数不一定会执行。 ? ? ? ? 但是 while 循环是在 main 函数中的,main 函数编译器在编译的时候,只能检测到 main 函数对 flag 的使用,flag 是全局变量,在运行时,编译器只能检测到 main 函数对 flag 没有任何更改操作(虽然在信号捕捉函数有,但是编译器识别不到,因为它们是两个执行流)。 ? ? ? ? 如果没有人改 flag ,如果编译器优化级别较高的时候,那么编译器会将 flag 优化成寄存器变量,或者将 flag 设置到寄存器里。
? ? ? ? 所以如果编译器优化级别较高的时候,发送了二号信号,即使被捕捉了,也一直死循环不会退出。 ? ? ? ? 接下来就来优化一下:
? ? ? ? 我们可以看到,我们只要在编译的时候加上 -O3 ,就会发生我们上面说到的那种情况。 ? ? ? ? 那么接下来就要用到 volatile 进行解决:
? ? ? ? 我们可以看到,即使现在还是 -O3 的优化,我们还是可以通过发送 2号 信号来结束进程。 这个 volatile?的意义是:
? ? ? ? 所以它 volatile 是保持了内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作! 8. SIGCHLD信号????????进程一章讲过用wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不 能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一 下,程序实现复杂。 ????????其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。 ????????请编写一个程序完成以下功能:父进程fork出子进程,子进程调用exit(2)终止,父进程自定 义SIGCHLD信号的处理函数, 在其中调用wait获得子进程的退出状态并打印。 ????????事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用。
? ? ? ? 这里在 handler 循环的原因是:SIGCHLD(17号)是普通信号的,有可能有很多进程,最终在某一个时刻都退出了,因为记录信号的 pending 位只有一个,所以此时知道有进程退出,但是如果此时只 wait 一次,那么就极有可能只回收了一次,那么其它信号可能就没有回收,所以这里要用循环不断的去回收。 ? ? ? ? 在 waitpid 的时候设置位非阻塞(WNOHANG):当在进行 while 循环检测时会有这种现象:我们创建了十个子进程,此时有五个退出了,当不断的将五个回收完,我们此时应该还是要 wait,所以我们这里的 waitpid 要有两个功能,一是去检测还有没有子进程退出的,二回收已经退出的。所以当把五个回收完它并不知道退出完了,就会认为还有,所以还会调 wait,但是如果没有了,那么 wait 就会被阻塞住,进而导致在 handler 那里就回不去了。 ? ? ? ? 所以这里就设置成 WNOHANG,有就一直回收,只要回收失败的时候,就证明把所有的子进程回收完了,然后再继续去运行父进程。 ????????接下来我们要验证一下,怎么确认子进程终止时会给父进程发SIGCHLD信号:
? ? ? ? ?我们可以看到,确实会给父进程发送 17号 信号,而且确定是回收的子进程。 ? ? ? ? 接下来我们就来试试通过调用 SIG_IGN? 去自动清理子进程: ? ? ? ? ? 我们可以看到在前三秒内有两个进程,三秒后子进程退出,而且没有看到僵尸(Z状态)进程,这个只在Linux下是可以的,其他的好像不可以。 ?????????如上就是?进程信号 的所有知识,接下来要讲解 Linux多线程 如果大家喜欢看此文章并且有收获,可以支持下 兔7 ,给?兔7 三连加关注,你的关注是对我最大的鼓励,也是我的创作动力~! ? ? ? ? 再次感谢大家观看,感谢大家支持! |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/15 9:51:29- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |