【问题标题】:Implementing posix_spawn on Linux在 Linux 上实现 posix_spawn
【发布时间】:2014-08-14 14:37:24
【问题描述】:

我很想知道是否可以在 Linux 中使用 vfork+exec 的组合来实现 posix_spawn。以一种非常简化的方式(省略大多数可选参数),这可能看起来或多或少像这样:

int my_posix_spawn(pid_t *ppid, char **argv, char **env)
{
    pid_t pid;

    pid = vfork();
    if (pid == -1)
        return errno;

    if (pid == 0)
    {
        /* Child */
        execve(argv[0], argv, env);

        /* If we got here, execve failed. How to communicate this to
         * the parent? */
        _exit(-1);
    }

    /* Parent */
    if (ppid != NULL)
        *ppid = pid;

    return 0;
}

但是我想知道如何处理vfork 成功(因此创建子进程)但exec 调用失败的情况。似乎没有办法将此传达给父级,它只会看到它显然可以成功创建一个子进程(因为它会返回一个有效的 pid)

有什么想法吗?

【问题讨论】:

  • 传达此类错误的方法是退出,状态为 127,如文档中所述。
  • 另一个选项是open argv[0] 并在fork 之前使用fstat 验证它是否可执行,然后是fexecve 它。这将排除许多可能导致execve 失败的情况——尽管还有其他情况需要处理。
  • @n.m.但这是否意味着如果不对孩子调用 waitpid 或等效项就无法做到这一点?
  • @user3553031 这只是可能出错的众多事情之一..
  • 父母无论如何都应该调用waitpid。

标签: c linux posix


【解决方案1】:

正如其他人在 cmets 中指出的那样,posix_spawn 被允许创建一个子进程,该子进程由于执行失败或其他分叉后失败而立即死亡;调用应用程序需要为此做好准备。但当然最好不要这样做。

an answer I wrote 中描述了将 exec 故障传达给父级的一般过程:What can cause exec to fail? What happens next?

然而,不幸的是,在vfork 之后,您需要执行的一些操作是不合法的,因为它具有讨厌的两次返回语义。我过去在an article on ewontfix.com 中讨论过这个话题。制作posix_spawn 以避免复制VM 的解决方案似乎是使用cloneCLONE_VM(可能还有CLONE_VFORK)来获得一个共享内存但不在同一个堆栈上运行的新进程。但是,这仍然需要非常小心,以避免对可能修改父级使用的内存的 libc 函数进行任何调用。我目前的实现在这里:

http://git.musl-libc.org/cgit/musl/tree/src/process/posix_spawn.c?id=v1.1.4

如您所见,它相当复杂。阅读 git 历史可能会了解一些已做出的设计决策。

【讨论】:

  • 非常有见地。这回答了我所有的问题,然后回答了更多问题。
【解决方案2】:

我认为当前的系统调用集没有什么好的方法可以做到这一点。您已经正确地发现了最大的问题——在vfork 之后没有任何可靠的方法来报告故障。其他问题包括设置子状态时的竞争条件,以及 Linux 对获取 closefrom 缺乏兴趣。

几年前,我草拟了一个新的系统级 API 来解决这个问题:关键添加是一个系统调用,我称之为 egg(),它创建一个进程而不给它一个地址空间,并且不继承任何状态从父母。显然,一个egg进程不能执行代码;但是您可以(使用一大堆新的系统调用)设置其所有内核端状态,然后(使用另一个系统调用hatch())将可执行文件加载到其中并设置它。至关重要的是,所有新系统调用都会在 parent 中报告失败。例如,有一个dup_into(pid, to_fd, from_fd) 调用将父文件描述符from_fd 复制到蛋状态进程pid 的文件描述符to_fd;如果失败,则父级获取失败代码。

我从来没有时间将所有这些内容充实到一个连贯的 API 规范中并对其进行编码(而且我也不是内核黑客),但我仍然认为这个概念是有道理的,我很乐意与之合作有人来完成它。

【讨论】:

  • 有什么原因不能像我描述的那样在用户空间中实现这个 API,用某种 IPC 将 dup_into 等调用编组到 CLONE_VM 进程中?
  • @R.. 在我的脑海中,我唯一能想到的可能会使这种方法不可能clone AFAICT 没有办法说“用 no 打开文件描述符启动这个孩子”。但是,它会受到您当前在 musl 中使用的所有问题的影响,然后是一些问题。额外的进程状态和无共享、重置为默认值、然后根据需要调整的方法使一切变得更容易推理。
  • @Zack:“从没有打开的文件描述符开始”与 POSIX 不兼容,它允许实现要求保留某些实现内部的文件描述符以保持一致的行为。但是,如果您不关心这一点,您可以自己关闭它们,但我认为设计的这方面会更好地删除并保留正常的 fd 继承。
  • @R.. 我可以想到“实现内部文件描述符”的用途,但我没有看到任何需要在 execve 上保持打开状态的情况(之后 libc 必须重新初始化反正本身)。基本上,我写过的每个产生子进程的程序都希望能够将继承的文件描述符集列入白名单,而不是列入黑名单。所以,不,我认为设计的“蛋从没有打开的文件描述符开始”方面是一个基本特征。
  • @Zack:你可以在这里查看理由:austingroupbugs.net/view.php?id=149
猜你喜欢
  • 2016-04-24
  • 2017-05-01
  • 2010-11-24
  • 2017-01-23
  • 2021-07-04
  • 1970-01-01
  • 2018-12-28
  • 2020-10-28
  • 1970-01-01
相关资源
最近更新 更多