TGViewer
开发者工具箱|编程·开发工具·资源 开发者工具箱|编程·开发工具·资源 @devtoolboxhub · 780 subscribers
Post #1561 11
Linux 读文件背后的预读与猜测机制

一位开发者花了两个星期研究 Linux 读文件时的实际行为,发现内核在大量场景下并非机械执行指令,而是在猜测程序意图。他从冷文件起始位置读取一个 4 KiB 页,内核却返回了四页;把读取位置往后移一页再试,只返回一页。同样的文件、同样的系统调用,差别仅在于起始偏移量。

偏移量为零时,mm/readahead.c 中的分支会直接进入 initial_readahead,内核默认程序要顺序流式读取整个文件,于是立即预取更多数据。从其他位置开始读则被假定为随机访问,直到访问模式证明并非如此。内核完全从偏移量推断意图,调用本身不携带任何意图信息。

测试环境是 Apple Silicon Mac 上的 OrbStack Linux 虚拟机,loop 设备上的 ext4 文件系统,内核 7.0.14,4 KiB 页,read_ahead_kb 为 128。容器共享宿主机内核,宿主机内存回收激进,完全缓存的文件约十五秒就会变冷。读取用 dd 完成,逐页统计靠一个调用 mmap 和 mincore() 的小型 C 工具实现。


read() 并不直接访问磁盘,它从页缓存拷贝字节到用户缓冲区。页缓存是内核用来记忆文件内容的 RAM,目标数据已在缓存中就不涉及设备;不在则先填充缓存再拷贝。程序对话的对象始终是内存,磁盘是更下一层的问题。清空缓存后读一页,四页驻留;什么都不读再统计,归零。

内核需要从设备取数据时,面临调用从未回答的问题:该取多少?只取请求的精确字节数看似诚实但通常错误,内核假设读一个块的程序会想要下一个块。从冷状态逐页顺序读,预读窗口依次打开为四页、十二页、十二页、十二页、三十一页、三十一页,然后停止。配置上限应为三十二页(128 KiB 除以 4 KiB),实测两次都是三十一页,作者尚未查明缺失的那一页。

预读窗口能跨独立进程增长,靠的是 try_context_readahead() 向后扫描页缓存中顺序读者留下的痕迹。缓存是共享的,访问模式会在互不知情的进程间泄漏,一个程序的读取会改变另一个程序的读取形态。


mmap 与 read() 是进入同一页缓存的两扇门,行为却不同。触碰一个映射页带回三十二页,等价的 read() 只带回四页;且窗口以触碰页为中心展开——触发第 100 页的缺页,得到第 84 到 115 页。read() 携带长度,内核能推断方向;缺页不携带任何信息,对称展开猜测是信息不足时的最优下注。

文件系统布局本身也是一种猜测。4 MiB 文件在 ext4 上逻辑大小与分配块完全一致,在 ext2 上则多花 4096 字节。ext2 inode 持有十二个直接块指针后回退到间接寻址,需要一层间接块;ext4 改用 extent 记录连续块区间,几条小记录覆盖整个文件,开销被舍入掉。ext2 的开销按单间接、双间接、三间接分层增长。

最困扰作者的是 major fault 计数器。从冷文件读 8 MiB 后检查进程 major fault,首次测得十八次——这是 Python 解释器自身页面被清出缓存后重新加载所致,与本次读取无关。在单进程内读取前后分别采样,差值为零。八 MiB 真实 I/O 未让计数器移动分毫。major fault 只在 MMU 找不到映射且内核需从存储填充时发生,普通 read() 根本不走这条路,ru_majflt 天然对此盲视。平坦的 major fault 曲线无法判断服务是否 I/O 密集。真正会动的数字在 /proc/<pid>/io 的 read_bytes 和 iostat -x 1。


程序与数据之间的每一层都在未经询问的情况下用某种保证换取速度。这些交易几乎总是正确,正因如此,人们很难建立对例外形态的直觉。文件读取负载在调整数据遍历顺序后变快,通常是内核预测开始命中,而非系统调用或缓冲区大小的问题。这也解释了 tokio::fs 为何是套着异步接口的线程池——对文件询问 readiness 轮询本就是错误问题。自内核 5.9 起可挂载 wait_page_queue 回调并从 task work 重试读取,io_uring 提供真正的异步提交路径,线程池如今更多是兼容性决策而非硬限制。

#开发者 #工具 #Linux #内核 #页缓存 #预读 #ext4 #ext2 #mmap #io_uring
@DevToolboxHub
More from @devtoolboxhub
  1. Sep 26, 2026PostgreSQL 复制槽上限参数 max_replication_slots 解析 max_replication_slots 决定共享内存中复制槽数组的长度,仅此而已。它不决…
  2. Sep 26, 2026五仓库架构踩坑:Gitlink 与双 CI Flude 团队复盘了多仓库架构的实践代价。项目从第一分钟起就选择拆分成独立仓库,Pipeline、engine、design-docs…
  3. Sep 26, 2026Xeno Core:TypeScript 后端架构框架 Node.js 复杂系统的架构选型往往决定代码的长期命运。开发者常在两条路之间纠结:要么依赖重度使用实验性装饰器和反射(如…
  4. Sep 25, 2026用 SLO 给 AI Agent 的行为定个预算 Grafana Labs 提出把可靠性工程里的错误预算(error budget)思路用到 AI Agent 上。延迟、token…
  5. Sep 25, 2026Xcode 27.2 改用 JSON 项目格式 Xcode 27.2 用基于 JSON 的 project.xcproj 取代了沿用多年的 project.pbxproj。新建项目…
  6. Sep 25, 2026PostgreSQL 的 max_prepared_transactions 该不该开 prepared transaction 是脱离会话独立存在的两阶段提交事务。执行 PREP…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →