fork()如何返回子进程

问题描述:

我知道fork()为子进程和父进程返回不同的值,但我无法找到有关发生这种情况的信息。子进程如何从fork获得返回值0?关于调用堆栈有什么不同?据我了解,对于父母它是这样的:fork()如何返回子进程

父进程 - 调用fork - > system_call - 调用fork - > fork执行 - 返回 - > system_call - - >父进程。

子进程中会发生什么?

+7

的PID它更通常被称为“父”与“子”进程得到的返回值。人们可能会指责你性别歧视。 – Thomas 2010-04-01 18:20:46

%的人叉

返回值

Upon successful completion, fork() returns a value of 0 to the child 
process and returns the process ID of the child process to the parent 
process. Otherwise, a value of -1 is returned to the parent process, no 
child process is created, and the global variable [errno][1] is set to indi- 
cate the error. 

会发生什么情况是,系统调用fork里面,整个过程是重复的。然后,每个分叉调用返回。现在这些是不同的上下文,所以他们可以返回不同的返回码。

如果你真的想知道它是如何工作在低水平,你总是可以check the source!如果你不习惯阅读内核代码,这段代码有点令人困惑,但内联注释给出了一个很好的提示。

有一个明确的回答你的问题源的最有趣的部分是在fork()的定义本身的尽头 -

if (error == 0) { 
    td->td_retval[0] = p2->p_pid; 
    td->td_retval[1] = 0; 
} 

“TD”显然适用于返回值的列表不同的线程。我不确定这个机制究竟是如何工作的(为什么没有两个单独的“线程”结构)。如果错误(从fork1返回,“实际”分叉函数)为0(无错误),则取第一个(父)线程并将其返回值设置为p2(新进程)的PID。如果它是“第二”线程(在p2中),则将返回值设置为0.

+0

这是sys/kern/kern_fork.c - Linux源代码? – osgx 2014-05-23 02:29:24

+0

@osgx:不,引用的源代码来自FreeBSD。 – jxh 2014-09-09 00:49:46

除了不同的返回值(这就是为什么返回值在那里,以便这两个进程可以说明差异!),这个过程在两侧看起来都是相同的。就儿子进程而言,它将刚刚以与父进程返回的方式一样从system_call返回。

+0

是的,但不同的回报值是如何工作的?一个函数如何返回两个不同的值? – EpsilonVector 2010-04-01 18:26:25

+0

因为支持'fork'的核心内核代码负责处理它 - 所以有两个独立的返回值,因此每个值都可以有不同的值。 – Amber 2010-04-01 20:31:10

系统调用返回两次(除非失败)。

  • 其中一个返回的是在子进程,并且有返回值是0。

  • 返回的其他是在父进程,并且有返回值是非零(无论是如果分支失败,则为负值,或者表示子项的PID为非零值)。

父和子之间的主要区别是:

  • 它们是独立的过程
  • PID的值是不同
  • PPID(父PID)的值是不同

POSIX标准中列出了其他更多不明显的差异。

从某种意义上说,如何真的不是你的问题。操作系统需要达到结果。但是,o/s克隆父进程,创建第二个子进程,它几乎与父进程完全相同,设置的属性必须与正确的新值不同,并且通常将数据页标记为CoW(copy on写)或等价物,这样当一个进程修改一个值时,它会得到一个单独的页面副本,以免与另一个进行干扰。这不像是弃用的(至少对我来说 - 对于POSIX而言是非标准的)vfork()系统调用,即使它在系统上可用,你也应该避开这个系统调用。每个进程在fork()之后继续,就好像函数返回一样 - 所以(如我上面所说的),系统调用返回两次,在两个彼此接近相同的克隆的每个进程中返回一次。

+4

我希望vfork()粉丝男孩能解释他们为什么会投下一票。 – 2010-04-01 19:30:37

+0

很好的解释和最新POSIX标准的良好链接(问题7)! – 2010-04-04 02:59:26

+1

+1以补偿vfork变形金刚迷。 – 2010-11-26 00:25:26

史蒂芬Schlansker的答案是相当不错,但只是添加一些更详细:

每个执行的进程都有一个相关的上下文(因此称为“上下文切换”) - 这个上下文包括,除其他事项外,该过程的代码段(包含机器指令),堆内存,堆栈和寄存器内容。发生上下文切换时,将保存旧进程的上下文,并加载新进程的上下文。

返回值的位置由ABI定义,以允许代码的互操作性。如果我正在为我的x86-64处理器编写ASM代码,并且我调用了C运行时,我知道返回值将显示在RAX寄存器中。

把这两件事情一起,逻辑的结论是,该呼叫到int pid = fork()结果在两个上下文下一个指令在每一个执行是一个移动RAX(从fork调用的返回值)的值成局部变量pid。当然,一次只能在一个CPU上执行一个进程,所以这些“返回”的顺序将由调度器决定。

我会尽量从流程内存布局的角度回答。伙计们,如果有任何错误或不准确的情况,请纠正我。

fork()的是唯一的系统调用的进程创建(除了最开始的过程中0),所以这个问题是进程创建内核实际上发生了什么。有两个与进程相关的内核数据结构,struct proc数组(又称进程表)和struct用户(又名u区)。

要创建新进程,必须正确创建或参数化这两个数据结构。直接的方法是与创建者(或父母的)proc & u区域对齐。大多数数据在父母&孩子(例如,代码段)除了返回寄存器中的值(例如80x86中的EAX),其父为子代pid和子代的值为0.从那时起,您有两个进程(现有的一个&新进程)由调度程序运行,并在调度时,每个将分别返回它们的值。

由于操作了子寄存器中的CPU寄存器,父节点和子节点都返回不同的值。

由task_struct表示的linux内核中的每个进程。 task_struct被封装在位于内核模式堆栈末尾的thread_info结构中(指针)。全局CPU上下文(寄存器)存储在此thread_info结构中。

struct thread_info { 
    struct task_struct *task;  /* main task structure */ 
    struct cpu_context_save cpu_context; /* cpu context */ 
} 

所有fork/clone()系统调用调用内核等价函数do_fork()。

long do_fork(unsigned long clone_flags, 
      unsigned long stack_start, 
      struct pt_regs *regs, 
      unsigned long stack_size, 
      int __user *parent_tidptr, 
      int __user *child_tidptr) 

这里是)执行的序列

do_fork( - > copy_process-> copy_thread() (copy_thread是拱特定功能调用)

copy_thread()份的寄存器值并将返回值更改为0 (如果是手臂)

struct pt_regs *childregs = task_pt_regs(p); 
*childregs = *regs; /* Copy register value from parent process*/ 
childregs->ARM_r0 = 0; /*Change the return value*/ 
thread->cpu_context.sp = (unsigned long)childregs;/*Write back the value to thread info*/ 
thread->cpu_context.pc = (unsigned long)ret_from_fork; 

当孩子被安排时,它执行一个装配例程ret_from_fork(),它将返回零。 父就从do_fork(),这是程序

nr = task_pid_vnr(p); 
return nr;