fork() 之后,父进程在子进程调用exec() 时终止

编程语言 2026-07-11

我正在尝试编写将子进程托管在伪终端(PTY)中的代码。这并不比简单地重定向标准输入输出复杂多少,但确实有一些细节要处理。根据文档:

  • 你调用 openpty,它会找到一个空闲的伪终端,并返回该伪终端双向通讯所用的 "master" 与 "slave" 文件描述符。
  • 你执行 fork,以创建子进程。
  • 在父进程中,fork 返回子进程的进程ID。父进程随后可以通过从master FD读取来观察该进程的输出,并向它写入以发送输入。
  • 在子进程中,fork 返回0。子进程现在有一些特定任务需要执行:

  • 需要关闭默认的stdin、stdout和 stderr的文件描述符。

  • dup2 用于将PTY的 slave FD插入到 STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO 中。
  • 最后,使用一个 exec 的变体将子进程替换为目标程序。

.NET的托管环境与这一序列并不完全兼容,因为存在各种管理线程,例如垃圾回收,而 fork 只会克隆调用它的线程。但我一直在基于一个充满希望的假设来编写代码:如果我仅限直接、简单的P/Invoke调用,避免任何封送和异常,那么需要执行的JIT输出将会相当直接,理论上应该工作,直到进程被替换为止。

据我所知,事实确实如此。在我的观察中,在 forkexec 之间对 closedup2 的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()

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章