所以,我得到了一行脚本:
echo test | cat | grep test
请您解释一下,在进行以下系统调用时,它究竟是如何工作的:pipe()、fork()、exec()和dup2()?
我在这里寻找一个总的概述,主要是操作顺序。
到目前为止,我所知道的是shell将使用fork()分叉,脚本的代码将使用exec()替换shell的代码但是pipe和dup2呢它们是如何就位的?
提前谢谢。
最佳答案
首先考虑一个简单的例子,例如:
echo test | cat
我们想要的是在一个单独的进程中执行
echo
,将其标准输出转移到执行cat
的进程的标准输入中理想情况下,这种转移,一旦设置,就不需要壳牌进一步干预——壳牌将冷静地等待两个过程退出。实现这一目标的机制称为“管道”它是在内核中实现并导出到用户空间的进程间通信设备一旦由Unix程序创建,管道就有一对文件描述符的外观,这些描述符具有特殊的属性,如果您写入其中一个,则可以从另一个中读取相同的数据。这在同一进程中不是很有用,但请记住,文件描述符(包括但不限于管道)是跨
fork()
甚至跨exec()
继承的。这使得pipe成为一种易于设置且相当有效的IPC机制。shell创建管道,现在拥有属于管道的一组文件描述符,一个用于读取,一个用于写入这些文件描述符由两个分叉子进程继承。现在只有当
echo
写入管道的write-end描述符而不是实际的标准输出时,如果cat
从管道的read-end描述符而不是标准输入中读取时,一切都会正常工作。但他们没有,这就是dup2
发挥作用的地方。dup2
将一个文件描述符复制为另一个文件描述符,并预先自动关闭新的描述符。例如,dup2(1, 15)
将关闭文件描述符1(按照用于标准输出的约定),并将其作为文件描述符15的副本重新打开—这意味着写入标准输出实际上等同于写入文件描述符15。这同样适用于读取:dup2(0, 8)
将使从文件描述符0(标准输入)读取等同于从文件描述符8读取。如果我们继续关闭原始文件描述符,打开的文件(或管道)将有效地从原始描述符移动到新描述符,就像科幻电视剧一样,首先在远程位置复制一块物质,然后分解原始文件。如果您仍然遵循这个理论,那么shell执行的操作顺序现在应该很清楚了:
shell创建一个管道,然后
fork
两个进程,这两个进程都将继承管道文件描述符r
和w
。在即将执行
echo
的子进程中,shell在dup2(1, w); close(w)
之前调用exec
,以便将标准输出重定向到管道的写端。在即将执行
cat
的子进程中,shell调用dup2(0, r); close(r)
,以便将标准输入重定向到管道的读取端。分叉后,主壳工艺必须自行关闭管道的两端一个原因是一旦子进程退出,就释放与管道相关的资源。另一种方法是允许
cat
实际终止-只有在关闭管道的所有写端副本之后,管道的读取器才会收到EOF。在上面的步骤中,我们确实在复制到1之后立即关闭了写端的子级冗余副本,即文件描述符15。但是文件描述符15也必须存在于父节点中,因为它是在该数字下继承的,并且只能由父节点关闭。否则,cat
的标准输入将永远不会报告EOF,其cat
进程将因此挂起。这种机制很容易被推广到三个或更多通过管道连接的过程对于三个进程,管道需要安排
echo
的输出写入cat
的输入,而cat
的输出写入grep
的输入。这需要两次呼叫pipe()
,三次呼叫fork()
,四次呼叫dup2()
和close
(一次呼叫echo
和grep
,两次呼叫cat
),三次呼叫exec()
,以及四次额外呼叫close()
(每个管道两次)。
关于c - 分解shell脚本;引擎盖下会发生什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20156171/