在学习 APUE 信号一章时,书中描述 SIGCLD和 SIGCHLD信号时,我一时没有搞清楚,在查阅一些资料后把二者的不同描述在此。

APUE这本书有一个很大的特点是:它全书写的是Unix平台的编程,因此会引入很多不同平台的差异,这在编写跨平台应用程序的时候有很大的参考价值。但是这样也带来了一个不好的后果:如果一个刚接触Unix编程的读者在读这些内容时(假设像我一样使用Linux平台学习),会一时抓不住重点,书中论述的在不同平台下的不同差异反而会成为学习的负担。另外还有一个更糟的问题是:对比的几个平台大部分都已经过时,或者说这些对比的平台在当前的使用环境中基本没有在用,于是出现我在学习的时候和一些没有意义的平台在对比,这样增加了学习的难度。

SIGCLD和SIGCHLD的讲述也是这个问题,文中夹杂着两者和不同的平台在论述,让我看了几遍都没搞清楚到底区别在哪儿。

1. 在Linux平台

不需要考虑两者,因为这二者就是一样的,在Linux平台的源码中有如下定义

#define SIGCLD SIGCHLD

2. POSIX的论述

POSIX使用的是 SIGCHLD的语义(不提及SIGCLD),可以理解为在 POSIX中压根儿就没有 SIGCLD这个变量的存在,我们只需要去理解 SIGCHLD即可

3. SIGCLD和SIGCHLD的由来

我们可以这样论述:

  1. SIGCLD是System V 这个系统定义的,并且提供了 SIGCLD的语义;
  2. SIGCHLD是 BSD 系统定义的,并且提供了 SIGCHLD的语义;

相当于同一个事情存在两种不同的做法,但是后面要做标准化,究竟选用哪一个呢?最后标准化组织决定采用 BSD的语义,也就是说我们处理 子进程变化的信号只使用 SIGCHLD这一个钦定的做法,SIGCLD我们不用管

4. 详细论述两者语义

其实通过上面的说法,我们已经知道在实际开发中我们几乎接触不到这种老古董的 System V系统,并且POSIX已经钦定了 SIGCHLD的处理方法,所以在现代的操作系统编程中我们根本就不用管 SIGCLD,有很多系统也是类似Linux的做法,定义了二者就是同一个宏

但是为什么要去了解 SIGCLD呢?这个就是一个问题,你在学习的时候遇到一个明明已经过时的东西,但是免不了总是有人提及它,有时候会搞得你很烦,所以也硬着头皮去被迫认识一些这种历史上的“错误做法”。

4.1 前提知识:信号如何处理(desposition of signals)

在论述它之前,我们先说一下对于信号处理的方式,一般是3个:

  • (1)不去管它
  • (2)设置忽略它
  • (3)设置捕获它

下面对这三个手段详细说明一下:

(1)不去管它,就是不去编写任何一行和这个信号相关的代码,让信号默认的行为其作用(操作系统默认对每一信号都有一套默认的做法)

(2)设置忽略它,其实是要写一行代码

signal(SIGxxx, SIG_IGN);

这样我们在收到这个信号之后,这个信号不会调用原先操作系统的默认做法。至于忽略之后又副作用咋办?也就是说我们本来操作系统对于一个信号有一种很好的响应,但是我写下了这行忽略的代码,那么这个信号又很严重是有后果的,那会出现什么情况?答案是:不知道,POSIX给出的解释是,这是一个未定义的行为。我们不应该依赖这种未定义的行为。

那么什么时候忽略是安全的呢?比如那种信号处不处理都无所谓的信号,可以安全的调用这行忽略的代码;

(3)设置捕捉它,也就是我们需要显式的写下如下的代码

1. signal(SIGxxx, sig_handler);2. void sig_handler(int signo)
{...
}

需要写一行设置代码,提供一个处理函数

前提知识说完了,还有一点要稍微提一下,我们可以这样设置信号的处理,比如

signal(SIGxxx, SIG_DFL);

这一行是说我们把信号的处理设置成系统默认的处理方式,它和(1)的处理是类似的。如果单独只有这一行针对某个信号的处理时,写不写无所谓。但是一般它是用来将(2)(3)中的做法恢复成(1)的做法,这个时候就是调用它的主要目的。

4.2 SIGCLD语义

首先要明确这是System V的信号,这个信号会在子进程状态出现变化的时候被调用

我们就按上面提到对于信号的处理方式(1)(2)(3)来分析这个信号的表现

(1)如果我们啥也不写【和手动的写一行 signal(SIGCLD, SIG_DFL)等价】

子进程结束,它会通知父进程,由于我们啥也没写,如果我们没有调用wait和waitpid等回收子进程残留信息的函数,那么就会出现僵死进程;

(2)如果我们明确的忽略它【手动写一行代码 signal(SIGCLD, SIG_IGN)】

子进程结束,它自我清理残留的进程信息。我们不需要调用wait和waitpid等回收函数,不会出现僵死进程。但是一旦我们调用了wait和waitpid反而会出现问题,就是这个wait和waitpid得不到任何子进程的残留信息(因为已经被自我清理了嘛),导致wait和waitpid返回-1(也就是没有找到任何子进程残留信息),并且把errno设置为 ECHILD

(3)如果我们捕获了SIGCLD

【手动写了一行 signal(SIGCLD, sig_handler);并且编写了函数 sig_handler】

当我们写下signal(SIGCLD, sig_handler) 这一行代码时,系统立刻会检查我们的进程是否有子进程已经结束等待回收,如果确实有,那么会立刻调用 sig_handler函数,如果我们在 sig_handler函数中写下这样的代码

static void sig_handler(int signo) /* interrupts pause() */
{pid_t pid;int status;printf("SIGCLD received\n");if (signal(SIGCLD, sig_handler) == SIG_ERR) /* reestablish handler */perror("signal error");if ((pid = wait(&status)) < 0) /* fetch child status */perror("wait error");printf("pid = %d\n", pid);
}

这里为什么要在 sig_handler中再次调用signal是由于在System V里面的信号处理一旦信号发生进入处理函数,信号的处理方式自动被修改为DFL(恢复到原来操作系统的默认处理),但是我们这样写的sig_handler会进入一个死循环,因为一旦遇到 signal(SIGCLD, sig_handler)的调用就去检查系统是否已经回收了子进程的残留信息,但是很显然我们的signal函数调用在 wait函数之前,因此就会再次进入到这个函数,一直递归调用直到栈溢出

会导致程序一直执行到栈溢出,解决方法是首先调用 wait 处理掉子进程残留,再调用signal

4.3 SIGCHLD语义

(1)如果啥也不写,那么子进程结束,它会通知父进程,由于我们啥也没写,如果我们没有调用wait和waitpid等回收子进程残留信息的函数,那么就会出现僵死进程;

这个语义和 SIGCLD是一模一样的;

(2)如果我们明确的忽略它【手动写一行代码 signal(SIGCHLD, SIG_IGN)】

那么会出现两种情况:

    1. 如果是在 4.4 BSD系统上调用,那么总是会产生僵死进程,和(1)的效果一样
    1. 如果是使用 signal和sigset两个函数来设置的信号处理(也就是说如果调用了 signal(SIGCHLD,SIG_IGN)或者 sigset(SIGCHLD, SIG_IGN))那么子进程会自我清理,不会产生僵死进程(在所有APUE的4个测试平台上表现是这样的,这4个平台是:FeeBSD 8.0 Linux 3.2.0 Mac OS X 10.6.8 Solaris 10)

这就相当坑爹了,在4.4BSD上一定会产生僵死进程,但是转到 FreeBSD 8.0(FreeBSD是基于BSD开发的)又不会,真的非常的混乱

(3) 如果我们自己捕获SIGCHLD信号【手动写了一行 signal(SIGCHLD, sig_handler);并且编写了函数 sig_handler】,那么结果是怎么样的呢? 其实POSIX又没有给出明确的说明,也就是这又是一个未标准化的行为(依赖各家自己的实现),在Linux上的实现是这样的:Linux不会去管之前已经退出等待回收的子进程,它只管SIGCHLD设定之后的子进程。也就是类似于法律里面不追究过往,我不管你以前子进程是什么情况,反正从我设定SIGCHLD处理之后,如果再出现子进程结束等待回收,那么就会调用我们指定的处理函数。

5. 总结

Unix系统中的信号机制是已经先有了各家实现后来才进行标准化的,因此标准化过程中需要考虑到各家实现中的不同之处,并且挑选某个平台的实现作为标准化的范本,这样就会造成特别多的问题,非常多的细节和平台化的差异。与之相反的是多线程的实现是现有了规范,然后再各家去实现规范,这样就非常的统一,不会存在着各种不一致的情况。

综合上面讨论的SIGCLD和SIGCHLD,如果想完全理解清楚,需要先了解:

  • (1)什么是不可靠的信号
  • (2)signal函数调用在各个平台的不一致性

有了这两个基础才能开始看上面提到的 SIGCLD和SIGCHLD的不同

最后关于信号处理的建议是:

  1. 不要理会SIGCLD,无视它的存在,只使用 SIGCHLD
  2. 只使用sigaction去设置信号处理函数,不要使用signal

6. 参考资料

  1. Unix高级环境编程 10.7 SIGCLD的语义
  2. signal(SIGCLD,SIG_IGN)https://blog.csdn.net/cffishappy/article/details/7005115?utm_medium=distribute.pc_relevant_t0.none-task-blog-2defaultCTRLISTdefault-1.no_search_link&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2defaultCTRLISTdefault-1.no_search_link

说说SIGCLD和SIGCHLD相关推荐

  1. 10.7 SIGCHLD定义

    经常混淆的两个信号就是SIGCLD以及SIGCHLD,信号SIGCLD源于System V,该信号的含义与源自BSD的信号SIGCHLD不一致.同时POSIX.1信号也称为SIGCHLD.源自BSD的 ...

  2. Linux 信号学习

    Linux 信号学习 信号量的基本概念 信号产生的条件 信号如何被处理 信号的异步特质 信号的分类 可靠信号/不可靠信号 实时信号/非实时信号 常见信号与默认行为 信号处理 `signal()` 函数 ...

  3. 《unix环境高级编程》--- 信号

    信号是软件中断. #include <signal.h>void (*signal(int signo, void (*func)(int)))(int); 该函数有2各参数,第一个为信 ...

  4. 信号 09 | SIGCLD语义

    1. SIGCLD信号 SIG_DFL :默认的处理方式是不理会这个信号,但是也不会丢弃子进程状态,所以如果不用wait,waitpid对其子进行进行状态信息回收,会产生僵尸进程. SIG_IGN : ...

  5. 关于SIGHCLD和SIGCLD

    SIGCLD 简单的说, SIGCLD不是可靠信号. 这里的不可靠是指当大量信号来的时候, 重复信号它只会处理一次, 而不是多次. 很多信号在瞬间("同时")产生,内核也不一定能够 ...

  6. UNIX再学习 -- 可重入函数和 SIGCHLD 语义

    一.可重入函数 参与信号处理的函数必须是可重入函数. 1.何为重入? 假设进程的住控制流程此刻正在调用 foo 函数,就在 foo 函数刚执行到一半的时候,内核向进程递送了信号 a:假设进程对信号 a ...

  7. linux下的僵尸进程处理SIGCHLD信号【转】

    转自:http://www.cnblogs.com/wuchanming/p/4020463.html 什么是僵尸进程? 首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打 ...

  8. OS / Linux / SIGCHLD 信号

    一.SIGCHLD 的产生条件 子进程终止时. 子进程接收到 SIGSTOP 信号停止时. 子进程处在停止态,接收到 SIGCONT 后唤醒时. 也就是说:子进程的运行状态发生变化就会发送 SIGCH ...

  9. signal(SIGCHLD, SIG_IGN) 和 signal(SIGPIPE, SIG_IGN) 使用场景

    一.signal(SIGCHLD, SIG_IGN); 因为并发服务器常常 fork 很多子进程,子进程终结之后需要服务器进程去 wait 清理资源.如果将此信号的处理方式设为忽略,可让内核把僵尸子进 ...

最新文章

  1. VBA中级班课时3小结
  2. kivy中kv语言的变态用法
  3. Visual Studio 2019更新到16.2.2
  4. JavaScript中的true和false
  5. 【控制】《多智能体系统的动力学分析与设计》徐光辉老师-第5章-基于采样位置信息二阶多智能体系统的多一致
  6. java几种删除_几种删除Linux目录的方法
  7. 基于深度学习的人脸识别系统(Caffe+OpenCV+Dlib)【三】VGG网络进行特征提取
  8. SharePoint对象模型性能考量
  9. 扩展欧几里得算法与模乘逆元的程序
  10. Windows 下安装 SVN 服务器、创建版本库、授权访问
  11. 2022软件测试技能 Jmeter+Ant+Jenkins持续集成并生成测试报告教程
  12. vue + el-menu 实现菜单栏无限多层级分类
  13. Leetcode 5053. 地图分析 (150周赛)
  14. 民国歌曲 - 毛毛雨
  15. 对于目标文件系统,文件过大怎么办
  16. 反函数抽样(包括离散的)
  17. 企业发展滞缓,还不是因为踩了这四个数据大坑!
  18. C#实现平面图形图像缩放、平移、自定义坐标系
  19. matlab的simulink中的normal模式acclerator等模式的选择方法
  20. shell(9): shell脚本安装chajian

热门文章

  1. 为什么程序员买不起房子?
  2. spark面试题整理
  3. TS复习-----TS中的函数
  4. Hive: Task failed task_ Job failed as tasks failed. failedMaps:1 failedReob failed as tasks failed
  5. hive执行msck repair报错msck is missing partition columns under hdfs://表分区路径
  6. JAVA POI 导出EXCEL时,EXCEL模板中的公式无效问题
  7. 论文阅读 (85):Residual Attention-Aided U-Net GAN and Multi-Instance Multilabel Classifier for Automatic
  8. 一双拖鞋引发的血案——我与《程序员》不得不说的故事
  9. php中取整的函数,php取整函数
  10. 求职陷阱:Lazarus组织以日本瑞穗銀行等招聘信息为诱饵的攻击活动分析