Unix/Linux编程:SIGCHLD信号

子进程的终止属异步事件,父进程无法预知其子进程何时终止(即使父进程向子进程发送SIGKILL信号,子进程终止的确切时间还依赖于系统的调度:子进程下一次在何时使用CPU)。父进程应该使用wait()或者类似调用来防止僵尸子进程的累积,以及采用如下两个方法来避免这一问题:

  • 父进程调用不带 WNOHANG 标志的 wait(),或 waitpid()方法,此时如果尚无已经终止的子进程,那么调用将会阻塞
  • 父进程周期性地调用带有 WNOHANG 标志的 waitpid(),执行针对已终止子进程的非阻塞式检查(轮询)。

这两种方法使用起来都有所不便:

  • 一方面,可能并不希望父进程以阻塞的方式来等待进程的终止
  • 另一方面,反复调用非阻塞的waitpid()会造成CPU资源的浪费,并增加应用程序设计的复杂的。

为了规避这些问题,可以采用针对SIGCHLD信号的处理程序

为SIGCHLD建立信号处理程序

无论一个子进程于何时终止,系统都会向其父进程发送 SIGCHLD 信号。对该信号的默认处理是将其忽略,不过也可以按照信号处理程序来捕获它。在处理程序中,可以使用wait()或者类似方法来收拾僵尸进程。

但是,当调用信号处理程序时,会暂时将引发调用的信号阻塞起来(除非为 sigaction()指定了 SA_NODEFER 标志),且不会对 SIGCHLD 之流的标准信号进行排队处理。这样一来,当SIGCHILD信号处理函数正在为一个终止的子进程运行时,如果相继由两个子进程终止,即使产生了两次SIGCHLD信号,父进程也只能捕获到一个。结果是,如果父进程的SIGCHLD信号处理程序每次只调用一次wait(),那么一些僵尸进程可能会成为“漏网之鱼”。

解决方案是:在SIGCHLD处理的程序内部循环以WNOHANG标准来调用waitpid(),直至再无其他终止的子进程需要处理为止。通常 SIGCHLD 处理程序都简单地由以下代码组成,仅仅捕获已终止子进程而不关心其退出状态

while(waitpid(-1, NULL, WNOHANG) > 0) continue;

上述循环会一直持续下去,直至 waitpid()返回 0,表明再无僵尸子进程存在,或-1,表示有错误发生(可能是 ECHILD,意即再无更多的子进程)

SIGCHLD 处理程序的设计问题

假设创建 SIGCHLD 处理程序的时候,该进程已经有子进程终止。那么内核会立即为父进程产生 SIGCHLD 信号吗?SUSv3 对这一点并未规定。一些源自系统 V(System V)的实现在这种情况下会产生 SIGCHLD 信号;而另一些系统,包括 Linux,则不这么做。为保障可移植性,应用应在创建任何子进程之前就设置好 SIGCHLD 处理程序,将这一隐患消解于无形

需要更深入考虑的问题是可重入性(reentrancy):在信号处理程序中使用系统调用(比如waitpid())可能会改变全局变量errno的值。当主程序试图显示设置errno或是在系统调用失败后检查 errno 值时,这一变化会与之发生冲突。出于这一原因,有时在编写 SIGCHLD 信号处理程序时,需要在一进入处理程序时就使用本地变量来保存 errno 值,而在返回前加以恢复

看个例子:

//通过 SIGCHLD 信号处理程序捕获已终止的子进程
void                    /* Examine a wait() status using the W* macros */
printWaitStatus(const char *msg, int status)
{
    if (msg != NULL)
        printf("%s", msg);

    if (WIFEXITED(status)) {
        printf("child exited, status=%d\n", WEXITSTATUS(status));

    } else if (WIFSIGNALED(status)) {
        printf("child killed by signal %d (%s)",
                WTERMSIG(status), strsignal(WTERMSIG(status)));
#ifdef WCOREDUMP        /* Not in SUSv3, may be absent on some systems */
        if (WCOREDUMP(status))
            printf(" (core dumped)");
#endif
        printf("\n");

    } else if (WIFSTOPPED(status)) {
        printf("child stopped by signal %d (%s)\n",
                WSTOPSIG(status), strsignal(WSTOPSIG(status)));

#ifdef WIFCONTINUED     /* SUSv3 has this, but older Linux versions and
                           some other UNIX implementations don't */
    } else if (WIFCONTINUED(status)) {
        printf("child continued\n");
#endif

    } else {            /* Should never happen */
        printf("what happened to this child? (status=%x)\n",
                (unsigned int) status);
    }
}

从程序下面的执行例子可以看出,尽管有 3 个子进程退出,而父进程只捕获到两次 SIGCHLD 信号。
在这里插入图片描述

向已停止的子进程发送 SIGCHLD 信号

正如可以使用 waitpid()来监测已停止的子进程一样,当信号导致子进程停止时,父进程也就有可能收到SIGCHLD 信号。调用 sigaction()设置 SIGCHLD 信号处理程序时,如传入 SA_ NOCLDSTOP 标志即可控制这一行为。若未使用该标志,系统会在子进程停止时向父进程发送 SIGCHLD 信号;反之,如果使用了这一标志,那么就不会因子进程的停止而发出 SIGCHLD信号

因为默认情况下会忽略信号 SIGCHLD,SA_NOCLDSTOP 标志仅在设置 SIGCHLD 信号处理程序时才有意义。而且,SA_NOCLDSTOP 只对SIGCHLD 信号起作用

SUSv3 也允许,当信号SIGCONT 导致已停止的子进程恢复执行时,向其父进程发送SIGCHLD信号。(相当于 waitpid()的 WCONTINUED 标志。)始于版本 2.6.9,Linux 内核实现了这一特性

忽略终止的子进程

更有可能像这样处理终止子进程:将对SIGCHLD的处置显式设置为SIG_IGN,系统从而会将其后终止的子进程立即删除,而不是转为僵尸进程。这时,会将子进程的状态之不问,故而所有后续的 wait()(或类似)调用不会返回子进程的任何信息

注意,虽然对信号 SIGCHLD 的默认处置就是将其忽略,但显式设置对 SIG_IGN 标志的处置还是会导致这里所描述的行为差异。在这方面,对信号 SIGCHLD 的处理非常独特,不同于其他信号

如果许多Unix实现一样,在Linux系统中将对SIGCHLD信号的处置置为SIG_IGN并不会影响任何僵尸进程的状态,对它们的等待仍然要照常进行。在其他一些 UNIX 实现中(例如 Solaris 8),将对 SIGCHLD 的处置设置为 SIG_IGN 确实会删除所有已有的僵尸进程。

信号 SIGCHLD 的 SIG_IGN 语义由来已久,源于系统 V(System V)。SUSv3 也规定了此
处所描述的行为,不过原始的 POSIX.1 标准对此则未作表述。因此,在一些较老的 UNIX 实现中,忽略 SIGCHLD 并不影响僵尸进程的创建。要防止产生僵尸进程,唯一完全可移植的方法就是(可能是从SIGCHLD 信号处理程序的内部)调用 wait()或者waitpid()。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户/服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11种状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五种I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程间共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux网络编程之线程篇 36线程介绍 什么是线程 进程与线程 线程优缺点 线程模型 N:1用户线程模型 1:1核心线程模型 N:M混合线程模型 37POSIX线程(一) POSIX线程库相关函数 用线程实现回射客户/服务器 38POSIX线程(二) 线程属性 线程特定数据 39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一个简单的线程池实现 线程池性能分析 线程池实现 网络编程, Linux, 密码

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值