该程序中涉及 fork() 系统调用的控制流如何?

How is the flow of control in this program involving fork() system call?

据我了解 fork() 系统调用

fork系统调用用于创建一个新进程,称为child进程,与parent进程

并发运行

创建新的child进程后,两个进程将执行fork()系统调用后的下一条指令

fork() returns 0 到 child 进程

fork()returns新建child进程到parent进程的进程ID(正值)

fork() returns 负值如果 child 进程创建失败

在这段代码中

void foo() { 
if (fork() == 0) 
    printf("Hello from Child!\n"); 
else 
    printf("Hello from Parent!\n"); 
} 

int main() { 
    foo(); 
    return 0; 
} 

输出为

Hello from Parent!
Hello from Child!

child进程是在主进程的foo函数的if-else条件下创建的child进程。

那么 child 进程是从哪里(哪条指令)开始执行的?

从输出中可以看出,Hello from Parentfork()returns0时打印出来。所以根据我的理解 Hello from Parent 实际上是由 Child Process

打印的

fork() 向 parent 进程返回一个正值,并且 parent 进程打印 Hello from Child。我对此的理解正确吗?

child 进程是从哪条指令开始执行的? fork() 的函数调用是在 if-else 的条件部分中给出的。所以 child 应该在 if-else 之后开始执行,但事实并非如此?

子进程是并行执行的第二个进程。你可能很容易得到

Hello from Child!
Hello from Parent!

例如,如果您打开终端 window,然后启动 firefox &,它“首先”运行终端 window 或浏览器 window ?两者同时运行

事实上,Linux 在重新启动父进程之前稍稍启动了子进程。这是因为大量调用 fork() 的程序会立即让子程序 exec() 成为一个程序,从而使父程序无需与子程序共享其所有内存。这样效率更高,因为共享内存是copy-on-write.

让我们首先确定这里的一个主要误解:

As it can be observed from the output, Hello from Parent is printed when fork() returns 0. So from my understanding Hello from Parent was actually printed by the Child Process

child 和 parent 是两个独立的进程 运行ning 并发。这两个输出的顺序不是 well-defined,会根据您的内核和其他时序考虑因素而有所不同,并且与您的代码包含按您所写的 if/else 块的事实无关.1

让我们将您的代码重写为抽象意义上的 "instructions" 的线性流:

0: Function foo():
1:  Invoke system call fork(), no arguments, store result to 
2:  If  is non-zero, jump to label #1.
3:  Invoke C function printf(), argument "Hello from Child!"
4:  Jump to label #2.
5: Label #1:
6:  Invoke C function printf(), argument "Hello from Parent!"
7: Label #2:
8: return control to calling function.

一旦您的程序到达 1:,就会调用系统调用,将控制权转移到内核。内核复制进程,将child的PID放入parent进程中fork的return值,将0放入[= child 中 fork 的值 58=]。在 x86 上,return 值作为系统调用约定的一部分存储在寄存器 eax(对于 x64 为 rax)。

这两个进程之一最终将被内核调度到 运行。在您的情况下,child 进程恰好是第一个被安排的。您的 user-mode 代码从内核模式取回控制权,读取为零的 return 值(如果在 x86 上则为 eax/rax),并且没有跳转到标签 #1。它打印 Hello from Child!,然后从函数 returned(到 foo 的调用者,因为 child 得到了 parent 的堆栈副本).

除了 parent 从系统调用返回一个 non-zero 值并打印 Hello from Parent! 外,parent 也发生了同样的情况。它被安排到 运行,并且您的 user-mode 代码在同一时间点从内核获取控制权,只是系统调用使用了不同的值 return。

1 两个输出也有可能以某种方式交错,但这与本次讨论无关,需要了解 Linux 进程如何执行I/O.