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

推荐订阅源

爱范儿
爱范儿
Simon Willison's Weblog
Simon Willison's Weblog
K
Kaspersky official blog
P
Palo Alto Networks Blog
Google DeepMind News
Google DeepMind News
www.infosecurity-magazine.com
www.infosecurity-magazine.com
AI
AI
G
GRAHAM CLULEY
O
OpenAI News
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Help Net Security
Help Net Security
L
LINUX DO - 热门话题
S
Schneier on Security
P
Privacy International News Feed
L
Lohrmann on Cybersecurity
SecWiki News
SecWiki News
C
Cybersecurity and Infrastructure Security Agency CISA
T
Threatpost
C
Cyber Attacks, Cyber Crime and Cyber Security
A
Arctic Wolf
C
Cisco Blogs
V2EX - 技术
V2EX - 技术
有赞技术团队
有赞技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
Latest news
Latest news
人人都是产品经理
人人都是产品经理
Schneier on Security
Schneier on Security
Last Week in AI
Last Week in AI
Webroot Blog
Webroot Blog
美团技术团队
N
News and Events Feed by Topic
大猫的无限游戏
大猫的无限游戏
Security Archives - TechRepublic
Security Archives - TechRepublic
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 三生石上(FineUI控件)
The Cloudflare Blog
Project Zero
Project Zero
博客园_首页
Cloudbric
Cloudbric
IT之家
IT之家
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
雷峰网
雷峰网
罗磊的独立博客
Hacker News: Ask HN
Hacker News: Ask HN
The Last Watchdog
The Last Watchdog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Tenable Blog
Scott Helme
Scott Helme

暗无天日

读: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 作用域陷阱 - 暗无天日 开源包装器的信任陷阱:四个危险信号 - 暗无天日 程序员愿意为 AI 写文档,却不愿为同事写 - 暗无天日 mktemp: Shell 脚本中临时文件的安全陷阱与最佳实践 - 暗无天日 WSL9x —— 在 Windows 9x 里跑 Linux 内核 6.19 用 ox.el 做你想做的事 —— org-export 高级编程指南 读:Hot-wiring the Lisp Machine —— 用纯 Elisp 构建零依赖的 Org 静态站点生成器 Elisp 性能优化的六个实战教训 - 暗无天日 fcitx5 下 Emacs 无法切换输入法的排查 - 暗无天日 ERT 测试交互命令的三种方式 - 暗无天日 SEM Assistant: 当 Elisp 守护进程遇上 LLM 用 dmsg 给 Elisp 加上结构化调试日志 用 org-habit 追踪非每日习惯 - 暗无天日 Clojure X-Men:当编程语言特性变成超能力 - 暗无天日 TIL: 用 diff-hl 在 fringe 中显示 git 变更 读:llm-test —— 用 LLM agent 驱动 Emacs 测试 TIL: AI 时代的橡皮鸭调试 - 暗无天日 fcitx 启动后键盘输入卡顿的排查 - 暗无天日 TIL: 早期网页的图片热区导航 - 暗无天日 读 Seeing the Whole System 用 Emacs 自动生成每周链接推荐 - 暗无天日 读:ASCII control characters in my terminal 读 What to learn - 暗无天日 Lisp 的括号之痛——一个愚人节玩笑揭开的老伤疤 - 暗无天日 一本书该"线性读"还是"并行读" - 暗无天日 读 How to Monetize a Blog:一篇伪装成变现指南的讽刺文 Python Mock 第三方依赖的四种策略 - 暗无天日 Emacs Lisp 热重载实用指南 - 暗无天日 Prot 的 Emacs 配置哲学 - 暗无天日 TIL: 从直播对谈中学到的三个 Emacs 技巧 - 暗无天日 TIL: 自动使用项目虚拟环境的 Python - 暗无天日 TIL: 让 Help buffer 自动获得焦点 一条命令让本地开发用上 HTTPS —— slim 工具介绍 用 fsck 检查和修复 Linux 文件系统 排查Linux进程"卡死"实战:从strace到gdb全流程 - 暗无天日 PostgreSQL 索引:从基础到你可能不知道的高级用法 - 暗无天日 用 .pdbrc 自定义 Python 调试器 ANSI 转义码的标准化现状 - 暗无天日 终端程序的潜规则 - 暗无天日 PARA Org-mode 测试配置 - 暗无天日 AI越强越辣鸡?控制论说这是必然的 - 暗无天日 AI 越强越需要你盯着——反馈循环实操指南 - 暗无天日 你的AI代理正在偷你的密钥——四种你没想到的泄露通道 - 暗无天日 LLM 在 DevOps 中的三种角色 - 暗无天日 写作风格的反建议 - 暗无天日 反驳本质复杂性——Dan Luu 论为什么《没有银弹》错了 - 暗无天日 文件充满了危险——Dan Luu 谈文件系统的可靠性陷阱 - 暗无天日 AI 时代的 PARA 方法:用 Org-mode 和 AI 打造个人知识管理系统 Linux 数据去重学习笔记 - 暗无天日 创建跨平台 ZIP 文件的隐藏陷阱:Extra Field - 暗无天日 X11 Forwarding 排障指南 - 暗无天日 IP欺骗端口扫描:当别人冒充你去扫描别人 - 暗无天日 Linux 输入栈全景解析:从硬件按键到屏幕响应 - 暗无天日 Unix 系统中那些被埋没的配置开关——以 FontConfig 为例 - 暗无天日 在Linux上限制儿童使用电脑 - 暗无天日 GIF不仅仅是一种图片格式——用GIF流做些奇怪的事 - 暗无天日 Leiningen 学习笔记:Clojure 项目构建与管理从入门到实战配置 - 暗无天日 Google SRE Book 读书笔记 - 暗无天日 为什么 nohup 在 crontab 中不起作用 Bash中的Indirection与Nameref - 暗无天日 Linux PAM 简介 - 暗无天日 从Linux ISO文件启动计算机 - 暗无天日 用 Bash 打造一个Screen Locker 用GitHub Actions自动构建EGO博客 - 暗无天日 blocking I/O 的作用 - 暗无天日 mobileog 手机端同步提示Error:2 No such file 的解决方法 回收 WSL2 VHDX 文件占用空间 使用 org-mode columnview 生成任务列表 - 暗无天日 Emacs 作为 MPD 客户端 - 暗无天日 移动文件路径却不破坏org file link的方法 - 暗无天日 如何合理的导出help link 成HTML - 暗无天日 笑话理解之Biology - 暗无天日
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 信号,知道子进程已经退出,于是自己也退出了。