首页 > 解决方案 > 存在或不存在等待时 fork 系统调用的控制流程

问题描述

在这段代码中(在 linux 上运行):

void child_process()
{
    int count=0;

    for(;count<1000;count++)
    {
        printf("Child Process: %04d\n",count);
    }
    printf("Child's process id: %d\n",getpid());
}

void parent_process()
{
    int count=0;

    for(;count<1000;count++)
    {
        printf("Parent Process: %04d\n",count);
    }
}


int main()
{
    pid_t pid;
    int   status;        

    if((pid = fork()) < 0)
    {
        printf("unable to create child process\n");
        exit(1);
    }
    
    if(pid == 0)
        child_process();
    

    if(pid > 0)
    {
        printf("Return value of wait: %d\n",wait();
        parent_process();
    }

    return 0;
}

如果wait()代码中不存在,则进程之一(子进程或父进程)将完成它的执行,然后将控制权交给 linux 终端,最后剩下的进程(子进程或父进程)将运行。这种情况的输出是:

Parent Process: 0998
Parent Process: 0999
guest@debian:~/c$ Child Process: 0645   //Control given to terminal & then child process is again picked for processing
Child Process: 0646
Child Process: 0647

如果wait()代码中存在这种情况,执行流程应该是什么?

fork()被调用时,必须创建一个包含父进程和子进程的进程树。在上面的代码中,当子进程的处理结束时,父进程通过系统调用被告知子僵尸进程死亡wait(),但是父子进程是两个独立的进程,控制权是否必须在子进程之后直接传递给父进程过程结束了?(根本没有控制其他进程,如终端) - 如果是,那么它就像子进程是父进程的一部分(就像从另一个函数调用的函数)。

标签: coperating-systemforkwait

解决方案


该评论至少具有误导性:

   //Control given to terminal & then child process is again picked for processing

“终端”过程并没有真正进入等式。假设您使用终端仿真器与您的程序进行交互,它始终在运行。(如果您使用的是控制台,则没有终端进程。但现在不太可能。)

控制用户界面的过程是您使用的任何外壳。您键入一些命令行,例如

$ ./a.out

shell 会安排你的程序运行。(顺便说一下,shell 是一个没有特殊权限的普通用户程序。你可以自己编写。)

具体来说,外壳:

  1. 用于fork创建子进程。
  2. 用于waitpid等待该子进程完成。

子进程设置任何必要的重定向,然后使用一些exec系统调用,通常是execve用程序替换自己./a.out,传递execve(或其他)您指定的命令行参数。

而已。

您的程序在./a.out中用于fork创建一个孩子,然后可能等待孩子完成后再终止。一旦你的父进程终止,shellwaitpid()就可以返回,一旦它返回,shell 就会打印一个新的命令提示符。

所以至少有三个相关的进程:shell、你的父进程和你的子进程。在没有同步函数的情况下waitpid(),无法保证排序。因此,当您的父进程调用fork()时,创建的子进程可以立即开始执行。或不。如果它确实立即开始执行,它不一定会抢占您的父进程,假设您的计算机相当现代并且具有多个内核。它们可以同时执行。但这不会持续很长时间,因为您的父进程将立即调用exit或立即调用wait.

当一个进程调用wait(or waitpid) 时,它会被挂起,并在它等待的进程终止时再次变为可运行。但同样没有保证。进程可运行这一事实并不意味着它将立即开始运行。但一般来说,在没有高负载的情况下,操作系统很快就会开始运行它。同样,它可能与另一个进程同时运行,例如您的子进程(如果您的父进程没有等待它完成)。

简而言之,如果您进行了一百万次实验,而您的父母在等待您的孩子,那么您将看到相同的结果一百万次;孩子必须在父母解除暂停之前完成,而您的父母必须在外壳解除暂停之前完成。(如果你的父进程在等待之前打印了一些东西,你会看到不同的结果;父子输出可以是任何顺序,甚至是重叠的。)

另一方面,如果您的父母不等孩子,那么您可能会看到许多结果中的任何一个,并且在一百万次重复中,您可能会看到多个结果(但概率不同)。由于父子之间没有同步,输出可以按任意顺序出现(或交错)。而且由于子进程与 shell 不同步,它的输出可能出现在 shell 提示符之前或之后,或者与 shell 提示符交错。没有保证,除了在你的父母完成之前外壳不会恢复。

请注意,终端仿真器是一个完全独立的进程,可始终运行。它拥有一个模拟终端的伪终端(“pty”)。伪终端是一种管道;在管道的一端是认为它正在与控制台通信的进程,而在另一端是终端仿真器,它解释写入 pty 的任何内容以便在 GUI 中呈现它,并发送任何击键它接收,适当地修改为通过管道返回的字符流。由于终端仿真器永远不会挂起,因此它的执行与您计算机上任何其他处于活动状态的进程交错,它会(或多或少)立即向您显示由您的 shell 发送的任何输出或它启动的进程。(再次,


推荐阅读