我知道fork()对子进程和父进程的返回方式不同,但我无法找到有关如何发生这种情况的信息。子进程如何从fork接收返回值0?有关调用堆栈的区别是什么?据我了解,对于父母来说,它是这样的:
父进程 - 调用fork - > system_call - 调用fork - > fork执行 - 返回 - > system_call - 返回 - >父进程。
子进程会发生什么?
答案 0 :(得分:23)
%man fork
返回值
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系统调用中,会发生重复的整个过程。然后,每个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。
答案 1 :(得分:7)
fork()
系统调用返回两次(除非失败)。
其中一个回复在子进程中,返回值为0.
另一个返回是在父进程中,并且返回值为非零(如果fork失败则为负数,或者指示子项的PID的非零值)。
父母和孩子之间的主要区别是:
POSIX标准中列出了其他更为模糊的差异。
从某种意义上说, How 确实不是你的问题。操作系统需要达到结果。但是,o / s克隆父进程,使得第二个子进程几乎完全是父进程的副本,设置必须与正确的新值不同的属性,并且通常将数据页标记为CoW(复制到写入)或等效的,以便当一个进程修改一个值时,它获得一个单独的页面副本,以免干扰另一个。这与被弃用的(至少我 - 非标准的POSIX)vfork()
系统调用不同,即使它在您的系统上可用,您也应该避免使用它。每个进程在fork()
之后继续,就好像函数返回一样 - 所以(正如我上面所说的那样),fork()
系统调用返回两次,两个进程中的每一个都有一次,这些进程几乎是彼此相同的克隆
答案 2 :(得分:7)
由于在子环境中操纵CPU寄存器,父和子都返回不同的值。
linux内核中的每个进程由task_struct表示。 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() - &GT; copy_process-&GT; 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()获取返回值,该值是进程的pid
nr = task_pid_vnr(p);
return nr;
答案 3 :(得分:3)
Steven Schlansker的回答非常好,但只是增加了一些细节:
每个执行进程都有一个关联的上下文(因此是“上下文切换”) - 该上下文包括进程的代码段(包含机器指令),其堆内存,堆栈和寄存器内容等。发生上下文切换时,将保存旧进程的上下文,并加载新进程的上下文。
返回值的位置由ABI定义,以允许代码互操作性。如果我正在为我的x86-64处理器编写ASM代码,并且我调用了C运行时,我知道返回值将显示在RAX寄存器中。
将这两个事物放在一起,逻辑结论是对int pid = fork()
的调用导致两个上下文,其中每个中执行的下一条指令是移动RAX值的一个(来自{的返回值) {1}}调用)进入局部变量fork
。当然,在一个cpu上一次只能执行一个进程,因此这些“返回”发生的顺序将由调度程序确定。
答案 4 :(得分:1)
我将尝试从进程内存布局的角度来回答。伙计们,如果有任何错误或不准确,请纠正我。
fork()是进程创建的唯一系统调用(最开始的进程0除外),因此问题实际上是内核中进程创建会发生什么。有两个与process,struct proc数组(aka process table)和struct user(aka u area)相关的内核数据结构。
要创建新流程,必须正确创建或参数化这两个数据结构。直接的方式是与creater(或父母的)proc&amp; amp;你的地区。大多数数据在父母和母亲之间重复。 child(例如,代码段),除了返回寄存器中的值(例如80x86中的EAX),其中父级带有子级的pid,子级为0.从那时起,您有两个进程(现有的一个和一个新的)由调度程序运行,并且在调度时,每个将分别返回它们的值。
答案 5 :(得分:0)
除了不同的返回值之外,这个过程从双方看起来都是相同的(这就是返回值存在的原因,因此这两个过程可以完全区分!)。就子进程而言,它将以与返回父进程相同的方式从system_call返回。