我正在开发一个需要在unix中实现fork()的项目。我阅读了freeBSD和openBSD源代码,但实际上很难理解。有人可以解释回归两次的概念吗?我知道一个返回是一个子进程的pid,并且返回到父进程,而另一个返回到零并且它返回到子进程。但我无法理解如何实现这种两次返回的概念......我怎么能回来两次呢?提前谢谢大家。
答案 0 :(得分:2)
当你调用fork
时,它会返回“两次”,因为fork会生成两个进程,每个进程都会返回。
因此,如果您正在实施fork
,则必须创建第二个流程而不结束第一个流程。然后返回两次行为将自然发生:两个不同进程中的每一个都将继续执行,只有它们返回的值不同(子项给零,父项给出子项的PID)。 / p>
答案 1 :(得分:1)
当你想到一个函数返回时,你会考虑通常的代码流,它从入口点(通常是main
)开始,然后以严格确定和线性的方式逐行执行。 / p>
但是,在实际系统中,可以有多个执行上下文,每个执行上下文都有自己的控制流(新的C ++标准实际上包含了这个概念)。每个单独的进程都是从main
开始的执行上下文,但您也可以从现有进程中创建新的执行上下文(实际上,所有操作系统都必须能够执行此操作!)。 fork
是创建新执行上下文的一种方法,新上下文的入口点是 fork
返回的点。但是,原始上下文也会继续运行,并且会在fork
调用后继续照常运行。新上下文是一个单独的进程,因此fork
在两个上下文中都返回(一次)。
还有其他创建新执行上下文的方法;一种是通过实例化std::thread
对象或使用特定于平台的函数来创建新的线程(在同一进程中);另一个是Linux的clone()
函数,它是Linux中Posix线程实现和fork
的基础(通过为内核的调度程序创建新的执行路径,以及是否复制所有虚拟内存(新进程))(新线程。)
答案 2 :(得分:0)
下面我将尝试解释如何从函数返回两次。 我从一开始就警告你,这一切都是黑客攻击。 但是有很多地方都在使用这些黑客。
首先让我们说我们有以下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()返回两次,以便我们可以同时访问两个printf。 所以这里是saveesp()的实现方式:
#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
这绝不是可移植代码。但是您可以为要支持的所有平台编写类似的程序集存根。
saveesp()的作用是,它将存储在堆栈中的返回地址保存到本地变量中。然后它返回1.这是一个非零返回,它将我们带到第一个printf。
在printf()之后我们调用jmpback()。这是实际的黑客攻击。此函数使得看起来saveesp()第二次返回。
它通过将保存的返回地址按下堆栈并执行ret来完成此操作。 ret将从堆栈中弹出地址并跳转到它。这次返回代码设置为零。因此,当我们“回到”我们的C例程时,看起来我们刚从saveesp()返回,返回值为零。因此到达第二个printf。
如果你对这种黑客感兴趣,你应该阅读更多关于用于实现异常处理的C标准中的setjmp和longjmp。
另外,我们实际上在openBSD内核的suspend / resume代码路径中使用它。 在第231和250行查看here它与上面几乎相同的C代码。然后看看第542行的汇编代码here是savecpu函数,它返回第一次暂停时,第375行是我们返回第二次恢复时的情况。