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
,(在我看来)将是一个-一次一行的程序。但是在第一个和第二个管道之间,所有数据都必须在内存中,不是吗?
数据不需要存储在 RAM 中。如果读者不在那里或跟不上,管道会阻止他们的作者;在 Linux 下(以及大多数其他实现,我想)有一些缓冲,但这不是必需的。正如mtraceur和JdeBP所提到的(见后者的回答),早期版本的 Unix 缓冲管道到磁盘,这就是它们帮助限制内存使用的方式:处理管道可以分成小程序,每个小程序都会在磁盘缓冲区的限制内处理一些数据。小程序占用更少的内存,并且管道的使用意味着可以串行化处理:第一个程序将运行,填充其输出缓冲区,被挂起,然后第二个程序将被调度,处理缓冲区等。现代系统是命令比早期的 Unix 系统大,可以并行运行许多管道;但是对于大量数据,您仍然会看到类似的效果(并且这种技术的变体用于“大数据”处理)。
在你的例子中,
sed
根据需要从中读取数据file
,然后在sort
准备好读取时写入;如果sort
尚未准备好,则写入阻塞。数据最终确实存在于内存中,但这是特定于 的sort
,并sort
准备处理任何问题(它将使用临时文件,因为要排序的数据量太大)。您可以通过运行查看阻塞行为
这会产生相当多的数据并将其通过管道传输到前两分钟还没有准备好读取任何内容的进程。您会看到许多
write
操作通过,但很快seq
就会停止并等待两分钟过去,被内核阻塞(write
系统调用等待)。这是你的根本错误。早期版本的 Unix 没有在 RAM 中保存管道数据。他们将它们存储在光盘上。管道有 i 节点;在 表示管道 设备的 磁盘 设备 上. 系统管理员运行了一个名为的程序
/etc/config
,以指定(除其他外)哪个磁盘上的哪个卷是管道设备,哪个卷是根设备,以及哪个转储设备。待处理数据的数量受限于磁盘上只有 i 节点的直接块用于存储这一事实。这种机制使代码更简单,因为从管道读取所采用的算法与从常规文件读取所采用的算法大致相同,但由于管道不可搜索且缓冲区是循环的,因此进行了一些调整。
这种机制在 1980 年代中后期被其他机制所取代。SCO XENIX 获得了“高性能管道系统”,它用内核缓冲区代替了 i 节点。4BSD 将未命名的管道变成了套接字对。AT&T 使用 STREAMS 机制重新实现了管道。
当然,该
sort
程序执行有限的内部排序 32KiB 输入块(或者如果 32KiB 不可用,它可以分配的任何较小的内存量),将排序结果写入中间stmX??
文件,/usr/tmp/
然后在其中外部合并排序以提供最终输出。进一步阅读
config
(1M)”。 Unix 程序员手册: 3. 系统管理工具。霍尔特、莱因哈特和温斯顿。ISBN 0030093139。第 23-28 页。你是部分正确的,但只是偶然的。
在您的示例中,确实必须在管道“之间”读取所有数据,但它不需要驻留在内存中(包括虚拟内存)。可以通过
sort
对临时文件进行部分排序和合并来对不适合 RAM 的数据集进行排序。但是,一个给定的事实是,您不可能在读取每个元素之前输出排序序列。这很明显。所以是的,sort
只能在从第一个管道读取(并完成任何操作,可能部分排序临时文件)之后开始输出到第二个管道。但它不一定必须将其全部保存在 RAM 中。但是,这与管道的工作方式无关。管道可以被命名(传统上它们都被命名),这意味着它们在文件系统中的位置,就像文件一样。这正是管道从前的样子,文件(作为优化,在物理内存可用性允许的情况下合并写入)。
如今,管道是一个小的、有限大小的内核缓冲区,数据被复制到其中,至少在概念上是这样。如果内核可以帮助它,则可以通过玩 VM 技巧来消除副本(例如,从文件管道传输通常只是使同一页面可供其他进程读取,因此它最终只是一个读取操作,而不是两个副本,并且没有无论如何都需要比缓冲区缓存已使用的更多内存。在某些情况下,您也可能获得 100% 的零拷贝。或者,非常接近。
如果管道很小且大小有限,那么这如何适用于任何未知(可能很大)的数据量?这很简单:当没有更多合适的时候,写入会阻塞,直到有空间为止。
在记忆非常稀缺的时候,许多简单程序的哲学是最有用的。因为,嗯,你可以一步一步地工作,一次一个。如今,除了一些额外的灵活性之外,我敢说,优势已经不再那么大了。
然而,管道的实现非常有效(它们必须如此!),所以也没有缺点,而且它是一个既定的东西,它工作得很好并且人们已经习惯了,所以没有必要改变范式。