Linux进程实践(4) --wait避免僵尸进程

时间:2023-03-08 17:23:45
Linux进程实践(4) --wait避免僵尸进程

Wait的背景

当子进程退出的时候,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止)

子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。

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

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

Wait

pid_t wait(int *status);

当我们用fork启动一个进程时,子进程就有了自己的生命,并将独立地运行。有时,我们需要知道某个子进程是否已经结束了,这样我们可以通过wait安排父进程在子进程结束之后。

status:该参数可以获得你等待子进程的信息

返回值:

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

特征:

1.wait系统调用会使父进程暂停执行,直到它的任意一个(并不是所有的)子进程结束为止。

2.返回的是子进程的PID,它通常是已经结束了的子进程;

3.状态信息允许父进程判定子进程的退出状态,即从子进程的main函数返回的值或子进程中exit/_exit语句的退出码。

4.如果status不是一个空指针,状态信息将被写入它指向的位置

//示例
int main()
{
    pid_t pid = fork();
    if (pid == -1)
        err_exit("fork");
    else if (pid == 0)
    {
        cout << "In Child, pid = " << getpid() << endl;
        sleep(5);
        exit(10);
    }

    int status;
    int returnPid = wait(&status);
    //两个pid的值相同,但是status的值根本不是10
    cout << "In Parent, returnPid = " << returnPid
         << ", status = " << status << endl;
}

Linux进程实践(4) --wait避免僵尸进程

Wait获取status

宏定义

描述

WIFEXITED(status)

WEXITSTATUS(status)

如果子进程正常结束,返回一个非零值

如果WIFEXITED非零,返回子进程退出码

WIFSIGNALED(status)

WTERMSIG(status)

子进程因为捕获信号而终止,返回非零值

如果WIFSIGNALED非零,返回信号代码

WIFSTOPPED(status)

WSTOPSIG(status)

如果子进程被暂停,返回一个非零值

如果WIFSTOPPED非零,返回一个信号代码

//示例
void printStatus(int status)
{
    if (WIFEXITED(status))
    {
        cout << "normal termination, exit status = " << WEXITSTATUS(status) << endl;
    }
    else if (WIFSIGNALED(status))
    {
        cout << "abnormal termination, signal number = " << WTERMSIG(status);
#ifdef WCOREDUMP
        if (WCOREDUMP(status))
            cout << " (core file generated)" << endl;
#else
        cout << endl;
#endif // WCOREDUMP
    }
    else if (WIFSTOPPED(status))
    {
        cout << "child stopped, status number = " << WSTOPSIG(status) << endl;
    }
    else
    {
        cout << "Unknow Stop!" << endl;
    }
}

//测试代码
int main()
{
    pid_t pid = fork();
    if (pid == -1)
        err_exit("fork");
    else if (pid == 0)
        exit(7);

    int status;
    if (wait(&status) == -1)
        err_exit("first wait error");
    printStatus(status);

    pid = fork();
    if (pid == -1)
        err_exit("fork");
    else if (pid == 0)
        abort();

    if (wait(&status) == -1)
        err_exit("first wait error");
    printStatus(status);

    pid = fork();
    if (pid == -1)
        err_exit("fork");
    else if (pid == 0)
        status /= 0;

    if (wait(&status) == -1)
        err_exit("first wait error");
    printStatus(status);

    return 0;
}

查看信号值

Linux进程实践(4) --wait避免僵尸进程

Waitpid

pid_t waitpid(pid_t pid, int *status,int options)

等待某个特定进程的结束

参数:

Pid:The value of pid can be:

<-1    meaning wait for any child process whose process group ID is equal to the absolute value of pid.

 -1      meaning wait for any child process(任一子进程).

0       meaning wait for any child process whose process group ID is equal to that of  the calling process(与调用者进程同在一个组的进程).

>0     meaning wait for the child whose process ID is equal to the value of pid.

status:如果不是空,会把状态信息写到它指向的位置(同wait)

options:允许改变waitpid的行为,最有用的一个选项是WNOHANG,它的作用是防止waitpid把调用者的执行挂起

返回值:

如果成功返回等待子进程的ID,失败返回-1

wait与waitpid的区别:

1.在一个子进程终止前, wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。

2.waitpid并不等待第一个终止的子进程:它有若干个选择项,可以控制它所等待的特定进程。

3.wait函数相当于是waitpid函数的一个特例。

waitpid(-1, &status, 0);

僵尸进程(如果不等待...)

当一个子进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行或者父进程调用了wait才告终止。

进程表中代表子进程的数据项是不会立刻释放的,虽然不再活跃了,可子进程还停留在系统里,因为它的退出码还需要保存起来以备父进程中后续的wait调用使用。它将称为一个“僵尸进程”

如何避免僵尸进程

方法1:调用wait或者waitpid函数查询子进程退出状态。

方法2:如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的(表明父进程忽略SIGCHLD信号,一切让Linux内核管理)。

-利用man手册,减少开发难度,提高开发技能

如:Man 7 signal 查找有什么信号可以使应用程序暂停

Linux进程实践(4) --wait避免僵尸进程