从我读到的关于系统调用的信息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
条件内时,将创建子进程。
那么子进程是从哪里(哪条指令)开始执行的呢?
从输出中可以看出,当fork()
返回0
时,将打印Hello from Parent
。所以据我了解Hello from Parent
实际上是由儿童进程打印的
fork()
向父进程返回一个正值,父进程打印Hello from Child
。我对此的理解正确吗?
子进程究竟是从哪条指令开始执行的?对fork()
的函数调用是在if-else
的条件部分中给出的。所以孩子应该在那if-else
之后开始执行,但这不是正在发生的事情吗?
让我们从这里确定一个主要的误解开始:
从输出中可以看出,当 fork() 返回 0 时,将打印来自父级的 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 上,返回值作为 syscall 调用约定的一部分存储在寄存器eax
(x64 为rax
)中。
这两个进程之一最终将被安排为由内核运行。在您的情况下,子进程恰好是第一个被安排的进程。您的用户模式代码从内核模式收回控制权,读取返回值(如果在 x86 上,则为 eax/rax 之外),该值为零,并且没有跳转到标签 #1。它打印Hello from Child!
,然后从函数返回(返回给foo
的调用者,因为子级得到了父堆栈的副本)。
父级也发生了同样的情况,只是父级从系统调用中返回了一个非零值,并打印了Hello from Parent!
。它被安排运行,并且您的用户模式代码在同一点从内核获得控制权,只是系统调用返回的值不同。
1这两个输出也可能以某种方式交错,但这与本次讨论无关,需要了解 Linux 进程如何执行 I/O。
子进程是并行执行的第二个进程。你可能很容易得到
Hello from Child!
Hello from Parent!
例如,如果您打开了一个终端窗口,并且启动firefox &
,哪个运行"首先",终端窗口还是浏览器窗口? 两者同时运行。
事实上,Linux 在重新处理父进程之前会稍微启动子进程。 这是因为大量调用fork()
的程序会立即让子程序exec()
程序,从而使父程序无需与子程序共享其所有内存。这更有效,因为共享内存是写入时复制的。