【问题标题】:How is the flow of control in this program involving fork() system call?该程序中涉及 fork() 系统调用的控制流程如何?
【发布时间】:2019-11-17 06:30:03
【问题描述】:

根据我读到的关于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 Parentfork() 返回0 时打印。所以据我了解Hello from Parent实际上是由子进程打印的

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

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

【问题讨论】:

  • 子进程在调用fork()后立即启动。在您的示例中,子进程还运行 if-else 语句。 Hello from Child! 是由子进程打印的,因为对于子进程,fork() 的返回值为 0。 Hello from Parent! 由父进程打印,因为fork() 返回子进程的 PID id(如果有错误,则返回 -1)。
  • 您可以将 fork 视为一个返回两次的调用,一次在父进程中,一次在作为副本的新子进程中。

标签: c fork system-calls


【解决方案1】:

让我们从这里的主要误解开始:

从输出中可以看出,当 fork() 返回 0 时,会打印来自 Parent 的 Hello。所以据我了解,来自 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。

【讨论】:

  • 不仅输出顺序没有明确定义。由于两个进程都写入标准输出,输出也很可能是交错的。例如Hello Hello from Child\n from Parent\n,我使用\n 来指示(比如说)输出窗口上的新文本行的开始。
  • @Peter 这也是正确的,我将对此添加评论。我最初担心的是,尝试包含 I/O 的详细信息会使答案过于复杂,并分散对 fork 如何操作以及进程在计划运行时如何从系统调用中夺回控制权等更直接相关的概念的注意力。跨度>
【解决方案2】:

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

Hello from Child!
Hello from Parent!

例如,如果您打开了一个终端窗口,并启动firefox &,它“首先”运行,是终端窗口还是浏览器窗口?两者同时运行。

事实上,Linux 在重新启动父进程之前会稍微启动子进程。这是因为大量调用fork() 的程序会立即让子exec() 一个程序,这使父进程无需与子进程共享其所有内存。这更有效,因为共享内存是写时复制的。

【讨论】:

    猜你喜欢
    • 2022-01-16
    • 2021-11-07
    • 2023-04-11
    • 2016-01-13
    • 2012-06-23
    • 1970-01-01
    • 2013-04-01
    • 2011-08-27
    • 2012-04-21
    相关资源
    最近更新 更多