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

时间:2019-07-07 14:37:18

标签: c fork system-calls

根据我对fork()系统调用的了解

fork系统调用用于创建一个新进程,称为子进程,该进程与父进程同时运行

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

fork()向子进程返回0

fork()将新创建的子流程的流程ID返回到父流程(正值)

如果子进程创建失败,

fork()返回负值

在这段代码中

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!

当控件处于主进程foo函数的if-else条件内时,将创建子进程。

那么子进程从哪里(哪个指令)开始执行?

从输出中可以看到,Hello from Parent返回fork()时将打印0。因此,据我了解,Hello from Parent实际上是由子进程打印的

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

子进程究竟从哪条指令开始执行的?在fork()的条件部分中给出了对if-else的函数调用。因此孩子应该在if-else之后开始执行,但这不是正在发生的事情吗?

2 个答案:

答案 0 :(得分:1)

子进程是第二个并行执行的进程。您可能同样容易得到

Hello from Child!
Hello from Parent!

例如,如果您打开了终端窗口,并且启动了firefox &(首先运行),那么终端窗口还是浏览器窗口呢?两者同时运行。

实际上,Linux在重新启动父进程之前会先启动子进程。这是因为大量调用fork()的程序立即向子级exec()提供了一个程序,从而使父级无需与子级共享所有内存。这是更有效的,因为共享内存是写时复制的。

答案 1 :(得分:1)

让我们从这里识别一个主要的误解开始:

  

从输出中可以看到,fork()返回0时,会打印出Parent的Hello。因此,根据我的理解,Child Process实际上会打印出Parent的Hello

子进程和父进程是同时运行的两个独立进程。这两个输出的顺序定义不明确,会根据您的内核和其他时序考虑因素而有所不同,并且与您的代码包含按编写时的if / else块这一事实无关。 1

让我们以抽象的意义将您的代码重写为线性的“指令”流:

0: Function foo():
1:  Invoke system call fork(), no arguments, store result to $1
2:  If $1 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:后,将调用系统调用,将控制权转移到内核。内核将复制该进程,将子进程的PID放入父进程的fork的返回值中,并将0放入子进程的fork的返回值中。在x86上,作为系统调用调用约定的一部分,返回值存储在寄存器eax中(对于x64,则为rax)。

这两个进程之一最终将被调度为由内核运行。在您的情况下,子进程恰巧是第一个被调度的进程。您的用户模式代码从内核模式夺回了控制权,读取了返回值(如果在x86上为eax / rax),该返回值为零,并且没有跳转到标签#1。它打印了Hello from Child!,然后从函数返回(返回到foo的调用者,因为孩子得到了父栈的副本)。

父级也发生了同样的情况,只是父级从系统调用返回了非零值并打印了Hello from Parent!。它按计划运行,并且您的用户模式代码同时从内核控制了系统调用返回的值。

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