fork() 之后,父进程在子进程调用exec() 时终止
我正在尝试编写将子进程托管在伪终端(PTY)中的代码。这并不比简单地重定向标准输入输出复杂多少,但确实有一些细节要处理。根据文档:
- 你调用
openpty,它会找到一个空闲的伪终端,并返回该伪终端双向通讯所用的 "master" 与 "slave" 文件描述符。 - 你执行
fork,以创建子进程。 - 在父进程中,
fork返回子进程的进程ID。父进程随后可以通过从master FD读取来观察该进程的输出,并向它写入以发送输入。 -
在子进程中,
fork返回0。子进程现在有一些特定任务需要执行: -
需要关闭默认的stdin、stdout和 stderr的文件描述符。
dup2用于将PTY的 slave FD插入到STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO中。- 最后,使用一个
exec的变体将子进程替换为目标程序。
.NET的托管环境与这一序列并不完全兼容,因为存在各种管理线程,例如垃圾回收,而 fork 只会克隆调用它的线程。但我一直在基于一个充满希望的假设来编写代码:如果我仅限直接、简单的P/Invoke调用,避免任何封送和异常,那么需要执行的JIT输出将会相当直接,理论上应该工作,直到进程被替换为止。
据我所知,事实确实如此。在我的观察中,在 fork 与 exec 之间对 close 和 dup2 的P/Invoke调用都运作良好。但是,一旦子进程调用 exec(我使用 execvp 变体,参数在 fork 之前就已预先封送好,以防万一),父进程就会死亡。没有看到任何解释;调试器只是显示 The program '[3744333] QBX.dll' has exited with code 0 (0x0).。
我最初的想法是一定有某些信号在发生,父进程以某种方式捕捉到了它们。当我在运行时代码库中阅读文档时,这似乎得到证实:信号是通过一个微小的存根处理程序捕获的,它只是将事件通知写入管道,供其他地方的信号处理线程读取。如果子进程得到管道句柄的拷贝,那么它写入的内容就会被父进程捕获。
运行时对 SystemNative_ForkAndExecProcess 的实现包含一些额外代码,在 fork 之前清除了信号处理程序。奇怪的是,它似乎在 fork 之后、exec 之前又恢复了该配置。无论如何,我严格复制了 SystemNative_ForkAndExecProcess 的做法,但只要子进程执行 exec,父进程就仍然会死亡。如果把子进程中的 exec 调用替换成 Environment.Exit,那么父进程显然没有要交互的子进程,但它也不会退出。
当子进程调用 exec 时,如何避免父进程退出?这应该是可能的,因为框架做到了。
解决方案
这似乎是因为父进程使用了SDL的结果。我创建了一个隔离测试,将SDL从方程中移除,虽然仍然有问题,但父进程不会终止。
简而言之:如果你已经调用了 SDL_Init,不要指望能够顺利完成 fork()。