Linux: 关于 SIGCHLD 的更多细节

僵尸进程

何为僵尸进程?

一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程

成为僵尸进程的因素
  1. 子进程 先于 父进程退出;
  2. 子进程的状态信息,没有被父进程回收;

那么问题来了,子进程退出了,父进程怎么知道呢?

对该机制有稍微了解的话,不难得知一个关键因素:SIGCHLD。正是这个SIGCHLD起到了通知的作用,所以后面的处理也是基于它而实现。

僵尸进程处理方案
  1. 父进程捕获 SIGCHLD 信号,则显示调用 waitwaitpid
  2. 父进程直接忽略该信号。signal(SIGCHLD, SIG_IGN),这样子进程直接会退出。 需要注意的是,虽然进程对于 `SIGCHLD`的默认动作是忽略,但是还是显示写出来,才能有效;
  3. 把父进程杀了,子进程直接过继给 init,由 init伺候着。 不用担心 init会挂着一堆僵尸, init本身的设计就有专门回收的处理,所以有多少回收多少;

SIGCHLD 还能干嘛

刚才我们在处理到父子进程相关的问题时,多多少少接触到SIGCHLD, 那么,只有在回收子进程的时候才需要用到么?感觉好浪费不是么?

别担心 ~ 这个作用肯定是不止这样的!

其实对于SIGCHLD,我们一般的理解是,子进程退出发送的信号,但其实不是的,这个信号代表的含义是:

子进程状态变更了,例如停止、继续、退出等,都会发送这个信号通知父进程。而父进程就能通过 wait/waitpid 来获悉这些状态了。

看起来有点意思,我们仿佛能借此做些有趣的事情了。

wait / waitpid 相关知识
#include <sys/wait.h>
pid_t wait(int * statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);

wait相对来说会常用点,因为不需要指定 pid,而waitpid就在一些需要指定特定pid时才会比较常见,那么它们之间的关系就真的是只是这样么?

其实wait是对waitpid的封装,专门用来回收子进程退出的信息,同样的,它简单粗暴的设置成了堵塞方式,如果没有任何子进程退出,那么就堵塞住。

waitpid功能非常强大,pidoptions都提供了非常灵活的用法:

    pid:
         < -1: 取该 pid 的绝对值,如果任何子进程的进程组ID等于该值,则该进程组的任一子进程中的进程状态发生变化,都会触发`waitpid`的回调;
        == -1: 监听范围扩大到任意子进程,也就是 wait(status);
        ==  0: 监听进程组ID和父进程一样的子进程;
         >  0: 监听该pid的子进程;

    options:
        WNOHANG: 调用时,指定的 pid 仍未结束运行,则 wait 立即返回 0;
        WUNTRACED: 当子进程被暂停时,则立即返回子进程的 pid;
        WCONTINUED: 当被暂停的子进程,又被信号恢复时,则立即返回子进程的pid;

而下面这些宏,将搭配status一起使用:

WIFEXITED(status): 当子进程调用 exit、_exit或者正常从 main 返回等正常结束时,返回 true 
    --> WEXITSTATUS(status): 获取上面的 exit_code
        
WIFSIGNALED(status): 当子进程被信号杀死时,返回 true;
    --> WTERMSIG(status): 获取信号的值(int)

WIFSTOPPED(status): 当自己弄成被信号暂停执行时,返回 true;
    --> WSTOPSIG(status): 获取该信号的值

WIFCONTINUED(status): 子进程接收到SIGCONT信号继续执行时,返回 true
最小实践

我们来个最小的 demo 来说明上面的怎么用:

#include<stdio.h>
#include<string.h>
#include<signal.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
int main(){
    int pid;
    if((pid = fork()) == 0){
        while(1){
            printf("Child: %d\n", getpid());
            sleep(1);
        }
    }
    else{
        int status;
        pid_t w;
        while(1){
            // 希望堵塞,所以没用 WNOHANG
            w = waitpid(pid, &status,WCONTINUED | WUNTRACED);
            if(WIFEXITED(status)){
                printf("子进程正常退出,状态码: %d\n", WEXITSTATUS(status));
                exit(0);
            } else if(WIFSIGNALED(status)){
                printf("子进程被信号杀死了! 信号值: %d\n", WTERMSIG(status));
                exit(0);
            } else if(WIFSTOPPED(status)){
                printf("子进程被信号暂停了! 信号值: %d\n", WSTOPSIG(status));
            } else if(WIFCONTINUED(status)){
                printf("子进程又恢复继续运行了\n");
            }
        }
    }
}

终端输出:

Child: 10848
Child: 10848                    # 子进程的 pid

子进程被信号暂停了!信号值:21       # kill -SIGTTIN 10848
子进程又恢复继续运行了                # kill -SIGTTIN 10848
...
子进程被信号暂停了! 信号值: 19      # kill -SIGSTOP 10848
子进程又恢复继续运行了                # kill -SIGTTIN 10848   
...
子进程被信号杀死了! 信号值: 15      # kill -SIGTERM 10848    

如果自己在子进程上面加个退出,就会打印:正常退出了   

结语

在上面的实验中,我们已经发现通过SIGCHLD除了用来回收子进程,还能获悉子进程的状态!

在操作系统上,也有很多利用这个在工作的,例如:后台进程,如果向标准输入读取内容时,是会被暂停的

为什么呢?

因为后台进程,是和终端断开连接的,当它从标准输入读取内容时,终端的驱动程序会发现这个操作,会发一个 SIGTTIN 给后台进程,让其暂停,并且通知用户,只有用户通过 fg 命令将其转换成 前台进程时,才能继续工作

正是有这样的一套机制,所以我们也能做出很多比较实在的东西了~

欢迎各位大神指点交流, QQ讨论群: 258498217

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏欧阳大哥的轮子

Windows窗口消息和消息队列

所有基于事件驱动的操作系统中的GUI程序,都会在主线程中运行一个消息泵来从消息队列中取出消息并执行对应的处理逻辑。消息队列中的消息除了由系统产生外,还提供了对应...

2215
来自专栏葡萄城控件技术团队

Asp.Net MVC4入门指南(10):第三方控件Studio for ASP.NET MVC4 工具应用

ComponentOne Studio for ASP.NET最新版本2013V1支持MVC4,其中包括: 新增 MVC 4 工程模板 (C# & VB) 开箱...

2299
来自专栏程序员的SOD蜜

Erlang语言学习入门

近期研究RabbitMQ,发现它是基于Erlang实现的,于是对Erlang这么语言发生了兴趣,官网地址 http://www.erlang.org/ ,去下载...

27110
来自专栏葡萄城控件技术团队

ASP.NET MVC 5 - 创建连接字符串(Connection String)并使用SQL Server LocalDB

您创建的MovieDBContext类负责处理连接到数据库,并将Movie对象映射到数据库记录的任务中。你可能会问一个问题,如何指定它将连接到数据库? 实际上,...

3118
来自专栏技术博客

Asp.Net Web API 2第十七课——Creating an OData Endpoint in ASP.NET Web API 2(OData终结点)

  很久没更新博客了,加上刚过年,现在准备重新开战,继续自己的学习之路。本文已同步到Web API2系列文章中http://www.cnblogs.com/ae...

892
来自专栏程序你好

ASP.NET Core MVC 概述

1212
来自专栏闻道于事

idea导入或打开项目配置问题

learn项目遇到问题: 1.IntelliJ Idea编译报错:请使用 -source 7 或更高版本以启用 diamond 运算符 file - proje...

2777
来自专栏葡萄城控件技术团队

.Net 高效开发之不可错过的实用工具 工欲善其事,必先利其器,没有好的工具,怎么能高效的开发出高质量的代码呢?本文为各ASP.NET 开发者介绍一些高效实用的工具,涉及SQL 管理,VS插件,内

工欲善其事,必先利其器,没有好的工具,怎么能高效的开发出高质量的代码呢?本文为各ASP.NET 开发者介绍一些高效实用的工具,涉及SQL 管理,VS插件,内存管...

2206
来自专栏ml

ijg库的使用的几点注意

ijg库(http://www.ijg.org/)是用于处理jpeg解码和压缩的库,最新版本为2014发布的版本,可以在官网中下载jpegsr9a.zip 使用...

3415
来自专栏有困难要上,没有困难创造困难也要上!

Pyinstaller ERROR: Assembly amd64_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_none not found

3918

扫码关注云+社区