一个小小的 Shell 管道符,内部实现可真不简单!

管道命令咱们常常使用,将一个指令的输出导入另外一个指令的输入,也就是屁股对上嘴,这个原理连编程小学生都知道。可是若是要深刻问进去,一个指令的输出是如何导入到另外一个指令的输入,管道又起到什么角色,估计能回答这个问题的人不足 1%。下面咱们来深刻分析一下管道指令的实现原理,对于下面的这条指令,shell 到底干了些什么shell

$ cmd1 | cmd2
复制代码

首先我用下面这张图来描述最终形态,而后再一步一步来分解最终形态的造成过程编程

图片

上图咱们看到了进程描述符表、管道、进程的父子关系。bash

fork 和 exec

shell 每次执行指令, 须要 fork 出一个子进程来执行,而后将子进程的镜像替换成目标指令,这又会用到 exec 函数。好比下面这条简单的指令微信

$ cmd
复制代码

exec 函数不会改变当前进程的进程号,不会改变进程之间的父子关系。能够将进程当作一个带壳的球体,exec 以后,外面的壳不会变,球里面的东西被彻底替换了。而输入输出文件描述符默认在壳上面,这意味着指令 cmd 的输入输出继承了 shell 进程的输入输出。函数

$ cmd1 | cmd2
复制代码

当指令里面包含一个管道符,意味着须要并行执行两个指令,这时候 shell 须要 fork 两次生成两个子进程,而后分别 exec 换成目标指令。spa

咱们注意到图里面还有一个 pipe,它就是负责父子进程通讯的管道。3d

pipe

管道用于父子进程的通讯,在 fork 以前建立 pipe,pipe将成为 fork 以后父子进程之间的纽带。pipe 函数会返回两个描述符(pipe_in, pipe_out),一个用于读,一个用于写。code

dup2

下面咱们就须要调整图中描述符的尖头,将 cmd1 进程的 stdout 描述符指向管道写,将 cmd2 进程的 stdin 描述符指向管道读,这就须要神奇的 dup2(fd1, fd2) 函数,它的做用是将 fd1 描述符关联 fd2 指向的内核对象,以前 fd1 指向的内核对象引用计数减一,若是减到零就销毁。注意平时咱们调用 close 方法本质上只是递减引用计数,同一个内核对象是能够被多个进程共享的。当引用计数减到零时就会正式关闭。cdn

下面咱们将 dup2 函数的规则应用一下,对两个进程分别调用 dup2 方法获得对象

而后再将不须要的描述符关闭掉,就获得了下面的终极图,完美!

若是是两个管道符三个命令以下,就会生成两个管道

$ cmd1 | cmd2 | cmd3
复制代码

若是任意一端的进程忽然挂掉了会发生什么?

假设 cmd1 先挂掉,管道写被动关闭,cmd2 在读取管道内容时会遭遇 EOF,而后正常结束。 假设 cmd2 先挂掉,管道读被动关闭,cmd1 继续写管道,这时候进程会收到一个 SIGPIPE 信号,默认动做是进程直接退出。

下一篇咱们将使用酷炫的代码来实现上面的整个过程,咱们不只要知道其中的原理,并且还须要经过亲手实验来了解其中更多的细枝末节。

阅读更多深度技术文章,扫一扫上面的二维码关注微信公众号「码洞」

相关文章
相关标签/搜索