返回

vfork() 后为何关闭相同文件符不会产生错误?

Linux

在 vfork() 后关闭相同文件符不会导致错误的原因

简介

vfork() 是一个系统调用,它创建一个新的进程,这个进程与父进程共享相同的地址空间。这意味着,在 vfork() 调用之后,父进程和子进程都可以访问相同的内存。

问题

给定下面的代码片段:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
  int pipefds[2];
  int file = open("test.txt", O_RDWR);

  if (pipe(pipefds) == -1) {
    perror("pipe");
    exit(EXIT_FAILURE);
  }

  pid_t pid = vfork();
  if (pid == -1) {
    perror("vfork");
    exit(EXIT_FAILURE);
  }

  if (pid == 0) {
    // 子进程
    close(pipefds[1]);
    close(file);  // 这不会导致错误
    execlp("grep", "grep", "test", NULL);
    exit(EXIT_FAILURE);  // execlp 失败后不会执行到此行
  } else {
    // 父进程
    close(pipefds[0]);
    close(file);  // 这会导致错误
  }

  return 0;
}

在上面的代码中,我们创建了一个管道 pipefds 和一个文件符 file 。然后,我们使用 vfork() 创建一个子进程,在子进程中关闭 pipefds[1]file 文件描述符。我们观察到,在子进程中关闭 pipefds[1] 不会导致错误,而关闭 file 会导致错误。为什么会出现这种情况?

原因

原因在于 vfork()exec() 函数之间的区别。

vfork() 创建一个子进程,但它不会复制父进程的地址空间。子进程与父进程共享相同的内存空间,直到子进程调用 exec()

exec() 替换子进程的地址空间,使用新程序的代码和数据。这会断开子进程与父进程的内存连接。

因此,在子进程中关闭 pipefds[1] 不会影响父进程的文件描述符,因为在子进程调用 exec() 之前,它们共享相同的地址空间。但是,关闭 file 会导致错误,因为 exec() 之后子进程的地址空间会发生变化,父进程就不再有对 file 的引用了。

pipefds[0] 的读取

即使 pipefds[0] 已在子进程中关闭,你仍然可以从 pipefds[0] 中读取,因为父进程仍然保留对 pipefds[0] 的文件描述符。这是因为 pipefds[0]vfork() 之后没有被关闭,并且在子进程调用 exec() 之前,它与父进程共享相同的地址空间。因此,父进程仍然可以访问 pipefds[0]

结论

vfork()exec() 之间,子进程和父进程共享相同的地址空间。因此,子进程关闭文件描述符不会影响父进程的文件描述符,除非子进程关闭的是父进程不再需要的描述符(如 pipefds[1] )。然而,在子进程调用 exec() 之后,子进程的地址空间会发生变化,父进程不再有对子进程文件描述符的引用,因此关闭这些描述符会导致错误。

常见问题解答

  1. 为什么在子进程中关闭 ** pipefds[1] 不会影响父进程的文件描述符?**

因为在子进程调用 exec() 之前,子进程和父进程共享相同的地址空间,因此对 pipefds[1] 的关闭不会影响父进程。

  1. 为什么在子进程中关闭 ** file 会导致父进程的文件描述符关闭?**

因为在 exec() 调用之后,子进程的地址空间会发生变化,父进程就不再有对 file 的引用了。

  1. 为什么即使 ** pipefds[0] 在子进程中关闭,父进程仍然可以从中读取?**

因为 pipefds[0]vfork() 之后没有被关闭,并且在子进程调用 exec() 之前,它与父进程共享相同的地址空间。

  1. 在哪些情况下,子进程中关闭的文件描述符会影响父进程的文件描述符?

如果父进程在子进程调用 exec() 之前关闭了文件描述符,那么子进程中的关闭也会影响父进程。

  1. 在 ** vfork() 之后使用 ** exec()** 有什么好处?**

vfork()exec() 结合使用可以创建高效的子进程,因为子进程不必复制父进程的地址空间。