You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何vfork()后关闭同个文件描述符不会触发错误?

关于vfork()+exec()与文件描述符、FILE指针的问题

我编写了如下代码片段:打开文件filename.txt,通过vfork()+exec()创建子进程,在子进程中尝试用grep查找文件内容。

FILE *file = fopen("filename.txt", "r");

int pipefds[2];
pipe(pipefds);

pid_t pid = vfork();
if (pid == 0) {
  // Child process
  close(pipefds[0]); // child doesn't need read end of the pipe

  dup2(fileno(file), STDIN_FILENO);
  pclose(file);
  
  dup2(pipefds[1], STDOUT_FILENO);
  close(pipefds[1]);
  
  execlp("grep", "grep", "pattern", NULL);
}

if (close(pipefds[1]) == -1) { // Why is this successful
  perror("close");
  return 1;
}
char buffer[1024];

// Why I can read from pipefds[0] if I closed it before exec() function
while (read(pipefds[0], buffer, sizeof(buffer) - 1) > 0) {
  std::cout << buffer << std::endl;
}

pclose(file); // Segmentation Fault here, as expected

我的疑问:
由于vfork()到execlp()期间子进程与父进程共享地址空间,子进程调用close(pipefds[1])理应修改父进程的文件描述符,但父进程在exec()后调用close(pipefds[1])并未报错,而调用pclose(file)却触发了预期的段错误。此外,为何子进程在exec()前关闭pipefds[0]后,父进程仍能从pipefds[0]读取数据?


核心前提:区分地址空间与文件描述符表

vfork()确实让子进程共享父进程的用户态地址空间,但文件描述符是进程级的独立资源——每个进程有自己专属的文件描述符表,地址空间共享不代表这个表会被共享。这是所有问题的关键。

逐个解答疑问

1. 父进程close(pipefds[1])为何成功?

子进程里调用的close(pipefds[1])操作的是子进程自己的文件描述符表,和父进程的表完全无关。父进程的pipefds[1]从pipe创建后就一直处于有效状态,直到父进程自身调用close,所以这个操作自然不会报错。

2. 子进程close(pipefds[0])后,父进程还能读取pipefds[0]?

同样的逻辑:子进程关闭的是自己持有的管道读端描述符,父进程的管道读端(pipefds[0])完全不受影响。管道的可用性取决于当前有多少进程持有对应端的文件描述符——只要父进程还持有pipefds[0],就能正常读取子进程中grep写入管道的数据。

3. pclose(file)触发段错误的原因

FILE*是用户态的结构体,存储在进程的地址空间中。子进程在vfork共享地址空间期间调用pclose(file),会直接销毁或置空这个结构体。父进程后续再使用已经被破坏的FILE*调用pclose,必然触发段错误——因为地址空间共享,子进程对结构体的修改会直接作用于父进程的内存。


内容的提问来源于stack exchange,提问作者Vahe Margaryan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 12:15:20