linux系统编程之进程(四):wait/waitpid函数与僵尸进程、fork 2 times

一、僵尸进程

当子进程退出的时候,内核会向父进程发送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.

示例程序:

/*************************************************************************
    > 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 + fork 避免僵尸进程

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

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

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》

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大学生计算机视觉学习DeepLearning

c++ 网络编程(九)TCP/IP LINUX/windows--使用IOCP模型 多线程超详细教程 以及 多线程实现服务端

原文链接:https://www.cnblogs.com/DOMLX/p/9661012.html

672
来自专栏IT笔记

JAVA中CAS原理详解

在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁 锁机制存在以下问题: 在多线程竞争下,加锁、释放锁会导致比较多的上下文切...

3888
来自专栏java一日一条

Java 线程池的理论与实践

前段时间公司里有个项目需要进行重构,目标是提高吞吐量和可用性,在这个过程中对原有的线程模型和处理逻辑进行了修改,发现有很多基础的多线程的知识已经模糊不清,如底层...

1005
来自专栏技术专栏

SpringMVC添加异步请求支持

注意web.xml应用需在所有的servlet和filter配置加上<async-supported>true</async-supported>

781
来自专栏熊二哥

快速入门系列--CLR--02多线程

最近,由于基础框架的整体升级,因此需要更新所有相关项目的DLL文件。这个过程存在不小的风险,因此也对发布后的生产服务器进行了密切的监控,结果还是出现了个别应用出...

1659
来自专栏orientlu

UNIX IPC

管道一般为有亲缘关系进程提供单路数据流, 通过pipe(int fd[2])创建, 返回两个文件描述符, fd[0] 用于读,fd[1]用于写。 通过 read...

942
来自专栏Java学习网

Java中使用线程时,请不要忘记Spring TaskExecutor组件

当我们实现的web应用程序需要长时间运行一个任务时,Spring TaskExecutor管理组件是一个很好选择,会给我们代码的实现提供很大的方便,也会节省时间...

3185
来自专栏JetpropelledSnake

Python Web学习笔记之多线程编程

本次给大家介绍Python的多线程编程,标题如下: Python多线程简介 Python多线程之threading模块 Python多线程之Lock线程锁 Py...

2948
来自专栏欧阳大哥的轮子

Windows窗口消息和消息队列

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

855
来自专栏Java3y

多线程三分钟就可以入个门了!

22911

扫码关注云+社区