Take the following example:
下面的例子:
int main(void)
{
pid_t pid;
pid = fork();
if (pid == 0)
ChildProcess();
else
ParentProcess();
}
So correct me if I am wrong, once fork() executes a child process is created. Now going by this answer fork() returns twice. That is once for the parent process and once for the child process.
因此,如果我错了,请纠正我,一旦fork()执行了创建子进程。现在通过这个答案fork()返回两次。这是父进程和子进程一次。
Which means that two separate processes come into existence DURING the fork call and not after it ending.
这意味着在fork调用期间而不是在它结束之后出现了两个独立的进程。
Now I don't get it how it understands how to return 0 for the child process and the correct PID for the parent process.
现在我不知道它如何理解如何为子进程返回0,以及如何为父进程返回正确的PID。
This where it gets really confusing. This answer states that fork() works by copying the context information of the process and manually setting the return value to 0.
这让人很困惑。这个答案说明fork()通过复制流程的上下文信息并手动将返回值设置为0来工作。
First am I right in saying that the return to any function is placed in a single register? Since in a single processor environment a process can call only one subroutine that returns only one value (correct me if I am wrong here).
首先,我说任何函数的返回都放在一个寄存器中是对的吗?因为在单个处理器环境中,进程只能调用一个只返回一个值的子例程(如果我在这里出错,请纠正我)。
Let's say I call a function foo() inside a routine and that function returns a value, that value will be stored in a register say BAR. Each time a function wants to return a value it will use a particular processor register. So if I am able to manually change the return value in the process block I am able to change the value returned to the function right?
假设我在一个例程中调用一个函数foo()这个函数返回一个值,这个值将被存储在一个寄存器中。每当函数要返回一个值时,它都会使用一个特定的处理器寄存器。如果我能手动改变进程块中的返回值我就能改变返回给函数的值,对吧?
So am I correct in thinking that is how fork() works?
那么,我是否正确地认为fork()是如何工作的呢?
5 个解决方案
#1
53
How it works is largely irrelevant - as a developer working at a certain level (ie, coding to the UNIX APIs), you really only need to know that it works.
它如何工作在很大程度上是无关紧要的——作为一个在一定级别上工作的开发人员(例如,为UNIX api编写代码),您只需要知道它是如何工作的。
Having said that however, and recognising that curiosity or a need to understand at some depth is generally a good trait to have, there are any number of ways that this could be done.
然而,说到这一点,并认识到好奇心或对某种深度理解的需要通常是一个好的特质,有很多方法可以做到这一点。
First off, your contention that a function can only return one value is correct as far as it goes but you need to remember that, after the process split, there are actually two instances of the function running, one in each process. They're mostly independent of each other and can follow different code paths. The following diagram may help in understanding this:
首先,您认为一个函数只能返回一个值的观点是正确的,但是您需要记住,在进程分割之后,实际上有两个函数实例在运行,每个进程中有一个。它们彼此独立,可以遵循不同的代码路径。下面的图表可能有助于理解这一点:
Process 314159 | Process 271828
-------------- | --------------
runs for a bit |
calls fork |
| comes into existence
returns 271828 | returns 0
You can hopefully see there that a single instance of fork
can only return one value (as per any other C function) but there are actually multiple instances running, which is why it's said to return multiple values in the documentation.
您可以看到,fork的一个实例只能返回一个值(与任何其他C函数一样),但是实际上有多个实例在运行,这就是为什么它在文档中返回多个值。
Here's one possibility on how it could work.
这是一种可能的方法。
When the fork()
function starts running, it stores the current process ID (PID).
当fork()函数开始运行时,它将存储当前进程ID (PID)。
Then, when it comes time to return, if the PID is the same as that stored, it's the parent. Otherwise it's the child. Pseudo-code follows:
然后,当返回时,如果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
Note that there's a lot of magic in the split_proc_into_two()
call and it almost certainly won't work that way at all under the covers(a). It's just to illustrate the concepts around it, which is basically:
请注意,split_proc_into_two()调用中有很多神奇之处,而且几乎可以肯定,在幕后(a),它根本不会这样工作。只是为了说明它周围的概念,基本上是:
- get the original PID before the split, which will remain identical for both processes after they split.
- 在分割之前获取原始的PID,在分割后这两个进程将保持相同。
- do the split.
- 分裂。
- get the current PID after the split, which will be different in the two processes.
- 得到分割后的当前PID,这两个过程是不同的。
You may also want to take a look at this answer, it explains the fork/exec
philosophy.
你可能也想看看这个答案,它解释了fork/exec哲学。
(a) It's almost certainly more complex than I've explained. For example, in MINIX, the call to fork
ends up running in the kernel, which has access to the entire process tree.
(a)它几乎肯定比我解释的要复杂。例如,在MINIX中,对fork的调用最终在内核中运行,内核可以访问整个进程树。
It simply copies the parent process structure into a free slot for the child, along the lines of:
它简单地将父进程结构复制到子进程的一个空闲槽中,如下所示:
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++;
Then it makes slight modifications to the child structure to ensure it will be suitable, including the line:
然后对子结构进行轻微修改,以确保它是合适的,包括行:
chld->p_reg[RET_REG] = 0; // make sure child receives zero
So, basically identical to the scheme I posited, but using data modifications rather than code path selection to decide what to return to the caller - in other words, you'd see something like:
因此,基本上与我提出的方案相同,但使用数据修改而不是代码路径选择来决定返回给调用者什么——换句话说,您将看到如下内容:
return rpc->p_reg[RET_REG];
at the end of fork()
so that the correct value gets returned depending on whether it's the parent or child process.
在fork()的末尾,根据父进程或子进程返回正确的值。
#2
29
In Linux fork()
happens in kernel; the actual place is the _do_fork
here. Simplified, the fork()
system call could be something like
在Linux中,fork()发生在内核中;实际的位置是_do_fork。简化后,fork()系统调用可能是类似的。
pid_t sys_fork() {
pid_t child = create_child_copy();
wait_for_child_to_start();
return child;
}
So in the kernel, fork()
really returns once, into the parent process. However the kernel also creates the child process as a copy of the parent process; but instead of returning from an ordinary function, it would synthetically create a new kernel stack for the newly created thread of the child process; and then context-switch to that thread (and process); as the newly created process returns from the context switching function, it would make the child process' thread end up returning to user mode with 0 as the return value from fork()
.
所以在内核中,fork()真正返回一次,进入父进程。但是,内核还将子进程创建为父进程的副本;但它不会从普通函数返回,而是为子进程新创建的线程创建一个新的内核堆栈;然后将上下文切换到该线程(和进程);当新创建的进程从上下文切换函数返回时,将使子进程的线程以0作为fork()的返回值返回到用户模式。
Basically fork()
in userland is just a thin wrapper returns the value that the kernel put onto its stack/into return register. The kernel sets up the new child process so that it returns 0 via this mechanism from its only thread; and the child pid is returned in the parent system call as any other return value from any system call such as read(2)
would be.
基本上,userland中的fork()只是一个瘦包装器,它返回内核在其堆栈/返回寄存器上的值。内核设置新的子进程,以便通过这种机制从它唯一的线程返回0;子pid在父系统调用中返回,就像任何系统调用(如read(2))的其他返回值一样。
#3
10
You first need to know how multitasking works. It is not useful to understand all the details, but every process runs in some kind of a virtual machine controlled by the kernel: a process has its own memory, processor and registers, etc. There is mapping of these virtual objects onto the real ones (the magic is in the kernel), and there is some machinery that swap virtual contexts (processes) to physical machine as time pass.
你首先需要知道多任务处理是如何工作的。它不是有用的了解所有的细节,但是每一个过程在某种虚拟机运行控制的内核:一个进程都有其自己的内存,处理器和寄存器,等等。这些虚拟对象的映射到实际的内核(魔法),还有一些机械,交换虚拟环境(过程)物理机器随着时间的过去。
Then, when the kernel forks a process (fork()
is an entry to the kernel), and creates a copy of almost everything in the parent process to the child process, it is able to modify everything needed. One of these is the modification of the corresponding structures to return 0 for the child and the pid of the child in the parent from current call to fork.
然后,当内核分出一个进程(fork()是内核的一个条目),并为子进程创建父进程中几乎所有内容的副本时,它就能够修改所需的所有内容。其中之一是对相应结构的修改,以返回0作为子节点的返回值,以及从当前调用到fork的父进程中子节点的pid。
Note: nether say "fork returns twice", a function call returns only once.
注意:在“fork返回两次”中,函数调用只返回一次。
Just think about a cloning machine: you enter alone, but two persons exit, one is you and the other is your clone (very slightly different); while cloning the machine is able to set a name different than yours to the clone.
想想克隆机器:你独自进入,但两个人离开,一个是你,另一个是你的克隆(非常微小的不同);当克隆机器能够设置一个不同于你的名字的克隆。
#4
8
The fork system call creates a new process and copies a lot of state from the parent process. Things like the file descriptor table gets copied, the memory mappings and their contents, etc. That state is inside the kernel.
fork系统调用创建一个新进程,并从父进程复制许多状态。文件描述符表、内存映射及其内容等都将被复制。该状态在内核中。
One of the things the kernel keeps track for every process are the values of registers this process needs to have restored at the return from a system call, trap, interrupt or context switch (most context switches happen on system calls or interrupts). Those registers are saved on a syscall/trap/interrupt and then restored when returning to userland. System calls return values by writing into that state. Which is what fork does. Parent fork gets one value, child process a different one.
内核始终跟踪每个进程的一个事项是该进程在系统调用、陷阱、中断或上下文切换返回时需要恢复的寄存器的值(大多数上下文切换发生在系统调用或中断上)。这些寄存器保存在一个syscall/trap/中断中,然后在返回userland时恢复。系统通过将值写入该状态来调用返回值。这就是fork所做的。父叉得到一个值,子进程是另一个值。
Since the forked process is different from the parent process, the kernel could do anything to it. Give it any values in registers, give it any memory mappings. To actually make sure that almost everything except the return value is the same as in the parent process requires more effort.
由于分叉进程与父进程不同,内核可以对它做任何事情。给它寄存器中的任何值,给它任何内存映射。要确保除了返回值以外的几乎所有内容都与父进程中的相同,需要付出更多的努力。
#5
2
For each running process, the kernel has a table of registers, to load back when a context switch is made. fork()
is a system call; a special call that, when made, the process gets a context switch and the kernel code executing the call runs in a different (kernel) thread.
对于每个正在运行的进程,内核都有一个寄存器表,以便在进行上下文切换时重新加载。fork()是一个系统调用;当生成一个特殊的调用时,该进程获得一个上下文切换,而执行调用的内核代码运行在一个不同的(内核)线程中。
The value returned by system calls is placed in a special register (EAX in x86) that your application reads after the call. When the fork()
call is made, the kernel makes a copy of the process, and in each table of registers of each process descriptor writes the appropiate value: 0, and the pid.
系统调用返回的值被放置在一个特殊的寄存器(x86中的EAX)中,您的应用程序在调用后读取该寄存器。当执行fork()调用时,内核会复制进程,并且在每个进程描述符的寄存器的每个表中写入appropiate值:0和pid。
#1
53
How it works is largely irrelevant - as a developer working at a certain level (ie, coding to the UNIX APIs), you really only need to know that it works.
它如何工作在很大程度上是无关紧要的——作为一个在一定级别上工作的开发人员(例如,为UNIX api编写代码),您只需要知道它是如何工作的。
Having said that however, and recognising that curiosity or a need to understand at some depth is generally a good trait to have, there are any number of ways that this could be done.
然而,说到这一点,并认识到好奇心或对某种深度理解的需要通常是一个好的特质,有很多方法可以做到这一点。
First off, your contention that a function can only return one value is correct as far as it goes but you need to remember that, after the process split, there are actually two instances of the function running, one in each process. They're mostly independent of each other and can follow different code paths. The following diagram may help in understanding this:
首先,您认为一个函数只能返回一个值的观点是正确的,但是您需要记住,在进程分割之后,实际上有两个函数实例在运行,每个进程中有一个。它们彼此独立,可以遵循不同的代码路径。下面的图表可能有助于理解这一点:
Process 314159 | Process 271828
-------------- | --------------
runs for a bit |
calls fork |
| comes into existence
returns 271828 | returns 0
You can hopefully see there that a single instance of fork
can only return one value (as per any other C function) but there are actually multiple instances running, which is why it's said to return multiple values in the documentation.
您可以看到,fork的一个实例只能返回一个值(与任何其他C函数一样),但是实际上有多个实例在运行,这就是为什么它在文档中返回多个值。
Here's one possibility on how it could work.
这是一种可能的方法。
When the fork()
function starts running, it stores the current process ID (PID).
当fork()函数开始运行时,它将存储当前进程ID (PID)。
Then, when it comes time to return, if the PID is the same as that stored, it's the parent. Otherwise it's the child. Pseudo-code follows:
然后,当返回时,如果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
Note that there's a lot of magic in the split_proc_into_two()
call and it almost certainly won't work that way at all under the covers(a). It's just to illustrate the concepts around it, which is basically:
请注意,split_proc_into_two()调用中有很多神奇之处,而且几乎可以肯定,在幕后(a),它根本不会这样工作。只是为了说明它周围的概念,基本上是:
- get the original PID before the split, which will remain identical for both processes after they split.
- 在分割之前获取原始的PID,在分割后这两个进程将保持相同。
- do the split.
- 分裂。
- get the current PID after the split, which will be different in the two processes.
- 得到分割后的当前PID,这两个过程是不同的。
You may also want to take a look at this answer, it explains the fork/exec
philosophy.
你可能也想看看这个答案,它解释了fork/exec哲学。
(a) It's almost certainly more complex than I've explained. For example, in MINIX, the call to fork
ends up running in the kernel, which has access to the entire process tree.
(a)它几乎肯定比我解释的要复杂。例如,在MINIX中,对fork的调用最终在内核中运行,内核可以访问整个进程树。
It simply copies the parent process structure into a free slot for the child, along the lines of:
它简单地将父进程结构复制到子进程的一个空闲槽中,如下所示:
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++;
Then it makes slight modifications to the child structure to ensure it will be suitable, including the line:
然后对子结构进行轻微修改,以确保它是合适的,包括行:
chld->p_reg[RET_REG] = 0; // make sure child receives zero
So, basically identical to the scheme I posited, but using data modifications rather than code path selection to decide what to return to the caller - in other words, you'd see something like:
因此,基本上与我提出的方案相同,但使用数据修改而不是代码路径选择来决定返回给调用者什么——换句话说,您将看到如下内容:
return rpc->p_reg[RET_REG];
at the end of fork()
so that the correct value gets returned depending on whether it's the parent or child process.
在fork()的末尾,根据父进程或子进程返回正确的值。
#2
29
In Linux fork()
happens in kernel; the actual place is the _do_fork
here. Simplified, the fork()
system call could be something like
在Linux中,fork()发生在内核中;实际的位置是_do_fork。简化后,fork()系统调用可能是类似的。
pid_t sys_fork() {
pid_t child = create_child_copy();
wait_for_child_to_start();
return child;
}
So in the kernel, fork()
really returns once, into the parent process. However the kernel also creates the child process as a copy of the parent process; but instead of returning from an ordinary function, it would synthetically create a new kernel stack for the newly created thread of the child process; and then context-switch to that thread (and process); as the newly created process returns from the context switching function, it would make the child process' thread end up returning to user mode with 0 as the return value from fork()
.
所以在内核中,fork()真正返回一次,进入父进程。但是,内核还将子进程创建为父进程的副本;但它不会从普通函数返回,而是为子进程新创建的线程创建一个新的内核堆栈;然后将上下文切换到该线程(和进程);当新创建的进程从上下文切换函数返回时,将使子进程的线程以0作为fork()的返回值返回到用户模式。
Basically fork()
in userland is just a thin wrapper returns the value that the kernel put onto its stack/into return register. The kernel sets up the new child process so that it returns 0 via this mechanism from its only thread; and the child pid is returned in the parent system call as any other return value from any system call such as read(2)
would be.
基本上,userland中的fork()只是一个瘦包装器,它返回内核在其堆栈/返回寄存器上的值。内核设置新的子进程,以便通过这种机制从它唯一的线程返回0;子pid在父系统调用中返回,就像任何系统调用(如read(2))的其他返回值一样。
#3
10
You first need to know how multitasking works. It is not useful to understand all the details, but every process runs in some kind of a virtual machine controlled by the kernel: a process has its own memory, processor and registers, etc. There is mapping of these virtual objects onto the real ones (the magic is in the kernel), and there is some machinery that swap virtual contexts (processes) to physical machine as time pass.
你首先需要知道多任务处理是如何工作的。它不是有用的了解所有的细节,但是每一个过程在某种虚拟机运行控制的内核:一个进程都有其自己的内存,处理器和寄存器,等等。这些虚拟对象的映射到实际的内核(魔法),还有一些机械,交换虚拟环境(过程)物理机器随着时间的过去。
Then, when the kernel forks a process (fork()
is an entry to the kernel), and creates a copy of almost everything in the parent process to the child process, it is able to modify everything needed. One of these is the modification of the corresponding structures to return 0 for the child and the pid of the child in the parent from current call to fork.
然后,当内核分出一个进程(fork()是内核的一个条目),并为子进程创建父进程中几乎所有内容的副本时,它就能够修改所需的所有内容。其中之一是对相应结构的修改,以返回0作为子节点的返回值,以及从当前调用到fork的父进程中子节点的pid。
Note: nether say "fork returns twice", a function call returns only once.
注意:在“fork返回两次”中,函数调用只返回一次。
Just think about a cloning machine: you enter alone, but two persons exit, one is you and the other is your clone (very slightly different); while cloning the machine is able to set a name different than yours to the clone.
想想克隆机器:你独自进入,但两个人离开,一个是你,另一个是你的克隆(非常微小的不同);当克隆机器能够设置一个不同于你的名字的克隆。
#4
8
The fork system call creates a new process and copies a lot of state from the parent process. Things like the file descriptor table gets copied, the memory mappings and their contents, etc. That state is inside the kernel.
fork系统调用创建一个新进程,并从父进程复制许多状态。文件描述符表、内存映射及其内容等都将被复制。该状态在内核中。
One of the things the kernel keeps track for every process are the values of registers this process needs to have restored at the return from a system call, trap, interrupt or context switch (most context switches happen on system calls or interrupts). Those registers are saved on a syscall/trap/interrupt and then restored when returning to userland. System calls return values by writing into that state. Which is what fork does. Parent fork gets one value, child process a different one.
内核始终跟踪每个进程的一个事项是该进程在系统调用、陷阱、中断或上下文切换返回时需要恢复的寄存器的值(大多数上下文切换发生在系统调用或中断上)。这些寄存器保存在一个syscall/trap/中断中,然后在返回userland时恢复。系统通过将值写入该状态来调用返回值。这就是fork所做的。父叉得到一个值,子进程是另一个值。
Since the forked process is different from the parent process, the kernel could do anything to it. Give it any values in registers, give it any memory mappings. To actually make sure that almost everything except the return value is the same as in the parent process requires more effort.
由于分叉进程与父进程不同,内核可以对它做任何事情。给它寄存器中的任何值,给它任何内存映射。要确保除了返回值以外的几乎所有内容都与父进程中的相同,需要付出更多的努力。
#5
2
For each running process, the kernel has a table of registers, to load back when a context switch is made. fork()
is a system call; a special call that, when made, the process gets a context switch and the kernel code executing the call runs in a different (kernel) thread.
对于每个正在运行的进程,内核都有一个寄存器表,以便在进行上下文切换时重新加载。fork()是一个系统调用;当生成一个特殊的调用时,该进程获得一个上下文切换,而执行调用的内核代码运行在一个不同的(内核)线程中。
The value returned by system calls is placed in a special register (EAX in x86) that your application reads after the call. When the fork()
call is made, the kernel makes a copy of the process, and in each table of registers of each process descriptor writes the appropiate value: 0, and the pid.
系统调用返回的值被放置在一个特殊的寄存器(x86中的EAX)中,您的应用程序在调用后读取该寄存器。当执行fork()调用时,内核会复制进程,并且在每个进程描述符的寄存器的每个表中写入appropiate值:0和pid。