shell如何对子进程进行管道处理

How does shell pipe the child process?

本文关键字:管道 处理 子进程 shell      更新时间:2023-10-16

最近我在研究linux进程间通信。但我在理解管道机制方面有一些问题。

我知道管道是由父进程创建的一对文件,然后父进程将文件描述符传递给其子进程,然后子进程可以对其进行操作。

但是,既然在fork()之后调用exec()时,子进程有一个全新的虚拟内存,那么为什么父进程可以将其信息传递给子进程呢?我错过了什么吗?

文件描述符是操作系统(内核)管理的资源的句柄。创建管道时,内核会创建一些设施,以便数据可以从管道的一端发送到另一端。

这些数据是通过内核发送的。

当您fork()时,子级继承所有文件描述符,这意味着它们继承由文件描述符所引用的内核管理的数据结构。因此,现在文件描述符引用的是子级和父级中完全相同的内核资源。由于内核资源存在于内核中,这一部分在两个进程之间共享,因此它不像用户空间内存那样重复。

基本上,将()数据写入管道的一端,然后将数据复制到内核中的缓冲区中。然后,您可以读取()该数据,并将其从内核缓冲区复制到读取过程的内存空间中。在fork()之后,子级和父级都引用内核中使用pipe()创建的同一缓冲区。

当一个进程exec()转到另一个进程时,该子进程通常继承父进程的标准文件路径:stdin(0)、stdout(1)、stderr(2)。当shell创建管道时,它使用dup2()调用将路径复制到所需的路径号,以便强制将正确的路径复制到子级的标准路径。

// pseudo-code:
// create the pipe
int pipe_end[2];
pipe(pipe_end);
// "back up" stdin
int save_in = dup(0);
// position the pipe to stdin for the benefit of the child
dup2(pipe_end[0], 0);
// start the child
fork() && exec();
// restore stdin
close(0);
dup2(save_in, 0);
// write to the child
write(pipe_end[1], ...);

信息不会传递给子进程,而是通过隐式约定完成的。父级知道它应该将fds复制到插槽0,1,2中,子级知道从这些描述符中读取/写入。你说得对,exec中没有魔法,除了参数和环境向量之外,子代实际上从其父代获得的信息为零。只是unix平台有这些约定,所以子代知道它要使用的相关fds,而父代知道为fds选择哪些数字。

对于需要传递两个或三个以上fds的进程,父进程确实必须显式传递数字。以下是我的机器上明显发生这种情况的一些过程(它可能被填充在其他地方的环境变量中):

  • klauncher --fd=8
  • /bin/dbus-daemon --fork --print-pid 5 --print-address 7 --session