C 在Linux上实现posix_spawn
我很好奇,是否可以在Linux中使用C 在Linux上实现posix_spawn,c,linux,posix,C,Linux,Posix,我很好奇,是否可以在Linux中使用vfork+exec的组合来实现。以一种非常简单的方式(不考虑大多数可选参数),这看起来或多或少像这样: 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 */
vfork
+exec
的组合来实现。以一种非常简单的方式(不考虑大多数可选参数),这看起来或多或少像这样:
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)
有什么想法吗?正如其他人在评论中指出的那样,
posix_spawn
被允许创建一个子进程,该子进程由于exec失败或其他post fork失败而立即死亡;调用应用程序需要为此做好准备。但当然最好不要这样做
有关此问题的中描述了将exec故障传达给父级的一般过程:
然而,不幸的是,您需要执行的一些操作在vfork
之后是不合法的,因为它的返回非常糟糕。我在过去的几年中已经讨论过这个话题。创建posix\u spawn
以避免复制虚拟机的解决方案似乎是将clone
与clone\u VM
(可能还有clone\u VFORK
)一起使用,以获得共享内存但不在同一堆栈上运行的新进程。但是,这仍然需要非常小心,以避免调用可能修改父级使用的内存的libc函数。我目前的实施情况如下:
正如你所看到的,这相当复杂。阅读git历史记录可能有助于了解所做的一些设计决策。我认为对于当前的一组系统调用,没有什么好方法可以做到这一点。您已经正确地确定了最大的问题——在
vWork
之后,没有任何可靠的方法来报告故障。其他问题包括设置子状态时的竞争条件,以及Linux对选择的兴趣不足
几年前,我草拟了一个新的系统级API来解决这个问题:关键的补充是一个系统调用,我称之为egg()
,它创建一个进程而不给它一个地址空间,也不从父进程继承任何状态。显然,egg进程不能执行代码;但是您可以(通过一大堆新的系统调用)设置它的所有内核侧状态,然后(通过另一个系统调用,hatch()
)将一个可执行文件加载到其中并设置它。至关重要的是,所有新的系统调用都在父系统中报告失败。例如,有一个dup\u-into(pid,to\u-fd,from\u-fd)
调用,它将父文件描述符从\u-fd
复制到鸡蛋状态进程pid
的文件描述符到\u-fd
;如果失败,父级将获取失败代码
我从来没有时间将所有这些内容充实到一个连贯的API规范中并编写代码(而且我也不是内核黑客)但是我仍然认为这个概念是有道理的,我很乐意与人合作来完成它。传达这种错误的方法是按照文档中指定的状态127退出。另一个选项是
打开argv[0]
,并在fork
之前使用fstat
验证它是否可执行,然后fexecve
it。这将排除许多可能导致execve
失败的情况——尽管还有其他情况需要处理。@n.m.但这是否意味着,如果不在孩子身上调用waitpid或等效函数,就无法做到这一点?@user3553031这只是许多可能出错的情况之一。。家长应该这样做不管怎样,打电话给waitpid,非常有洞察力。这回答了我所有的问题,还有更多的问题。是否有任何原因导致这个API不能像我描述的那样在用户空间中实现,将dup\u编组到等中。使用某种IPC调用到CLONE\u VM
进程中?@R。。在我脑海中,我能想到的唯一一件事可能会使这种方法变得不可能,那就是clone
AFAICT没有办法说“在没有打开的文件描述符的情况下启动这个孩子”。但是,它会受到您当前在musl中使用的方法的所有问题的影响,然后是一些问题。附加的进程状态和“不共享任何内容”,重置为默认值,然后根据需要进行调整,这使得一切都更容易推理。@Zack:“开始时没有打开的文件描述符”与POSIX不兼容,这允许实现要求保留某些实现内部文件描述符,以保持一致性行为。不过,如果您不关心这一点,您可以自己关闭它们,但我认为最好删除设计的这一方面,并保留正常的fd继承。@R。。我可以想到“实现内部文件描述符”的用法,但我看不到任何需要在execve
中保持打开状态的场景(之后libc必须重新初始化自身)。基本上,我编写的每个生成子进程的程序都希望能够将继承的文件描述符集列入白名单,而不是黑名单。所以,不,我认为“鸡蛋从没有打开的文件描述符开始”是设计的一个基本特征。@ ZACK:你可以在这里查看基本原理: