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

Mac*_*333 49 c unix fork process internals

请看以下示例:

int main(void)
{
     pid_t  pid;

     pid = fork();
     if (pid == 0) 
          ChildProcess();
     else 
          ParentProcess();
}
Run Code Online (Sandbox Code Playgroud)

所以纠正我,如果我错了,一旦fork()执行子进程被创建.现在通过这个答案 fork()返回两次.这对于父进程来说是一次,对于子进程则是一次.

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

现在我不明白它如何理解如何为子进程返回0以及为父进程返回正确的PID.

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

首先,我说对任何函数的返回都放在一个寄存器中吗?因为在单个处理器环境中,进程只能调用一个只返回一个值的子例程(如果我错了,请纠正我).

假设我在例程中调用函数foo()并且该函数返回一个值,该值将存储在一个名为BAR的寄存器中.每次函数想要返回一个值时,它将使用特定的处理器寄存器.因此,如果我能够手动更改过程块中的返回值,我可以更改返回到函数的值吗?

所以我认为fork()的工作原理是正确的吗?

pax*_*blo 54

如何它的工作原理基本上是无能为力的-作为一个开发者在一定水平的工作(即,编码到UNIX的API),你真的只需要知道的是它的工作原理.

话虽如此但是,并认识到好奇心或需要在一定的深度理解通常是一个很好的特点有,有任意数量的这种方式可以实现.

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

Process 314159 | Process 271828
-------------- | --------------
runs for a bit |
calls fork     |
               | comes into existence
returns 271828 | returns 0
Run Code Online (Sandbox Code Playgroud)

您可以希望看到单个实例fork只能返回一个值(根据任何其他C函数)但实际上有多个实例在运行,这就是为什么它会在文档中返回多个值.


下面是它如何一种可能性可以工作.

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

然后,当返回时,如果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
Run Code Online (Sandbox Code Playgroud)

请注意,在split_proc_into_two()通话中有很多魔法,它几乎肯定不会在封面(a)下以这种方式工作.它只是为了说明它周围的概念,基本上是:

  • 在拆分之前获取原始PID,这两个进程在拆分后保持相同.
  • 做分裂.
  • 分割后得到当前的PID,这两个过程会有所不同.

您可能还想看一下这个答案,它解释了这个fork/exec理念.


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

它只是将父进程结构复制到子进程的空闲槽中,顺序如下:

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++;
Run Code Online (Sandbox Code Playgroud)

然后它对子结构稍作修改以确保它是合适的,包括以下行:

chld->p_reg[RET_REG] = 0;       // make sure child receives zero
Run Code Online (Sandbox Code Playgroud)

所以,基本上与我提出的方案相同,但是使用数据修改而不是代码路径选择来决定返回给调用者的内容 - 换句话说,你会看到如下内容:

return rpc->p_reg[RET_REG];
Run Code Online (Sandbox Code Playgroud)

在结尾处,fork()以便返回正确的值,具体取决于它是父进程还是子进程.

  • @ralfhtp,完全取决于你对"它"的定义.`fork`的单个实例仅返回*one*值,它现在只有*两个*副本,在子进程和父进程中各有一个. (12认同)
  • @paxdiablo:不,fork只返回一个值.只是在调用fork之后,有两个独立的进程在运行,而fork在每个进程中返回一个不同的值.它实际上没有什么不同,如果你有一个程序运行的两个实例,并且程序中的某些函数调用返回不同的值,具体取决于每个实例的状态. (5认同)
  • 没有足够的JQuery."它的工作原理在很大程度上与你无关".但问题很清楚,OP正在问"如何". (4认同)
  • `fork()`返回父项中子项的`PID`,并在子项中返回'0`,因此它返回**两个**值,一个在父(PID)中,一个在子(0)中(在成功...) (3认同)
  • @ Machina333,是的,就标准描述的C"虚拟设备"环境而言,只能从一个函数返回一个值.分叉操作超出了标准的范围,因此标准仅适用于单个实例. (2认同)
  • 请注意,`setjmp`函数可以在单个进程中多次返回. (2认同)

Ant*_*ala 29

在Linux中fork()发生在内核中; 实际的地方就在_do_fork这里.简化后,fork()系统调用可能就像

pid_t sys_fork() {
    pid_t child = create_child_copy();
    wait_for_child_to_start();
    return child;
}
Run Code Online (Sandbox Code Playgroud)

所以在内核中,fork()真正返回一次,进入父进程.但是,内核还会将子进程创建为父进程的副本; 但它不是从普通函数返回,而是合成地为子进程的新创建的线程创建新的内核堆栈 ; 然后上下文切换到该线程(和进程); 当新创建的进程从上下文切换函数返回时,它将使子进程的线程最终返回到用户模式,其中0为返回值fork().


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

  • @ClickRick没有绝对**不**.这是关于内核堆栈. (4认同)

Jea*_*nès 10

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

然后,当内核分叉进程(fork()是内核的一个条目),并在进程的进程中创建几乎所有内容的副本时,它就能够修改所需的所有内容.其中一个是修改相应的结构,为子节点返回0,父节点的pid从当前调用fork返回.

注意:nether说"fork返回两次",函数调用只返回一次.

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


Art*_*Art 8

fork系统调用创建一个新进程并从父进程复制许多状态.像文件描述符表这样的东西被复制,内存映射和它们的内容等等.这个状态在内核中.

内核跟踪每个进程的一个原因是这个进程需要在系统调用,陷阱,中断或上下文切换返回时恢复的寄存器值(大多数上下文切换发生在系统调用或中断上).这些寄存器保存在系统调用/陷阱/中断中,然后在返回用户空间时恢复.系统调用通过写入该状态返回值.叉子是做什么的.父fork获取一个值,子进程获取另一个值.

由于分叉进程与父进程不同,因此内核可以对其执行任何操作.在寄存器中给它任何值,给它任何内存映射.实际上要确保除了返回值之外的几乎所有内容都与父进程中的相同,这需要更多的努力.