说说SIGCLD和SIGCHLD

Posted csxiaoshui

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了说说SIGCLD和SIGCHLD相关的知识,希望对你有一定的参考价值。

在学习 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的主要内容,如果未能解决你的问题,请参考以下文章

说说SIGCLD和SIGCHLD

说说SIGCLD和SIGCHLD

10.7 SIGCHLD定义

14僵尸进程方式3 4补充

子进程是否也应该解除阻塞的SIGCHLD信号?

Notepad++编辑器——Verilog代码片段直接编译