布莱恩·科尼汉 (Brian Kernighan) 解释道这个视频早期贝尔实验室对小语言/程序的吸引力是基于内存限制
一台大机器有 64 k 字节——K,而不是 M 或 G——所以这意味着任何单独的程序都不能很大,因此有编写小程序的自然倾向,然后是管道机制,基本上是输入输出重定向,使得将一个程序链接到另一个程序成为可能。
但考虑到数据必须存储在 RAM 中才能在程序之间传输,我不明白这如何限制内存使用。
从维基百科:
在大多数类 Unix 系统中,管道的所有进程同时启动[强调我的],它们的流适当连接,并由调度程序与机器上运行的所有其他进程一起管理。将 Unix 管道与其他管道实现区分开来的一个重要方面是缓冲的概念:例如,发送程序每秒可能产生 5000 字节,而接收程序每秒只能接受 100 字节,但不能数据丢失。相反,发送程序的输出保存在缓冲区中。当接收程序准备好读取数据时,管道中的下一个程序将从缓冲区中读取数据。在 Linux 中,缓冲区的大小为 65536 字节 (64KB)。如果需要,可以使用名为 bfr 的开源第三方过滤器来提供更大的缓冲区。
这让我更加困惑,因为这完全违背了小程序的目的(尽管它们将模块化到一定规模)。
我唯一能想到的解决我的第一个问题(内存限制取决于数据大小的问题)的方法是,当时根本没有计算大数据集,而管道真正要解决的问题是程序本身所需的内存量。但考虑到维基百科引用中的粗体文本,即使这也让我感到困惑:因为一次没有实现一个程序。
如果使用临时文件,所有这些都会很有意义,但我的理解是管道不会写入磁盘(除非使用交换)。
例子:
sed 'simplesubstitution' file | sort | uniq > file2
我很清楚,sed
正在读取文件并逐行将其吐出。但是sort
,正如 BK 在链接视频中所述,是一个句号,因此所有数据都必须读入内存(或者确实如此吗?),然后将其传递到uniq
,(在我看来)这将是一个- 一次一行程序。但是在第一个和第二个管道之间,所有数据都必须位于内存中,不是吗?
答案1
数据不需要存储在 RAM 中。如果读者不存在或者跟不上,管道就会阻塞写者;在Linux(以及我想的大多数其他实现)下有一些缓冲,但这不是必需的。正如所提到的追踪器和杰德BP(看后者的回答),早期版本的 Unix 将管道缓冲到磁盘,这就是它们如何帮助限制内存使用:处理管道可以分成小程序,每个程序都会在磁盘缓冲区的限制内处理一些数据。小程序占用更少的内存,并且管道的使用意味着可以序列化处理:第一个程序将运行,填充其输出缓冲区,暂停,然后第二个程序将被调度,处理缓冲区等。现代系统是命令数量级比早期的 Unix 系统大,并且可以并行运行多个管道;但对于大量数据,您仍然会看到类似的效果(并且这种技术的变体用于“大数据”处理)。
在你的例子中,
sed 'simplesubstitution' file | sort | uniq > file2
sed
根据需要读取数据file
,然后只要sort
准备好读取就写入;如果sort
尚未准备好,则写入会阻塞。数据最终确实存在于内存中,但这是特定于 的sort
,并且sort
准备好处理任何问题(如果要排序的数据量太大,它将使用临时文件)。
您可以通过运行来查看阻塞行为
strace seq 1000000 -1 1 | (sleep 120; sort -n)
这会产生大量数据并将其传输到尚未准备好读取的进程任何事物前两分钟。您将看到许多write
操作正在进行,但很快seq
就会停止并等待两分钟过去,被内核阻止(系统write
调用等待)。
答案2
但考虑到数据必须存储在 RAM 中才能在程序之间传输,我不明白这如何限制内存使用。
这是你的根本错误。早期版本的 Unix 不在 RAM 中保存管道数据。他们将它们存储在光盘上。管道有 i 节点;在表示为的光盘设备上管道装置。系统管理员运行一个名为的程序/etc/config
来指定(除其他外)哪个磁盘上的哪个卷是管道设备,哪个卷是管道设备根设备,其中转储装置。
待处理数据的数量受到以下事实的限制:直接块磁盘上的 i 节点用于存储。这种机制使代码更简单,因为从管道中读取数据所采用的算法与读取常规文件所采用的算法大致相同,只是由于管道不可查找且缓冲区是循环的这一事实而进行了一些调整。
这种机制在 20 世纪 80 年代中后期被其他机制所取代。 SCO XENIX 获得了“高性能管道系统”,它用核心缓冲区取代了 i 节点。 4BSD 将无名管道制成套接字对。 AT&T 使用 STREAMS 机制重新实现了管道。
当然,sort
程序对 32KiB 的输入块(或者如果 32KiB 不可用,它可以分配的任何较小的内存量)执行有限的内部排序,将排序的结果写入中间stmX??
文件,/usr/tmp/
然后在中间文件中进行外部合并排序以提供最终的结果。输出。
进一步阅读
- 史蒂夫·D·佩特 (1996)。 “进程间通信”。UNIX 内部原理:实用方法。艾迪生-韦斯利。 ISBN 9780201877212。
- 莫里斯·J·巴赫 (1987)。 “文件系统的系统调用”。 Unix操作系统的设计。普伦蒂斯·霍尔。国际标准书号 0132017571。
- 史蒂文·V·埃尔哈特 (1986)。 “
config
(1M)”。 Unix 程序员手册:3. 系统管理设施。霍尔特、莱因哈特和温斯顿。 ISBN 0030093139。第 23-28 页。 - Abhijit Menon-Sen(2020-03-23)。Unix 管道是如何实现的?。 toroid.org。
答案3
你是部分正确的,但只是意外地。
在您的示例中,所有数据确实必须已在管道“之间”读取,但不需要驻留在内存中(包括虚拟内存)。通常的实现sort
可以通过对临时文件进行部分排序并合并来对不适合 RAM 的数据集进行排序。然而,一个给定的事实是,在读取每个元素之前,您不可能输出排序序列。这是很明显的。所以是的,sort
只能在读取第一个管道的所有内容(并完成任何操作,可能部分排序临时文件)后才能开始输出到第二个管道。但它不必须将其全部保存在 RAM 中。
然而,这与管道的工作原理无关。管道可以命名(传统上它们都是命名的),这意味着它们在文件系统中拥有一个位置,就像文件一样。这就是管道曾经的样子,文件(作为一种优化,在物理内存可用性允许的情况下写入合并)。
如今,管道是一个小型的、有限大小的内核缓冲区,数据被复制到其中,至少是这样从概念上来说发生。如果内核可以帮助它,则可以通过使用虚拟机技巧来消除副本(例如,从文件进行管道传输通常只会使同一页面可供其他进程读取,因此最终只是一个读取操作,而不是两个副本,并且没有在某些情况下,您可能还需要比缓冲区高速缓存已使用的更多内存,或者非常接近的内存。
如果管道很小且尺寸有限,那么这如何适用于任何未知(可能很大)的数据量?这很简单:当没有更多空间适合时,写入会阻塞,直到再次有空间为止。
曾几何时,当内存非常匮乏时,许多简单程序的原理是非常有用的。因为,嗯,你可以一步一步地完成工作,一次一项。如今,除了一些额外的灵活性之外,我敢说,优势已经不再那么大了。
然而,管道的实现非常高效(它们必须如此!),因此也没有缺点,而且它是一个运行良好且人们已经习惯的既定事物,因此无需更改范例。