C 叉子和返回两次
我正在从事一个需要在unix中实现fork()的项目。我读过freeBSD和openBSD的源代码,但很难理解。有人能解释一下这个概念吗?我知道一个返回是子进程的pid,返回给父进程,另一个返回为零,返回给子进程。但我无法理解如何实现返回两次的想法。。。我怎么能回来两次?提前感谢大家。当您调用C 叉子和返回两次,c,unix,fork,freebsd,openbsd,C,Unix,Fork,Freebsd,Openbsd,我正在从事一个需要在unix中实现fork()的项目。我读过freeBSD和openBSD的源代码,但很难理解。有人能解释一下这个概念吗?我知道一个返回是子进程的pid,返回给父进程,另一个返回为零,返回给子进程。但我无法理解如何实现返回两次的想法。。。我怎么能回来两次?提前感谢大家。当您调用fork时,它会返回“两次”,因为fork生成两个进程,每个进程都返回 因此,如果要实现fork,则必须创建第二个进程而不结束第一个进程。然后,返回两次的行为将自然发生:两个不同的进程中的每一个都将继续执行
fork
时,它会返回“两次”,因为fork生成两个进程,每个进程都返回
因此,如果要实现
fork
,则必须创建第二个进程而不结束第一个进程。然后,返回两次的行为将自然发生:两个不同的进程中的每一个都将继续执行,只是它们返回的值不同(子进程返回零,父进程返回子进程的PID)。当您想到函数返回时,您会想到从入口点开始的常规代码流(通常是main
),然后以严格确定的线性方式逐行执行
然而,在现实世界中,有可能有多个执行上下文,每个执行上下文都有自己的控制流(新的C++标准实际上包含了这个概念)。每个单独的进程是从<代码>主< /COD>开始的执行上下文,但也可以从现有的一个创建一个新的执行上下文。(事实上,所有操作系统都必须能够做到这一点!).
fork
是创建新执行上下文的一种方法,新上下文的入口点是fork
返回的点。但是,原始上下文也会继续运行,并且在调用fork
后会像往常一样继续运行。新上下文是一个单独的进程,因此fork
返回(一次)在这两种情况下
还有其他创建新执行上下文的方法;一种是通过实例化
std::thread
对象或使用特定于平台的函数来创建新线程(在同一进程中);另一种是Linux的clone()
函数,它是Linux中Posix线程实现和fork
的基础(通过为内核的调度程序创建新的执行路径,并复制所有虚拟内存(新进程)或不复制(新线程)。下面我将尝试解释如何从函数返回两次。
我从一开始就警告你,这都是黑客行为。
但是有很多地方使用这种黑客
首先,假设我们有下面的C程序
#include <stdio.h>
uint64_t saved_ret;
int main(int argc, char *argv[])
{
if (saveesp()) {
printf("here! esp = %llX\n", saved_ret);
jmpback();
} else {
printf("there! esp = %llX\n", saved_ret);
}
return 0;
}
这绝不是可移植的代码,但是你可以为你想要支持的所有平台编写类似的程序集存根
saveesp()的作用是,它获取存储在堆栈上的返回地址并将其保存到一个局部变量。之后,它返回1。这是一个非零返回,它将我们带到第一个printf
在printf()之后,我们调用jmpback()。这是实际的hack。此函数使saveesp()再次返回
它通过将保存的返回地址下推堆栈并执行ret来完成此操作。ret将从堆栈中弹出地址并跳转到它。这次返回代码设置为零。因此,当我们“到达”C例程时,似乎我们刚从saveesp()返回,返回值为零。因此,第二个printf被到达
如果您对这类黑客感兴趣,您应该阅读更多关于用于实现异常处理的C标准中的setjmp和longjmp的内容
此外,我们实际上在挂起/恢复代码路径上的OpenBSD内核中使用了它。
看一看第231行和第250行,这与上面的C代码几乎相同。然后看一看汇编代码,第542行是savecpu函数,它在挂起时第一次返回,第375行是我们在恢复时第二次返回的位置。它并不是真正的“返回两次”;它在两个进程中各返回一次。为什么需要实现
fork()
?您所在的系统还没有它吗?
#define _ENTRY(x) \
.text; .globl x; .type x,@function; x:
#define NENTRY(y) _ENTRY(y)
NENTRY(saveesp)
movq (%rsp), %rax
movq %rax, saved_ret
movl $1, %eax
ret
NENTRY(jmpback)
xorq %rax, %rax
pushq saved_ret
ret