3

我正在开发一个需要在 unix 中实现 fork() 的项目。我阅读了 freeBSD 和 openBSD 源代码,但它真的很难理解。有人可以解释一下返回两次的概念吗?我知道一个返回是一个孩子的pid,它被返回给父母,另一个是零,它被返回给一个子进程。但是我无法理解如何实现两次返回的概念……我怎样才能返回两次?提前感谢大家。

4

3 回答 3

2

当您调用 时fork,它会返回“两次”,因为分叉会产生两个进程,每个进程都会返回。

因此,如果您正在实施fork,则必须创建第二个流程而不结束第一个流程。然后返回两次行为将自然发生:两个不同进程中的每一个都将继续执行,只是它们返回的不同(孩子给零,而父母给孩子的 PID)。

于 2012-03-04T00:16:54.053 回答
2

当您想到一个函数返回时,您会想到通常的代码流,它从入口点(通常main)开始,然后以严格确定性和线性的方式逐行执行。

然而,在现实世界的系统中,可能有多个执行上下文,每个上下文都有自己的控制流(新的 C++ 标准实际上包含了这个概念)。每个单独的进程都是一个从 开始的执行上下文main,但您也可以从现有的执行上下文中创建一个新的执行上下文(事实上,所有操作系统都必须能够做到这一点!)。fork是创建新执行上下文的一种方式,新上下文的入口点返回点forkfork但是,原始上下文也继续运行,并且在调用后它照常继续运行。新上下文是一个单独的进程,因此fork在两个上下文中都返回(一次)。

还有其他方法可以创建新的执行上下文;一种是通过实例化对象或使用特定于平台的函数来创建一个新线程(在同一进程中) ;std::thread另一个是 Linux 的clone()功能,它是 Posix 线程实现和forkLinux 的基础(通过为内核的调度程序创建新的执行路径,并复制所有虚拟内存(新进程)或不复制(新线程)。

于 2012-03-04T00:27:58.270 回答
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 。

如果您对这类 hack 感兴趣,您应该阅读更多关于用于实现异常处理的 C 标准中的 setjmp 和 longjmp 的信息。

此外,我们实际上在 OpenBSD 内核中的挂起/恢复代码路径上使用它。看一下第 231 和 250 行,它与上面的 C 代码几乎相同。然后看看这里的汇编代码,第 542 行是 savecpu 函数,它在挂起时第一次返回,第 375 行是我们在恢复时第二次返回的地方。

于 2012-08-01T11:47:07.743 回答