(Unix)pipe 把程序当成可组合的函数

1973 年一夜写完的 Unix 管道,接口只有"字节进、字节出",凭什么撑了 50 年

凌晨两点,你在线上排一个问题。日志文件明明在滚,你敲下:

bash 复制代码
tail -f app.log | grep ERROR

什么都没有。屏幕干干净净。

你的第一反应是 grep 写错了,或者日志里根本没有 ERROR。于是直接 grep ERROR app.log,红字一行行滚出来 ------ 有,而且很多。

这不是 grep 的错,也不是 tail 的错。管道正在完美地执行它唯一的契约:字节进,字节出。问题出在两节管道之间:glibc 发现 stdout 不是终端,就把输出从行缓冲切成 4KB 块缓冲。你那些 ERROR 还躺在缓冲区里,等攒够一页。

加个参数就好了:stdbuf -oL grep ERROR。

这件事之后,我把 | 重新想了一遍。我一直以为它是个语法糖,作用只是省掉一个临时文件。但它其实是一个极其激进的架构决定:把"函数组合"这件事,从编程语言内部搬到了操作系统层。

这篇文章只回答一个问题:

凭什么一个接口只有"字节进、字节出"两句话的东西,能撑住 50 年,而我们后来精心设计的对象管道、消息总线、微服务契约,一个接一个地塌?


一、管道不是语法糖,是一次"用表达能力换通用性"的交易

先看它到底做了什么。

内核里有一个环形缓冲区(一块固定大小的内存,写完一圈就绕回开头)。写端往里塞字节,读端往外取字节。塞满就阻塞写者,取空就阻塞读者,写端全部关闭读者拿到 EOF。就这些。

text 复制代码
       进程 A                     内核                      进程 B
   ┌───────────┐        ┌──────────────────────┐        ┌───────────┐
   │  write()  │──────▶ │  ██████████░░░░░░░░  │ ─────▶ │  read()   │
   │  fd 1     │        │  环形缓冲区 64 KiB    │        │  fd 0     │
   └───────────┘        └──────────────────────┘        └───────────┘
                              ▲            ▲
                        写满则阻塞    读空则阻塞
                        写端关闭 → 读端收到 EOF(read 返回 0)

Linux 上这个缓冲区默认 65536 字节,正好 16 个内存页(pipe(7),Linux 2.6.11 起的默认值;macOS/Darwin 一侧是 16 KiB)。

三个细节决定了它的性格:

  • 半双工。只能单向流。想双向,开两条管道。
  • 不可定位。man page 原话:"It is not possible to apply lseek(2) to a pipe." 没有"第几个字节"这个概念,只有"下一个字节"。
  • 复用已有接口 。它没有发明任何新的系统调用,用的就是每个人都已经会用的 read() 和 write(),作用在文件描述符(fd,进程持有的一个整数句柄,代表"某个可以读写的对象")上。

最后一条是最狠的。

任何在 1973 年之前就已经写好的程序,只要它从 fd 0 读、往 fd 1 写,它自动就成了管道的一节,作者一行代码都不用改。

McIlroy 后来回忆这件事,说 Thompson 做的东西"和我提的方案不完全一样,他发明了一个稍微更好一点的"。而 McIlroy 自己 1964 年提的方案,是更"高级"、更"正确"的那种 ------ 一个带方向、带语义的专用通信原语。

结果是那个"更差"的赢了。

〔分析〕这是第一层反讽,也是 Richard Gabriel 在《Lisp: Good News, Bad News, How to Win Big》里提出的"Worse is Better"的完美实例:接口的简单性优先于正确性。"更差"的那个不需要任何人改变自己,所以一夜之间长满系统;"更好"的那个,每个使用者都得先说服自己搬家。

符号本身也藏着同一条逻辑。

第一版语法复用了重定向符,写出来是 sort input >pr>opr>,传参数还得加引号。因为 < 被对称接受,同一条管道能反着写。第三版手册用了整整一页纸解释这套语法。后来 Thompson 要在一次大型演讲上讲它,觉得实在说不出口,才改成中缀的 | ------ 到第四版手册,四句话就描述完了。

从一页纸到四句话。记法成本决定使用频率,这条 1973 年就验证过了。


二、八年的空白,比那一夜更值得看

这段历史通常从"一夜实现"讲起。真正有信息量的是前面那八年。

1964 年 10 月 11 日,贝尔实验室的 Doug McIlroy 在一份内部备忘录里用打字机敲下一句话:

We should have some ways of coupling programs like garden hose --- screw in another segment when it becomes necessary to massage data in another way.

译:我们该有办法像接水管一样接程序 ------ 需要换个方式处理数据时,就再拧上一节。

Dennis Ritchie 把那张泛黄的纸挂在自己办公室墙上。然后,八年,什么都没有发生。

Ritchie 在《The Evolution of the Unix Time-sharing System》里的记录是这样的:

The idea, explained one afternoon on a blackboard, intrigued us but failed to ignite any immediate action... What a failure of imagination!

(这个想法某天下午在黑板上讲了一遍,我们都觉得有意思,但没有激起任何立刻的行动......真是一次想象力的失败。)

到 1972 年第二版 Unix 发布,管道仍然不在里面。McIlroy 反复提,提了八年,最后是 Thompson 被念烦了。McIlroy 的原话:

In one feverish night Ken wrote and installed the pipe system call, added pipes to the shell, and modified several utilities, such as pr and ov, to be usable as filters. The next day saw an unforgettable orgy of one-liners...

(一个狂热的夜晚,Ken 写完并装上了管道的系统调用,把管道加进 shell,还改了好几个工具让它们能当过滤器用。第二天是一场难忘的单行命令狂欢。)

下面这张时间线里,我想让你注意的是中间那一段的长度。
#mermaid-svg-xjFXDX0jccGxBA96{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xjFXDX0jccGxBA96 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xjFXDX0jccGxBA96 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xjFXDX0jccGxBA96 .error-icon{fill:#552222;}#mermaid-svg-xjFXDX0jccGxBA96 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xjFXDX0jccGxBA96 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xjFXDX0jccGxBA96 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xjFXDX0jccGxBA96 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xjFXDX0jccGxBA96 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xjFXDX0jccGxBA96 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xjFXDX0jccGxBA96 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xjFXDX0jccGxBA96 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xjFXDX0jccGxBA96 .marker.cross{stroke:#333333;}#mermaid-svg-xjFXDX0jccGxBA96 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xjFXDX0jccGxBA96 p{margin:0;}#mermaid-svg-xjFXDX0jccGxBA96 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xjFXDX0jccGxBA96 .cluster-label text{fill:#333;}#mermaid-svg-xjFXDX0jccGxBA96 .cluster-label span{color:#333;}#mermaid-svg-xjFXDX0jccGxBA96 .cluster-label span p{background-color:transparent;}#mermaid-svg-xjFXDX0jccGxBA96 .label text,#mermaid-svg-xjFXDX0jccGxBA96 span{fill:#333;color:#333;}#mermaid-svg-xjFXDX0jccGxBA96 .node rect,#mermaid-svg-xjFXDX0jccGxBA96 .node circle,#mermaid-svg-xjFXDX0jccGxBA96 .node ellipse,#mermaid-svg-xjFXDX0jccGxBA96 .node polygon,#mermaid-svg-xjFXDX0jccGxBA96 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xjFXDX0jccGxBA96 .rough-node .label text,#mermaid-svg-xjFXDX0jccGxBA96 .node .label text,#mermaid-svg-xjFXDX0jccGxBA96 .image-shape .label,#mermaid-svg-xjFXDX0jccGxBA96 .icon-shape .label{text-anchor:middle;}#mermaid-svg-xjFXDX0jccGxBA96 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xjFXDX0jccGxBA96 .rough-node .label,#mermaid-svg-xjFXDX0jccGxBA96 .node .label,#mermaid-svg-xjFXDX0jccGxBA96 .image-shape .label,#mermaid-svg-xjFXDX0jccGxBA96 .icon-shape .label{text-align:center;}#mermaid-svg-xjFXDX0jccGxBA96 .node.clickable{cursor:pointer;}#mermaid-svg-xjFXDX0jccGxBA96 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xjFXDX0jccGxBA96 .arrowheadPath{fill:#333333;}#mermaid-svg-xjFXDX0jccGxBA96 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xjFXDX0jccGxBA96 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xjFXDX0jccGxBA96 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xjFXDX0jccGxBA96 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xjFXDX0jccGxBA96 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xjFXDX0jccGxBA96 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xjFXDX0jccGxBA96 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xjFXDX0jccGxBA96 .cluster text{fill:#333;}#mermaid-svg-xjFXDX0jccGxBA96 .cluster span{color:#333;}#mermaid-svg-xjFXDX0jccGxBA96 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xjFXDX0jccGxBA96 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xjFXDX0jccGxBA96 rect.text{fill:none;stroke-width:0;}#mermaid-svg-xjFXDX0jccGxBA96 .icon-shape,#mermaid-svg-xjFXDX0jccGxBA96 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xjFXDX0jccGxBA96 .icon-shape p,#mermaid-svg-xjFXDX0jccGxBA96 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xjFXDX0jccGxBA96 .icon-shape .label rect,#mermaid-svg-xjFXDX0jccGxBA96 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xjFXDX0jccGxBA96 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xjFXDX0jccGxBA96 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xjFXDX0jccGxBA96 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1964.10.11

McIlroy 备忘录

像接水管一样接程序
1964 - 1972

八年无动作

Ritchie:一次想象力的失败
1973 年初

Thompson 一夜实现

pipe() + shell 的 |
1973.02

第三版手册收录
1978

BSTJ 定调

as yet unknown
1986

McIlroy 六段管道

回应 Knuth

从想法到实现,中间隔了八年;从实现到能用,隔了一夜。

(严格说,这个日期是窗口而非一个点:第三版手册 1973 年 2 月收录了管道,但 Ritchie 本人把那一夜记成了 1972 年,而他是在场的人。)

〔分析〕这一节最值得记住的不是"一夜"的浪漫,而是那八年。当时四五个全世界最强的系统程序员,都看着这个想法,都觉得有意思,然后回去干别的了。这不是压制,不是视野狭窄 ------ 是"看得懂"和"值得先停下来做"本来就是两件事。

所以管道的真实成本结构是:实现成本约等于零,组织成本约等于八年。 1973 年那一夜只花了 Thompson 几个小时,前面八年花的是一个部门主任的持续施压 ------ 他后来在口述史里承认,那是他"极少数几乎对 Unix 施加了管理控制"的地方。

普林斯顿的科学史学者 Michael Mahoney 在 1989 年访谈他时问:Unix 的"工具箱"观念,在管道之前存在吗?

McIlroy 的回答是三个字:"Pipes created it."

管道创造了工具箱观念,不是反过来。


三、那句 "as yet unknown",是所有威力的来源

1978 年,贝尔实验室在《Bell System Technical Journal》上出了一期 Unix 专刊。前言里有我认为整个 Unix 哲学中最重要的一句话,比流传更广的 "do one thing well" 重要得多:

Expect the output of every program to become the input to another, as yet unknown, program.

请把注意力放在 "as yet unknown" 这四个字上。

一个程序在设计的时候,不允许知道自己下游是谁。

sort 不知道读它的是 uniq、是 head、还是一个十年后才被发明出来的程序。grep 不知道上游是文件、是键盘、还是网络另一头的一台机器。没有协商、没有注册、没有共享类型、没有版本握手。两个程序协作,而彼此代码里关于对方的描述是零行。

这个"不知道"是有经济后果的,而且是可以算出来的。

假设你有 50 个只做一件事的工具。把它们连成管道(同一条管道里不重复用同一个工具),长度 1 到 3 的有多少条:

管道长度 组合数 算式
1 节 50 50
2 节 2,450 50 × 49
3 节 117,600 50 × 49 × 48
合计 120,100 ---

50 个工具,12 万条管道。

现在换一个对照组:一个 50 项功能的单体应用。它能做的事,永远是 50 项。

〔分析〕差别不在"能力",在增长函数的形状。加第 51 个工具,你不是多了一个能力,是多了它所能参与的所有管道 ------ 包括十年后才会有人想到的那些。加第 51 个功能,你多了一个功能,就这样。

这就是为什么 xargs、tee、sed 这些 1970 年代的零件今天还活在你的 CI 脚本里:不是因为写得好,是因为它们不知道自己在跟谁合作。


四、纯文本赢的不是"简单",是"可见"

现在该问一句:为什么载体必须是文本?

如果 1973 年选择"传结构化数据",管道在设计上会更"正确",但也极可能死掉。文本赢在三个非常具体的地方。

第一,人和机器共读。 这一串命令,你在中途任何一处 tee 出来看一眼,都能立刻判断对错:

bash 复制代码
cat access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20

这个能力叫可观测性,1973 年就有了,而且是免费附赠的。

第二,零协商。 不需要 schema、不需要版本号、不需要握手。grep 不关心上游吐的是 JSON 还是 CSV,只要它是字节。

第三,探针成本极低。 一条管道跑错了,插一段看一眼再拔掉,成本是敲 6 个字符;同样的事在 gRPC 链路上,你得先起代理、配抓包规则。

〔分析〕所以要区分两件事:真正被继承下来的不是"文本"这个载体,而是"接口窄 + 互不知情"这个结构。 文本只是 1973 年唯一能同时满足"人可读"和"机器可解"的载体 ------ 换句话说,它是一个时代约束下的最优解,不是永恒的最优解。这个区分很重要,它直接解释了为什么后来的 JSON Lines 能无缝接替纯文本,而不需要推翻管道(第六节会展开)。


五、代价清单:每一次成功的组合,都在某处漏掉了一点东西

只讲成功面的文章像软文。管道的代价非常具体,每条都能在今天的生产里找到对应事故。

5.1 tar 的存在,本身就是一场忏悔

1979 年第七版 Unix 带来 tar。它为什么必须存在?

因为一个字节流表达不了这些东西:文件从哪儿开始、到哪儿结束、权限是什么、谁拥有它、什么时候改的。

纯文本流里没有"边界"和"元数据"这两个概念。

于是 tar 在流内部人工造出这两样东西:一段 512 字节的头部(文件名、大小、权限、mtime),一段内容,补齐到 512 的整数倍。这是给一个不擅长表达结构的接口打的补丁 ------ 而且这个补丁今天还在被 docker save、helm package、npm pack 一层层继承。

5.2 连文件名都装不下

Unix 的文件名允许包含空格,也允许包含换行符。于是:

bash 复制代码
find . -name "*.log" | xargs rm           # 遇到空格就炸
find . -name "*.log" -print0 | xargs -0 rm   # 才对

-print0 和 -0 这对参数的存在,是文本流接口的一份漏洞公告:"换行符是记录分隔符"这个假设从一开始就是错的。 正解是 NUL 字节 ------ 唯一不可能出现在文件名里的字符。

5.3 xargs 是根耻辱柱

管道里只有一条通道。

所以在语言层面完全不同的两样东西 ------ "数据"和"参数" ------ 在管道里被压平成同一种东西:字节。

于是需要 xargs:一个专门把字节从"数据"重新解释成"参数"的转换器。它每天都在提醒你,管道只有一条车道,而函数调用需要两条(参数 + 返回值)。

5.4 静默错误:切错列,退出码还是 0

bash 复制代码
cat access.log | cut -d' ' -f7 | sort | uniq -c | sort -rn | head

如果哪天日志格式改了,第 7 列不再是 URL,这段命令照样跑完,照样返回 0,照样给你一张漂亮的统计表 ------ 只是全错。

管道链上没有任何一个环节能做类型校验。sort 收到什么就排什么。整条链路唯一的正确性保证,是你脑子里那份 schema,而它不在任何一行代码里、不在任何一次 code review 里、不在任何一次 CI 里。

这是管道最贵的代价:它把"接口错误"从编译期、启动期,全部推到了"人肉校验期"。

5.5 缓冲与 SIGPIPE,以及那两个不对称

开头那个 tail -f | grep 是缓冲问题。

还有 SIGPIPE(一个信号,进程往"没人读"的管道里写时内核会发给它,默认动作是直接杀掉进程):

bash 复制代码
yes | head -5

yes 会立刻死掉。因为 head 读完 5 行就退出、关掉读端,yes 下一次写就吃到 SIGPIPE,被终止。

这是管道优雅的地方:上游会被下游的"我不需要了"自动干掉,不需要任何协调代码。

但它不对称:

bash 复制代码
sort | head -5

sort 必须读完全部输入 才吐出第一行。你的 head 想省事,sort 不允许。数据依赖决定了流水线里有没有真正的短路,而这件事管道帮不了你。

5.6 性能:McIlroy 自己的管道,慢 10 倍

1986 年 6 月,《Communications of the ACM》的 Programming Pearls 专栏。Jon Bentley 请 Donald Knuth 写一个程序:找出文本里出现频率最高的 k 个词。

Knuth 用他的 literate programming(把程序当文学作品来写,代码和推导交织)交了一篇完整的文章 ------ 精心设计的前缀树(trie,按字符逐层分叉的查找结构),完整的正确性论证,教科书级别的作品。

McIlroy 的回应是一封短信,标题叫《A Review》。他先说了这句关键的话:

A wise engineering solution would produce --- or better, exploit --- reusable parts.

(一个明智的工程解,应该产出可复用的部件 ------ 或者更好,直接利用已有的。)

然后他给了六段管道:

bash 复制代码
tr -cs A-Za-z '\n' |
tr A-Z a-z |
sort |
uniq -c |
sort -rn |
sed ${1}q

他原文写道:这个脚本是当场写出来的,一次跑通,在 VAX-11/750 上处理一个一万词的文件花了 30 秒。

这个故事通常被讲成"管道吊打 Knuth"。但真实数据不是这样。

有人拿乔伊斯的《尤利西斯》拼成 96 MB 的测试文件跑过基准:

实现 耗时 时间复杂度
C++(前缀树 + 堆) 4.43 s O((N+k)·log k)
Python(Counter) 10.55 s O(N + k·log Q)
AWK + sort 20.61 s O(N + Q·log Q)
McIlroy 的六段管道 43.55 s O(N log N)

(数据来自 Code Golf Stack Exchange 上对该题基准的复现,非 McIlroy 原文。)

慢了整整 10 倍。

〔分析〕这才是管道最诚实的地方:它从来不是最优解,它是"下一个问题出现时还在那儿"的解。 Knuth 的程序更快,但它只解决那一个问题;McIlroy 的管道慢 10 倍,可它那六个零件,今天还在你我的终端里。

这六段里每一段都在"浪费":sort 把全部数据落盘(外部排序),uniq -c 再全量过一遍。管道不跨节优化,只保证每节的局部最优,全局最优的责任交给你。而且每过一节还要两次拷贝(用户态↔内核),五节就是十次。


六、那又怎样

三个判断,一个边界。

一、接口宽度和复用广度,是反比关系。

管道只有一个接口:字节进,字节出。正因为窄到不能再窄,它才能接上任何东西。

你在设计 API 时把接口定义得越"完备"(更多必填字段、更多枚举、更多隐式约定),能复用它的人就越少。这不是审美偏好,是第三节那张组合数表格的直接推论:每增加一个约束,能接上的下游就少一批,而损失是指数级的。

二、JSON Lines 是管道的正确进化,不是背叛。

jq 之所以能接上 grep、接上 kubectl、接上 tail -f,是因为它保留了行这个边界 ------ 一行一个完整对象。它没有破坏"字节流 + 定界符"这个结构,只是把载荷从"人读文本"升级成了"可解析文本"。

bash 复制代码
kubectl logs -f deploy/api | jq -c 'select(.level=="error") | .msg'

同一个 |,同样的互不知情,但这次下游拿到的东西有类型了。改载荷,不改结构 ------ 这是管道该有的进化方式。

〔分析〕凡试图"改结构"的方案都死得比较难看。XML-RPC、SOAP、早期 EJB 的问题不是复杂,是要求两端互相了解 ------ 每加一个参与者,协商成本按人数平方涨。

三、但别把管道的成功无限外推。

管道不跨机器 。这是它的硬边界,也是为什么微服务最终需要 RPC,而不是 ssh host 'cmd' | ...。

一旦跨了网络,你就必须把管道刻意删掉的那些"互相了解"一层层加回来:序列化格式、超时、重试、幂等、背压、版本兼容。每一样都是管道用"我不知道你是谁"省掉的东西。

〔分析〕所以今天那些"微服务塌了"的故事,本质上不是管道的失败,而是把只在一个进程树内成立的假设,硬搬到了有网络分区、有独立部署、有异构版本的世界里。管道能成立,有个前提很少被说出来:两端程序同时启动、同时死亡、跑在同一台机器上、由同一个人编写,而且整个生命周期不超过一条命令的执行时间。

这个前提一旦破了,管道的极简就不再是优势,而是漏洞。


写在最后

回到开头那个凌晨两点的终端。

tail -f app.log | grep ERROR 什么都没输出 ------ 这条管道里的每一个程序都在完美工作。tail 在看文件,grep 在匹配,缓冲区在缓冲,内核在拷贝。整条链路没有一处故障。

它失败的地方,恰好就是它成功的地方:没有任何一个环节知道自己下游是谁,所以也没有任何一个环节能察觉到下游正在等。

50 年了,这个性质没有变过,也不会变。

管道五十年只做了一件事:把程序之间的"互相了解",删到了零。它剩下的全部威力,都是这个零换来的。


参考资料

  1. M. D. McIlroy,1964 年 10 月 11 日贝尔实验室内部备忘录(原文由 D. M. Ritchie 保存,后在多份 Unix 史料中引用)
  2. D. M. Ritchie, The Evolution of the Unix Time-sharing System, 1979 / 1984
  3. M. D. McIlroy, E. N. Pinson, B. A. Tague, UNIX Time-Sharing System: Foreword, Bell System Technical Journal, 57(6),1978
相关推荐
CHHH_HHH1 小时前
【Linux网络篇】初识网络与Socket编程基础
linux·服务器·c语言·开发语言·网络·后端·1024程序员节
星恒随风2 小时前
Linux线程同步与互斥:彻底理解读者写者问题、读写锁原理、读者优先与写者优先(附pthread完整实验)
linux·服务器·笔记·1024程序员节
_upupup2 小时前
Linux中的进程
linux·服务器·1024程序员节
虚无的纽扣2 小时前
【Linux】线程同步:条件不满足时,线程该怎么等
linux·服务器·1024程序员节
Ruiery3 小时前
Linux 6.6内核并发与同步(一):原子操作与内存屏障 — 内核同步的硬件底座
linux·运维·服务器·1024程序员节
我是大猴子3 小时前
py how claude work项目分析
运维·服务器·windows·1024程序员节
H.莓飛3 小时前
【数据结构】排序_OJ题(二):划分与选择
linux·服务器·c语言·开发语言·数据结构·visual studio
djjjx.3 小时前
Linux进程篇(七):环境变量的底层溯源、核心获取机制与安全应用
linux·运维·服务器
.道阻且长.4 小时前
Linux 基本开发工具:编辑器vim,配置vim
linux·运维·服务器·职场和发展·编辑器·vim·1024程序员节