等待存在或不存在时fork系统调用的控制流程

Control flow of fork system call when wait is present or not present

在此代码中(运行 在 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()系统调用通知父进程僵尸进程死亡,但是父子进程是两个独立的进程,是否强制子进程结束后控制权直接传递给父进程?(完全没有控制权给其他进程,如终端)-如果是,那么它就像子进程是父进程的一部分(像一个函数)从另一个函数调用)。

此评论至少具有误导性:

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

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

控制用户界面的进程是您正在使用的任何 shell。你输入一些 command-line like

$ ./a.out

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

具体来说,shell:

  1. 使用 fork 创建一个 child 进程。
  2. 使用 waitpid 等待 child 进程完成。

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

就是这样。

您的程序,在 ./a.out 中,使用 fork 创建一个 child,然后可能会等待 child 在终止之前完成。一旦您的 parent 进程终止,shell 的 waitpid() 就可以 return,一旦它 return,shell打印一个新的命令提示符。

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

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

简而言之,如果您进行了一百万次实验,而您的 parent 等待您的 child,那么您将看到同样的结果一百万次; child 必须在 parent 取消挂起之前完成,您的 parent 必须在 shell 取消挂起之前完成。 (如果你的 parent 进程在 before waiting 之前打印了一些东西,你会看到不同的结果;parent 和 child 输出可以是任意顺序,或者甚至重叠。)

另一方面,如果您的 parent 没有等待 child,那么您可能会看到许多结果中的任何一个,并且在一百万次重复中您可能会看到不止一个(但概率不同)。由于 parent 和 child 之间没有同步,输出可能以任一顺序出现(或交错)。由于 child 与 shell 不同步,它的输出可能出现在 shell 的提示之前或之后,或者与 shell 的提示交错。没有任何保证,除了 shell 在您的 parent 完成之前不会恢复。

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