fork()产生的子进程ppid有时不是父进程pid-Linux中的僵尸进程处理

写完之后,我想到了更严谨的验证方法,在文章尾部,
前面的问题类似于随机出现的问题,找到问题并解决,
后面的验证是将这个随机问题通过sleep()函数复现,佐证前文结论。

概述

父进程若先于子进程结束,子进程变成僵尸进程,
此时linux会将子进程分配给init进程,令其作为新的父进程结束子进程,
可在代码最后加上wait()函数,令父进程等待子进程消亡。

问题描述

在使用fork()函数产生子进程时,有时子进程的ppid是父进程的pid,有时又不是,是什么导致的?

如下图,第二次运行这段程序时,子进程的ppid不再是父进程,但父进程的ppid却始终是终端pid

五次运行结果不同:

ps命令显示终端pid:

ps命令显示pid为1693的进程:

fork()与进程产生相关知识

首先要了解进程是如何产生的,
在linux中,进程直接是父子关系,父进程有子进程的控制权,
在windows中,父子关系被句柄代替,句柄可传递,感兴趣可看这篇:操作系统原理学习笔记(六)进程控制
linux的所有进程,都有一个父进程,
一般认为pid=1的init进程作为优先级最高的进程,并由其创建其他用户进程。
进程的pid为其进程唯一标识符,ppid则是其父进程的pid。

其次,要了解fork()函数的作用,
fork()函数可以拷贝当前进程的数据空间,赋予新生成的子进程,
fork()出的子进程的ppid是父进程的pid。

三个问题

为什么在fork()的子进程的ppid有时不是父进程的pid,而变成了systemd的pid?

systemd在这里的作用是什么?

如何防止这种僵尸进程的出现?

linux对僵尸进程的处理

实际上,出现这种情况,是因为父进程先于子进程结束,
此时,子进程并未终止,父进程结束,子进程就变成了僵尸进程。
僵尸进程是无法关闭的,因为其没有父进程控制,
但linux将僵尸进程分配给init进程,就解决了这个问题,
在ubuntu20.04的linux系统中,使用了systemd来完成部分init进程的工作,
也就出现了僵尸进程被分配给systemd进程的情况了,

systemd在这里的作用,就是结束这个子进程。

如何防止父进程先于子进程终止?
在代码末尾使用wait()函数,
wait()函数,可以让该进程等待,直到所有子进程消亡,才继续运行。

问题参考代码

#include <stdio.h>
#include <unistd.h>
#include <wait.h>

int main(){
    int pid = fork();
    if(pid < 0) {
        printf("error in fork()\n");
    }
    else if(pid == 0){
        printf("child process pid is %d, ppid is %d\n", getpid(), getppid());
    }
    else {
        printf("parent process pid is %d, ppid is %d\n", getpid(), getppid());
        //wait(NULL); //当加入这行代码,父进程就不会先于子进程结束。
    }
    return 0;
}      

wait()效果

将wait()函数加入后,运行10次该程序,不再出现上述问题。


更加严谨的验证:通过sleep()函数

在上方代码中,去除了wait()函数,
并在pid==0的子进程代码块末尾,添加了两行代码:
sleep(1);
printf("\nafter sleep(1), child pid is: %d, ppid is %d\n", getpid(), getppid());

修改后效果如下图

可以看出,图中1、3、4、5四次运行中,
在sleep(1)之前的child ppid都是父进程pid,即此时父进程未先于子进程结束。
而在第2次运行中,
sleep(1)前子进程的ppid不是父进程pid,此时父进程先于子进程结束。
这里就是前文提到的随机出现的情况。

而在使用sleep(1)后,问题可以有效复现了,
可以看出,在使用sleep(1)后,child ppid每次都是1776,而不是父进程pid
1776是前文提到的systemd进程,即init进程。
如果此时加入wait()后,child ppid会显示正确,则表示验证了前文的观点:
父进程可能先于子进程结束,成为僵尸进程的子进程会被分配给init进程,如果在父进程中使用wait()函数,可防止该情况的发生。

可以看出,加上wait()后,
经过sleep(1)的子程序的ppid显示正确。

#include <stdio.h>
#include <unistd.h>
#include <wait.h>

int main(){
    int pid = fork();
    if(pid < 0) {
        printf("error in fork()\n");
    }
    else if(pid == 0){
        printf("child process pid is %d, ppid is %d\n", getpid(), getppid());
        sleep(1);
        printf("\nafter sleep(1), child pid is: %d, ppid is %d\n", getpid(), getppid());
    }
    else {
        printf("parent process pid is %d, ppid is %d\n", getpid(), getppid());
        //wait(NULL); //当加入这行代码,父进程就不会先于子进程结束。
    }
    return 0;
}      

You may also like...

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注