繁体   English   中英

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

[英]How does fork() know when to return 0?

请看以下示例:

int main(void)
{
     pid_t  pid;

     pid = fork();
     if (pid == 0) 
          ChildProcess();
     else 
          ParentProcess();
}

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

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

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

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

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

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

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

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

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

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

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

您可以希望看到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

请注意, 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++;

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

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

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

return rpc->p_reg[RET_REG];

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

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

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

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


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

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

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

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

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

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

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

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

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

系统调用返回的值放在应用程序在调用后读取的特殊寄存器(x86中的EAX)中。 当调用fork() ,内核会生成进程的副本,并在每个进程描述符的每个寄存器表中写入适当的值:0和pid。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM