惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
S
SegmentFault 最新的问题
Jina AI
Jina AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
IT之家
IT之家
美团技术团队
V
Visual Studio Blog
人人都是产品经理
人人都是产品经理
博客园 - 叶小钗
有赞技术团队
有赞技术团队
GbyAI
GbyAI
宝玉的分享
宝玉的分享
腾讯CDC
M
MIT News - Artificial intelligence
博客园 - 【当耐特】
Google DeepMind News
Google DeepMind News
月光博客
月光博客
MyScale Blog
MyScale Blog
Last Week in AI
Last Week in AI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 司徒正美
Recent Announcements
Recent Announcements
MongoDB | Blog
MongoDB | Blog

暗无天日

读:AI Agent 安全日志——从可见性与隐私的两难说起 - 暗无天日 读:AI Agent 生产化——一份从原型到上线的速查清单 - 暗无天日 AI写作的语言指纹——如何让文字不那么像机器 - 暗无天日 读:50 条 Claude Code 技巧——一个工程经理的六个月使用心得 读:AI 辅助开发为什么让 E2E 测试更有价值 - 暗无天日 读:在Emacs中使用Claude Code(Spacemacs适配版) - 暗无天日 Claude Code 背后的工程哲学——读 Agent Harness Engineering 读:Agent Harness Engineering——AI 智能体不只是模型,还有套件 - 暗无天日 browser-harness:让 AI 直接接管你的浏览器 - 暗无天日 读:Security-First CI/CD —— DevSecOps 自动化实践指南 TIL: 数字小键盘的小数点陷阱与行内算术求值 - 暗无天日 读:Immutability 不是万能药,它是一种权衡 - 暗无天日 Conducty:给 Claude Code 加上项目记忆和并行执行能力 - 暗无天日 读 — GitHub Trending 里的 Claude Code 技能包 读 — Prompt Caching 省钱指南 TIL: Emacs 中那些跟鼠标配合的冷门快捷键 - 暗无天日 读:Anvil——把 Emacs 变成 AI 的工具服务器 读:Emacs 代码折叠终极指南 - 暗无天日 读:Clojure 搭车客指南 - 暗无天日 git推送失败后恢复仓库损坏的完整记录 - 暗无天日 多智能体系统的两个有效模式——以及对 Claude Code 用户的启示 - 暗无天日 用 Org Babel 写 Literate 博文:扩展执行 + 定制导出 proced:Emacs 内置的进程查看器 - 暗无天日 从 proced 定制中学到的 Elisp 模式 读:让 Emacs proced 在 macOS 上显示 CPU 和内存 异步编程的函数着色税 - 暗无天日 链式调用的代价:JavaScript 和 Clojure 的共同教训 - 暗无天日 hyperfine:命令行基准测试工具 - 暗无天日 管道中的变量去哪了?——子 shell 作用域陷阱 - 暗无天日 开源包装器的信任陷阱:四个危险信号 - 暗无天日
yes 管道 head 发生了什么 - 暗无天日
2026-04-12 · via 暗无天日

前面我们通过简化的 strace 输出理解了整个过程。现在让我们做一个完整的、可复现的实验,亲眼见证这个精妙的机制是如何运作的。

执行以下命令:

strace -f -e trace=write,read,pipe,pipe2,clone,close,dup2,execve,exit_group -o /tmp/yes_head.log bash -c 'yes | head'

这个命令会跟踪所有相关的系统调用,并将结果保存到 /tmp/yes_head.log 文件中。让我们看看完整的输出:

61937 execve("/usr/bin/bash", ["bash", "-c", "yes | head"], ...) = 0
61937 pipe2([3, 4], 0)                  = 0
61937 clone(...)                        = 61938
61937 close(4)                          = 0
61937 clone(...)                        = 61939
61937 close(3)                          = 0
61938 close(3)                          = 0
61938 dup2(4, 1)                        = 1
61938 close(4)                          = 0
61939 dup2(3, 0)                        = 0
61939 close(3)                          = 0
61938 execve("/usr/bin/yes", ["yes"], ...) = 0
61939 execve("/usr/bin/head", ["head"], ...) = 0
61938 write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192) = 8192
61939 read(0, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192) = 8192
61938 write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192) = 8192
61938 write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192) = 8192
61938 write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192 <unfinished ...>
61939 close(0)                          = 0
61938 <... write resumed>               = 8192
61939 write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\n", 20 <unfinished ...>)
61938 write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192 <unfinished ...>)
61939 <... write resumed>               = 20
61938 <... write resumed>               = -1 EPIPE (断开的管道)
61938 --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=61938, si_uid=1000} ---
61939 close(1)                          = 0
61939 close(2)                          = 0
61939 exit_group(0)                     = ?
61938 +++ killed by SIGPIPE +++
61939 +++ exited with 0 +++
61937 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=61938, si_status=SIGPIPE} ---
61937 exit_group(0)                     = ?
61937 +++ exited with 0 +++

让我们逐步解读这个输出(61937 是 bash,61938 是 yes,61939 是 head):

阶段一:Shell 建立管道和进程

61937 pipe2([3, 4], 0)                  = 0       <-- 创建管道:fd[0]=3(读), fd[1]=4(写)
61937 clone(...)                        = 61938   <-- fork 出 yes 进程
61937 close(4)                          = 0       <-- bash 关闭写端(自己不写)
61937 clone(...)                        = 61939   <-- fork 出 head 进程
61937 close(3)                          = 0       <-- bash 关闭读端(自己不读)

Shell 创建了管道和两个子进程,然后自己关闭了管道的两端(因为 bash 不需要读写这个管道)。

阶段二:子进程重定向文件描述符

61938 close(3)                          = 0       <-- yes 关闭读端
61938 dup2(4, 1)                        = 1       <-- yes: fd[1] --> stdout
61938 close(4)                          = 0       <-- yes 关闭原来的 fd[1]

61939 dup2(3, 0)                        = 0       <-- head: fd[0] --> stdin
61939 close(3)                          = 0       <-- head 关闭原来的 fd[0]

yes 进程(61938)关闭了读端,将写端设为 stdout;=head= 进程(61939)关闭了写端,将读端设为 stdin。

阶段三:进程执行

61938 execve("/usr/bin/yes", ["yes"], ...) = 0
61939 execve("/usr/bin/head", ["head"], ...) = 0

两个进程分别加载并执行对应的程序。

阶段四:数据传输

61938 write(1, "y\ny\n...", 8192)        = 8192    <-- yes 写入 8192 字节
61939 read(0, "y\ny\n...", 8192)         = 8192    <-- head 读取 8192 字节
61938 write(1, "y\ny\n...", 8192)        = 8192    <-- yes 继续写入
61938 write(1, "y\ny\n...", 8192)        = 8192    <-- yes 继续写入
61938 write(1, "y\ny\n...", 8192)        = 8192    <-- yes 继续写入

yes 持续写入数据(每次 8192 字节),=head= 读取了一次 8192 字节后就不再读取了——因为它只需要 10 行数据(20 字节)就够了。

阶段五:head 完成任务,关闭读端

61938 write(1, "y\ny\n...", 8192 <unfinished ...>
61939 close(0)                          = 0       <-- head 关闭 stdin(管道读端)
61938 <... write resumed>               = 8192

注意这里的时序:=yes= 正在写入第 5 次数据(尚未完成),=head= 就已经关闭了 stdin(管道读端)。然后 yes 的写入操作才完成。这说明 headyes 是真正并行运行的。

阶段六:yes 收到 SIGPIPE 终止

61939 write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\n", 20 <unfinished ...>)
61938 write(1, "y\ny\n...", 8192 <unfinished ...>)
61939 <... write resumed>               = 20
61938 <... write resumed>               = -1 EPIPE (断开的管道)
61938 --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=61938, si_uid=1000} ---
61938 +++ killed by SIGPIPE +++

head 把 10 行结果输出到终端( write(1, ..., 20) ),同时 yes 尝试再次写入管道,但此时管道读端已经关闭,=write()= 返回 EPIPE 错误,=yes= 随即收到 SIGPIPE 信号被杀死。

阶段七:head 输出结果并退出

61939 close(1)                          = 0       <-- head 关闭 stdout
61939 close(2)                          = 0       <-- head 关闭 stderr
61939 exit_group(0)                     = ?       <-- head 正常退出
61939 +++ exited with 0 +++

head 将读取的数据输出到终端后,关闭所有文件描述符,正常退出。

阶段八:Shell 收到子进程退出信号

61937 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=61938, si_status=SIGPIPE} ---
61937 exit_group(0)                     = ?
61937 +++ exited with 0 +++

Shell 收到 SIGCHLD 信号,知道子进程已经退出,于是自己也退出了。