fork()如何知道何时返回0?

以下面的例子:

int main(void) { pid_t pid; pid = fork(); if (pid == 0) ChildProcess(); else ParentProcess(); } 

所以纠正我,如果我错了,一旦fork()执行一个subprocess创build。 现在通过这个答案 fork()返回两次。 这是一次为父进程,一次为subprocess。

这意味着两个独立的进程在fork调用期间生成,而不是在结束之后生成。

现在我不明白它如何理解如何为subprocess返回0和为父进程正确的PID。

这在哪里变得非常混乱。 这个答案指出fork()通过复制进程的上下文信息并手动将返回值设置为0来工作。

首先我正确地说,返回任何函数都放在一个寄存器中? 由于在单个处理器环境中,一个进程只能调用一个只返回一个值的子例程(如果我在这里错了,请纠正我)。

比方说,我在一个例程中调用一个函数foo(),该函数返回一个值,该值将被存储在一个寄存器中,比如BAR。 每次函数想要返回一个值,它都将使用一个特定的处理器寄存器。 所以,如果我能够手动更改过程块中的返回值,我可以更改返回到该function的值?

所以我正确地认为fork()是如何工作的?

它是如何工作的,在很大程度上是不相关的 – 作为一个开发人员在某个级别上工作(即编码到UNIX API),你只需要知道它的工作原理。

话虽如此,并且认识到好奇心或需要在一定程度上理解是一个很好的特征,但有许多方法可以做到这一点。

首先,一个函数只能返回一个值的争论是正确的,但是您需要记住,在进程拆分之后,实际上有两个函数运行的实例,每个进程中有一个实例。 他们大多是相互独立的,可以遵循不同的代码path。 下图可能有助于理解这一点:

 Process 314159 | Process 271828 -------------- | -------------- runs for a bit | calls fork | | comes into existence returns 271828 | returns 0 

你可以希望在那里看到fork一个实例只能返回一个值(如同任何其他C函数一样),但实际上有多个实例在运行,这就是为什么在文档中返回多个值的原因。


这是一个如何工作的可能性。

fork()函数开始运行时,它存储当前进程ID(PID)。

然后,当它返回时,如果PID与存储的相同,那么它就是父级。 否则,这是孩子。 伪代码如下:

 def fork(): saved_pid = getpid() # Magic here, returns PID of other process or -1 on failure. other_pid = split_proc_into_two(); if other_pid == -1: # fork failed -> return -1 return -1 if saved_pid == getpid(): # pid same, parent -> return child PID return other_pid return 0 # pid changed, child, return zero 

请注意,在split_proc_into_two()调用中有很多魔法,在封面(a)下几乎肯定不会那样工作。 这只是为了说明它的概念,基本上是:

  • 在拆分之前得到原始的PID,在拆分之后两个进程保持相同。
  • 做分裂。
  • 分割后得到当前的PID,这在两个进程中是不同的。

您可能也想看看这个答案 ,它解释了fork/exec哲学。


(a)几乎肯定比我解释的更复杂。 例如,在MINIX中,对fork的调用最终在内核中运行,该内核可以访问整个进程树。

它简单地将父进程结构复制到subprocess的空闲槽中,如下所示:

 sptr = (char *) proc_addr (k1); // parent pointer chld = (char *) proc_addr (k2); // child pointer dptr = chld; bytes = sizeof (struct proc); // bytes to copy while (bytes--) // copy the structure *dptr++ = *sptr++; 

然后对儿童结构进行微小的修改,以确保其适用性,包括:

 chld->p_reg[RET_REG] = 0; // make sure child receives zero 

所以,与我所设想的scheme基本相同,但是使用数据修改而不是代码pathselect来决定返回给调用者的内容 – 换句话说,您会看到如下所示的内容:

 return rpc->p_reg[RET_REG]; 

fork()的末尾,以便返回正确的值,具体取决于它是父进程还是subprocess。

在Linux中fork()发生在内核中; 实际的地方是这里的_do_fork 。 简化的fork()系统调用可能类似于

 pid_t sys_fork() { pid_t child = create_child_copy(); wait_for_child_to_start(); return child; } 

所以在内核中, fork()真的返回一次 ,进入父进程。 但是,内核也会将subprocess创build为父进程的副本; 但不是从普通函数返回,而是为新创build的subprocess的线程合成一个新的内核栈 ; 然后上下文切换到该线程(和进程); 因为新创build的进程从上下文切换函数返回,它将使subprocess的线程以fork()的返回值0返回到用户模式。


基本上,userland中的fork()只是一个简单的包装器,它返回内核放入其堆栈的值/返回寄存器。 内核设置新的subprocess,以便它通过这个机制从其唯一的线程返回0; 并且父系统调用中返回子pid,因为来自任何系统调用(例如read(2)任何其他返回值都将是。

你首先需要知道多任务是如何工作的。 理解所有的细节是没有用的,但是每个进程都运行在某种由内核控制的虚拟机上:一个进程有自己的内存,处理器和寄存器等等。这些虚拟对象映射到真实的对象上(神奇的是在内核中),并且有一些机器可以随着时间的推移将虚拟上下文(进程)交换到物理机器上。

然后,当内核派生一个进程( fork()是内核的入口),并且创build进程中的几乎所有内容到进程的副本时,就可以修改所需的所有内容。 其中之一就是修改相应的结构,以便将当前调用返回给子代的子代和父代中的子代的pid。

注意:nether说“fork返回两次”,函数调用只返回一次。

想一想克隆机器:你一个人进入,但两个人退出,一个是你,另一个是你的克隆(非常不同); 而克隆机器可以设置一个不同于你的克隆名称。

fork系统调用创build一个新进程,并从父进程复制大量的状态。 像文件描述符表的东西被复制,内存映射及其内容等等。这个状态在内核里面。

内核为每个进程logging的内容之一是该进程在系统调用,陷阱,中断或上下文切换(大部分上下文切换发生在系统调用或中断)的返回时需要恢复的寄存器的值。 这些寄存器保存在系统调用/陷阱/中断,然后返回到用户区时恢复。 系统通过写入该状态来调用返回值。 叉子是做什么的 父叉获得一个值,subprocess不同。

由于分叉进程与父进程不同,内核可以做任何事情。 在寄存器中给它任何值,给它任何内存映射。 为了确保几乎除了返回值之外的所有内容都与父进程中的相同,需要付出更多的努力。

对于每个正在运行的进程,内核都有一个寄存器表,在进行上下文切换时加载回来。 fork()是一个系统调用; 一个特殊的调用,当进行时,进程得到上下文切换,执行调用的内核代码在不同的(内核)线程中运行。

系统调用返回的值放置在应用程序在调用之后读取的特殊寄存器(x86中的EAX)中。 当进行fork()调用时,内核会复制进程,并在每个进程描述符的每个寄存器表中写入相应的值:0和pid。