一、僵尸进程

当子进程退出的时候,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止)
子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。

A child that terminates, but has not been waited for becomes a "zombie".  The kernel maintains a 
minimal set of information  about the  zombie  process (PID, termination status, resource usage 
information) in order to allow the parent to later perform a wait to obtain information about the 
child.  As long as a zombie is not removed from the system via a wait, it will consume a slot in 
 
the kernel  process  table,  and if this table fills, it will not be possible to create further 
processes.  If a parent process terminates, then its "zombie" children (if any) are adopted by 
init(8), which automatically performs a wait to remove the zombies.

父进程查询子进程的退出状态可以用wait/waitpid函数。

二、如何避免僵尸进程

当一个子进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行或者父进程调用了wait/waitpid才告终止。
进程表中代表子进程的数据项是不会立刻释放的,虽然不再活跃了,可子进程还停留在系统里,因为它的退出码还需要保存起来以备父进程中后续的wait/waitpid调用使用。它将称为一个“僵进程”。

调用wait或者waitpid函数查询子进程退出状态,此方法父进程会被挂起(waitpid可以设置不挂起)。
如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。也可以不忽略SIGCHLD信号,而接收在信号处理函数中调用wait/waitpid。

// 让子进程退出后自动回收,避免成为僵尸或者需要父进程 wait。
struct sigaction sat_cld = { .sa_handler = SIG_IGN, .sa_flags = SA_NOCLDWAIT };
sigaction(SIGCHLD, &sat_cld, NULL);

而在运维中常用的手段是杀死父进程,这样子进程会由init 进程接管,由它来清理子进程的状态。

三、wait函数

头文件<sys/types.h>和<sys/wait.h>
函数功能:当我们用fork启动一个进程时,子进程就有了自己的生命,并将独立地运行。有时,我们需要知道某个子进程是否已经结束了,我们可以通过wait安排父进程在子进程结束之后。
函数原型
pid_t wait(int *status)
函数参数
status:该参数可以获得你等待子进程的信息
返回值:
成功等待子进程,  函数返回等待子进程的ID

wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。
返回的是子进程的PID,它通常是结束的子进程
状态信息允许父进程判定子进程的退出状态,即从子进程的main函数返回的值或子进程中exit语句的退出码。
如果status不是一个空指针,状态信息将被写入它指向的位置

通过以下的宏定义可以获得子进程的退出状态

WIFEXITED(status)
如果子进程正常结束,返回一个非零值
WEXITSTATUS(status) 如果WIFEXITED非零,返回子进程退出码
WIFSIGNALED(status) 子进程因为捕获信号而终止,返回非零值
WTERMSIG(status) 如果WIFSIGNALED非零,返回信号代码
WIFSTOPPED(status) 如果子进程被暂停,返回一个非零值
WSTOPSIG(status) 如果WIFSTOPPED非零,返回一个信号代码

四、waitpid函数

函数功能:用来等待某个特定进程的结束

函数原型:
pid_t waitpid(pid_t pid, int *status,int options)
 参数:
status:如果不是空,会把状态信息写到它指向的位置
options:允许改变waitpid的行为,最有用的一个选项是WNOHANG,它的作用是防止waitpid把调用者的执行挂起等待(return immediately if no child has exited.)
返回值:如果成功, 返回等待子进程的ID,失败返回-1

对于waitpid的p i d参数的解释与其值有关:
pid == -1 等待任一子进程。于是在这一功能方面waitpid与wait等效。
pid > 0 等待其进程I D与p i d相等的子进程。
pid == 0 等待其组I D等于调用进程的组I D的任一子进程。换句话说是与调用者进程同在一个组的进程。
pid < -1 等待其组I D等于p i d的绝对值的任一子进程。

五、wait和waitpid函数的区别

两个函数都用于等待进程的状态变化,包括正常退出,被信号异常终止,被信号暂停,被信号唤醒继续执行等。

在一个子进程终止前, wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。
waitpid并不只能等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。
实际上wait函数是waitpid函数的一个特例。

RETURN VALUE

wait(): on success, returns the process ID of the terminated child; on error, -1 is returned.

waitpid(): on success, returns the process ID of the child whose
state has changed; if WNOHANG  was  specified  and  one  or  more
       child(ren) specified by pid exist, but have not yet changed state, then 0 is returned.  On error, -1 is returned.

示例程序:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
 
/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main(int argc, char *argv[])
{
    pid_t pid;
    pid = fork();
    if (pid == -1)
        ERR_EXIT("fork error");

if (pid == 0)
    {
        sleep(3);
        printf("this is child\n");
        //      exit(100);
        abort();
    }

printf("this is parent\n");
    int status;
    int ret;
    ret = wait(&status); // 阻塞等待子进程退出
    //  ret = waitpid(-1, &status, 0);
    //  ret = waitpid(pid, &status, 0);
    /* waitpid可以等待特定的进程,而不仅仅是第一个退出的子进程
     * 且可以设置option为WNOHANG,即不阻塞等待 */
    printf("ret=%d, pid=%d\n", ret, pid);
    if (WIFEXITED(status))
        printf("child exited normal exit status=%d\n", WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
        printf("child exited abnormal signal number=%d\n", WTERMSIG(status));
    else if (WIFSTOPPED(status))
        printf("child stopped signal number=%d\n", WSTOPSIG(status));

return 0;
}

输出为:

simba@ubuntu:~/Documents/code/linux_programming/APUE/process$ ./wait 
this is parent
this is child
ret=7156, pid=7156
child exited abnormal signal number=6

说明子进程被信号异常终止,因为我们调用了abort(), 即产生SIGABRT信号将子进程终止,可以查到此信号序号为6。如果我们不使用abort 而是exit(100), 则应该输出 child exited normal exit status=100  ,即正常退出。

也就是所谓两次 fork 调用,主进程并不直接创建目标子进程,而是通过创建一个 Son,然后再由Son 创建实际的目标子进程 Grandson。Son 在创建

Grandson 后立即返回,并由主进程 waitpid回收掉。而真正的目标 Grandson 则因为 "生父" Son 死掉而被 init 收养,然后直接被人道毁灭。

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 
void create_child()
{
    pid_t son = fork();
    if (son == 0)
    {
        pid_t grandson = fork();
        if (grandson == 0)
        {
            printf("child: %d, parent: %d\n", getpid(), getppid());
            exit(EXIT_SUCCESS);
        }
        exit(EXIT_SUCCESS);
    }
    else if (son > 0)
    {
        waitpid(son, NULL, 0);
    }
    else
    {
        perror("fork");
    }
}

int main(int argc, char *argv[])
{
    for (int i = 0; i < 10; i++)
    {
        create_child();
    }
    while(true) pause();
    return EXIT_SUCCESS;
}

参考:《APUE》

wait/waitpid函数与僵尸进程、fork 2 times的更多相关文章

  1. linux 中的进程wait()和waitpid函数,僵尸进程详解,以及利用这两个函数解决进程同步问题

    转载自:http://blog.sina.com.cn/s/blog_7776b9d3010144f9.html 在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / wait ...

  2. wait函数与waitpid函数(僵尸进程)

    当子进程退出时,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止) 子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程.它只保留最小的一些 ...

  3. Linux进程理解与实践(四)wait函数处理僵尸进程

    Wait的背景 当子进程退出的时候,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止) 子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程 ...

  4. C/C++网络编程8——多进程服务器端之销毁僵尸进程

    上一节提到,当子进程执行结束,父进程还在执行,在父进程结束之前子进程会成为僵尸进程,那么怎么销毁僵尸进程呢?父进程主动接收子进程的返回值. 销毁僵尸进程的方法: 1:使用wait函数 2:使用wait ...

  5. linux系统编程之进程(三):进程复制fork,孤儿进程,僵尸进程

    本节目标: 复制进程映像 fork系统调用 孤儿进程.僵尸进程 写时复制 一,进程复制(或产生)      使用fork函数得到的子进程从父进程的继承了整个进程的地址空间,包括:进程上下文.进程堆栈. ...

  6. 二十三、Linux 进程与信号---进程链和进程扇、守护进程和孤儿进程以及僵尸进程

    23.1 进程链和进程扇 23.1.1 概念 进程链:一个父进程构建出一个子进程,子进程再构建出子子进程,子子进程构建出子子子进程.... 这种就为进程链 进程扇:一个父进程构建出多个子进程,子进程都 ...

  7. OS之进程管理---孤儿进程和僵尸进程

    僵尸进程 当一个进程终止时,操作系统会释放其资源,不过它位于进程表中的条目还是在的,直到它的父进程调用wait():这是因为进程表中包含了进程的退出状态.当进程已经终止,但是其父进尚未调用wait() ...

  8. python学习笔记—— 多进程中的 孤儿进程和僵尸进程

    1 基本概述 1.1 孤儿进程和僵尸进程 父进程创建子进程后,较为理想状态是子进程结束,父进程回收子进程并释放子进程占有的资源:而实际上,父子进程是异步过程,两者谁先结束是无顺的,一般可以通过父进程调 ...

  9. UNIX高级环境编程(9)进程控制(Process Control)- fork,vfork,僵尸进程,wait和waitpid

    本章包含内容有: 创建新进程 程序执行(program execution) 进程终止(process termination) 进程的各种ID   1 进程标识符(Process Identifie ...

随机推荐

  1. jQuery中开发插件的两种方式(附Demo)

    做web开发的基本上都会用到jQuery,jQuery插件开发两种方式:一种是类扩展的方式开发插件,jQuery添加新的全局函数(jQuery的全局函数是属于jQuery命名空间的函数),如果将jQu ...

  2. 使用泛型集合取代datatable作为返回值实现面向对象

    开会的时候,师父说.我们在机房重构时,尽量不要用datatable作为返回值.改用泛型集合的方式,这样能够实现真正的面向对象. 通过查资料和同学交流,把这个问题给攻克了. 对于泛型集合.我也有了一些认 ...

  3. 网络结构设计——负载均衡之LVS学习笔记(四)

    LVS按个人理解的说就是将一台Linux服务器当作路由器等功能的技术.LVS---Linux虚拟服务器. LVS实现了三种IP负载均衡技术VS/NAT.VS/TUN.VS/DR. 今天简单分享一下我在 ...

  4. rcp(插件开发) The 'Eclipse-LazyStart' header is deprecated, use 'Bundle-ActivationPolicy'

    'Eclipse-LazyStart' 这个规范在3.4    osgi 4.1 以后就不支持了 大家使用Bundle-ActivationPolicy: lazy 这个属性来实现插件的懒加载吧!

  5. Learning How To Code Neural Networks

    原文:https://medium.com/learning-new-stuff/how-to-learn-neural-networks-758b78f2736e#.ly5wpz44d This i ...

  6. 【Python】理想论坛帖子读取爬虫1.04版

    1.01-1.03版本都有多线程争抢DB的问题,线程数一多问题就严重了. 这个版本把各线程要添加数据的SQL放到数组里,等最后一次性完成,这样就好些了.但乱码问题和未全部完成即退出现象还在,而且速度上 ...

  7. 在Lotus Notes设置邮件转发

    Notes里面设置邮件转发,一种是创建一个Agent代理,但这种方式有弊端,就是邮件标题缺失,这个比较别扭.这里就不推荐了. 另一种方法是创建Rule规则,这种方式完美.具体方法如下: 1.点Tool ...

  8. nginx学习笔记(四)-----日志切割脚本及定时任务

    一.日志切割脚本 #!/bin/sh #nginx目录 BASE_DIR=/usr/local/nginx #生成的日志 BASE_FILE_NAME=jonychen.access.log CURR ...

  9. TotalCommander使用方法,如何对图片批量重命名

    1 文件或文件夹重命名 F2 2 计算所有文件夹的大小 A/t+Shift+Enter.(这样对于文件的更新操作就更加快捷有效了,比如我的文档里面只有若干个子文件夹有更新,则别的都不用动,只要修改那些 ...

  10. Opencv2.4.9安装和在visualstudio 2013中配置

    Opencv2.4.9安装和在visualstudio 2013中配置 下载opencv和在windows下安装: 最新版本号的opencv是2014.4.25的opencv2.4.9,这里选择当前最 ...