c - 存在或不存在等待时 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()
,但是父子进程是两个独立的进程,控制权是否必须在子进程之后直接传递给父进程过程结束了?(根本没有控制其他进程,如终端) - 如果是,那么它就像子进程是父进程的一部分(就像从另一个函数调用的函数)。
解决方案
该评论至少具有误导性:
//Control given to terminal & then child process is again picked for processing
“终端”过程并没有真正进入等式。假设您使用终端仿真器与您的程序进行交互,它始终在运行。(如果您使用的是控制台,则没有终端进程。但现在不太可能。)
控制用户界面的过程是您使用的任何外壳。您键入一些命令行,例如
$ ./a.out
shell 会安排你的程序运行。(顺便说一下,shell 是一个没有特殊权限的普通用户程序。你可以自己编写。)
具体来说,外壳:
- 用于
fork
创建子进程。 - 用于
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 发送的任何输出或它启动的进程。(再次,