news 2026/5/28 8:57:33

Linux IO栈:页缓存、块层与IO调度深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux IO栈:页缓存、块层与IO调度深度解析

Linux I/O 全栈:从write()到磁盘磁头——一个字节的万里长征

你调用write(fd, buf, 4096)只花了 50 微秒。这 50 微秒里发生了什么?这篇文章追踪一个字节的完整旅程——从用户态系统调用到磁盘控制器,经过 VFS、Page Cache、Block Layer、I/O 调度器、设备驱动,共七层。


一、你写了write()之后

intfd=open("/data/file.txt",O_WRONLY|O_CREAT,0644);charbuf[4096]="Hello, World!";write(fd,buf,4096);close(fd);

这行write()触发了 Linux I/O 栈的全部七层:

用户态 ───── write(fd, buf, 4096) │ ═════════╪═══════════ 系统调用边界 │ 内核态 ┌───────▼──────────┐ 1. VFS 层(统一接口) │ vfs_write() │ └───────┬──────────┘ │ ┌───────▼──────────┐ 2. 文件系统层(ext4/XFS) │ ext4_file_write() │ └───────┬──────────┘ │ ┌───────▼──────────┐ 3. Page Cache(缓存层) │ generic_perform_ │ │ write() │ └───────┬──────────┘ │ ┌───────▼──────────┐ 4. Block Layer(I/O 提交层) │ submit_bio() │ └───────┬──────────┘ │ ┌───────▼──────────┐ 5. I/O 调度器(排序/合并) │ blk_mq (多队列) │ └───────┬──────────┘ │ ┌───────▼──────────┐ 6. 设备驱动(NVMe/SATA/SCSI) │ nvme_queue_rq() │ └───────┬──────────┘ │ ┌───────▼──────────┐ 7. 硬件(磁盘控制器 → NAND/盘片) │ DMA 传输 │ └──────────────────┘

二、第一层:VFS——不管你用 ext4 还是 XFS,接口都一样

// fs/read_write.c (简化)ssize_tvfs_write(structfile*file,constchar__user*buf,size_tcount,loff_t*pos){// 1. 安全检查:文件是否可写?用户传入的指针是否有效?if(!(file->f_mode&FMODE_WRITE))return-EBADF;// 2. 验证用户空间缓冲区if(!access_ok(buf,count))return-EFAULT;// 3. 调用具体文件系统的 write 实现// file->f_op->write 指向 ext4_file_write() 或 xfs_file_write()ret=file->f_op->write(file,buf,count,pos);// 4. 更新文件的修改时间(mtime)file_accessed(file);// 更新 atimereturnret;}

VFS 的职责:安全检查、用户空间内存验证、文件系统路由。性能开销极小。


三、第二层 + 第三层:文件系统 + Page Cache

3.1 ext4 的写入流程

// fs/ext4/file.c (简化)staticssize_text4_file_write(structkiocb*iocb,structiov_iter*from){// 如果文件以 O_DIRECT 打开,走直接 I/O(绕过 Page Cache)if(iocb->ki_flags&IOCB_DIRECT)returnext4_dio_write_iter(iocb,from);// 否则走缓冲 I/O(经过 Page Cache)——99% 的场景returngeneric_perform_write(iocb,from);}

3.2 Page Cache——写入的秘密就在这里

关键事实:默认情况下,write()不会直接写到磁盘。它把数据写入Page Cache(内存中的页面缓存),然后返回。实际的磁盘写入由内核在后台异步完成(称为「回写」或 writeback)。

// mm/filemap.c (简化)ssize_tgeneric_perform_write(structkiocb*iocb,structiov_iter*i){while(iov_iter_count(i)){// 1. 在 Page Cache 中找到或创建一个页(4KB)page=grab_cache_page_write_begin(mapping,index);// 2. 把用户空间的数据复制到这个页copied=iov_iter_copy_from_user_atomic(page,i,offset,bytes);// 3. 标记这个页为「脏页」(dirty page)// 这意味着它和磁盘上的内容不一致,需要后续写回__set_page_dirty_nobuffers(page);// 4. 如果脏页过多,触发后台回写balance_dirty_pages_ratelimited(mapping);pos+=copied;}}

Page Cache 的意义:

  1. 写缓冲——把多次小写入合并成一次大写入(减少 I/O 次数)
  2. 读缓存——下次读同一文件时直接从内存返回,零磁盘 I/O
  3. 预读——顺序读时提前把后续的页加载到 Page Cache

3.3 脏页回写——谁来决定什么时候写盘

脏页水位线(以 /proc/sys/vm/dirty_* 控制): ┌──────────────────────────────┐ 100% dirty_ratio (默认 20%) │ 强制回写:阻塞所有写入进程 │ 到达此线:write() 调用者被阻塞 ├──────────────────────────────┤ │ 后台回写:唤醒 flusher 线程 │ dirty_background_ratio (默认 10%) ├──────────────────────────────┤ │ 正常:只标记脏页,不写盘 │ └──────────────────────────────┘ 0%

Linux 有一组内核线程flusher(以前叫pdflush)专门负责脏页回写:

# 查看当前脏页配置sysctl-a|grepdirty# dirty_background_ratio: 脏页超过总内存的 10% 时启动后台回写# dirty_ratio: 脏页超过 20% 时阻塞写入进程,强制回写# dirty_expire_centisecs: 脏页超过 30 秒未回写,强制回写(3000 = 30s)# dirty_writeback_centisecs: flusher 线程每 5 秒醒来检查一次(500 = 5s)

注意:fsync()O_SYNC可以强制立即写盘,绕过 Page Cache 的延迟。数据库(如 PostgreSQL、MySQL)大量使用fsync()来保证事务的持久性——代价是吞吐量大幅下降。


四、第四层:Block Layer——最后的合并机会

当 Page Cache 决定回写时,它构建一个bio(block I/O)结构体,提交给 Block Layer。

// 一个 bio 描述一次块设备 I/O 请求structbio{sector_tbi_sector;// 起始扇区号(LBA)structbio*bi_next;// 下一个 bio(链表)unsignedshortbi_vcnt;// bio_vec 数组中的条目数structbio_vec*bi_io_vec;// 数据所在的内存页列表// ...};structbio_vec{structpage*bv_page;// 指向 Page Cache 中的页unsignedintbv_len;// 这个段(segment)的长度unsignedintbv_offset;// 页内偏移};

Block Layer 的优化——合并相邻请求:

提交前:三个独立的 bio bio1: 扇区 100-103 bio2: 扇区 104-107 ← 和 bio1 相邻! bio3: 扇区 200-203 合并后: bio1: 扇区 100-107 ← 合并为一个连续的请求 bio3: 扇区 200-203

这种合并对机械硬盘(HDD)来说极其重要——减少磁头寻道次数。对于 SSD,合并的作用是减少 I/O 命令数(NVMe 协议的快的是并行性,但合并仍能减少命令提交开销)。


五、第五层:I/O 调度器——给请求排队的「交通警察」

Block Layer 把 bio 转换成request,放入请求队列。I/O 调度器决定这些 request 的执行顺序。

5.1 机械硬盘时代的调度器

对于旋转磁盘(HDD),访问时间 = 寻道时间 + 旋转延迟 + 数据传输时间。寻道时间通常是 5-10ms,是绝对主导因素。

调度器策略适合
noop不排序,只合并SSD、虚拟机
deadline保证每个请求的延迟上限,同时尽量合并通用 HDD
cfq(完全公平队列)每个进程一个队列,轮转调度,公平分配带宽多用户系统
anticipatory完成一个请求后等 6ms,看有没有相邻请求(已被 deadline 取代)

deadline 调度器的工作原理:

请求按 LBA 排序的红黑树(用于合并): 扇区 50 → 100 → 150 → 200 → 300 请求按到达时间的 FIFO 队列(用于保证延迟上限): 读请求延迟上限:500ms(默认) 写请求延迟上限:5000ms(写不重要,可以等) 调度逻辑: 1. 先检查读 FIFO 队头——超过 500ms 了?立刻处理! 2. 否则从排序树中取相邻请求(合并优化) 3. 处理一批请求后,再检查写 FIFO——超过 5 秒了?立刻处理!

5.2 SSD 时代:blk-mq 多队列

SSD 没有旋转寻道的时间,内部有多个并行通道。传统单队列调度器成了瓶颈——请求队列本身的开销限制了 SSD 的性能。

Linux 3.13 引入了 blk-mq(多队列块层):

每个 CPU 核心一个软件队列(加锁开销极小): CPU 0 → [软队列0] ──┐ CPU 1 → [软队列1] ──┤ CPU 2 → [软队列2] ──┤→ [硬件调度队列] → 设备驱动 CPU 3 → [软队列3] ──┘ 特点: - 队列绑定到 CPU 核心 → 无锁或极少的锁 - 多个硬件队列 → 利用 NVMe 的多队列并行特性 - 不支持 I/O 排序(SSD 内部并行,LBA 排序没有意义)
# 查看当前的 I/O 调度器cat/sys/block/sda/queue/scheduler# 输出示例:[mq-deadline] none# 对于 NVMe SSD,用 none(完全不调度,直接下发)echonone>/sys/block/nvme0n1/queue/scheduler

六、第六层 + 第七层:设备驱动与硬件

6.1 NVMe vs SATA——协议层面的差异

维度SATA(AHCI)NVMe
命令队列深度最多 32最多 65536
队列数1最多 65536
中断方式每完成一个命令产生一次中断支持中断聚合(多个完成一次性通知)
延迟~100μs~10μs
协议栈SCSI 翻译层 → ATA原生 PCIe,几乎没有翻译开销

NVMe 是为闪存从头设计的协议,不需要兼容几十年前的硬盘控制器。这也是为什么 NVMe SSD 的延迟比 SATA SSD 低一个数量级——不是闪存更快,是协议层的开销更小

6.2 DMA——CPU 不搬数据

传统 I/O 方式(PIO,Programmed I/O)需要 CPU 逐字节从内存搬到磁盘控制器。对于 4KB 的一次写入,这就是几千条mov指令。

DMA(Direct Memory Access,直接内存访问)让磁盘控制器自己从内存拉数据:

不用 DMA: 用 DMA: CPU 搬数据到磁盘控制器 磁盘控制器直接从内存读 ┌──────┐ ┌────────┐ ┌──────┐ ┌────────┐ │ CPU │───→│ 控制器 │ │ CPU │ ←设置→ │ 控制器 │ └──────┘ └───┬────┘ └──────┘ └───┬────┘ │ │ ┌───▼──┐ 直接从内存读 → ┌───▼──┐ │ 磁盘 │ │ 磁盘 │ └──────┘ └──────┘

DMA 传输过程中,CPU 可以去做别的事(比如调度下一个进程)。传输完成后,磁盘控制器通过中断通知 CPU。


七、直接 I/O vs 缓冲 I/O——数据库的选择

模式描述write() 返回时数据去了哪
缓冲 I/O(默认)经过 Page Cache在内存中,等待后台回写
直接 I/O(O_DIRECT绕过 Page Cache直接提交给磁盘
同步 I/O(O_SYNC缓冲 I/O + 立即回写数据已写到磁盘
// 缓冲 I/O(默认)intfd1=open("file.txt",O_WRONLY);// 直接 I/O——数据库常用,自己管理缓存intfd2=open("file.txt",O_WRONLY|O_DIRECT);// 同步 I/O——每一步都确认落盘后才返回intfd3=open("file.txt",O_WRONLY|O_SYNC);

为什么数据库偏好直接 I/O?数据库(MySQL/PostgreSQL)有自己的缓冲池(buffer pool),Page Cache 的存在意味着数据被缓存了两遍——一次在数据库的 buffer pool,一次在 Page Cache。更糟的是,Page Cache 可能在一些情况下持有脏页,让数据库误以为数据已经安全落盘。用 O_DIRECT 避免了这层重复和不确定性。


八、动手实验:追踪一次 I/O

# 1. 用 strace 追踪 write 系统调用strace-etrace=write,open,closecat/etc/hostname>/dev/null# 2. 用 blktrace 追踪块设备 I/Osudoblktrace-d/dev/sda-o-|blkparse-i-# 3. 用 iostat 查看 I/O 统计iostat-x1# 4. 查看 Page Cache 使用情况cat/proc/meminfo|grep-E"Cached|Dirty|Writeback"# 5. 查看块设备队列参数cat/sys/block/sda/queue/nr_requests# 最大请求数cat/sys/block/sda/queue/max_sectors_kb# 单次请求最大大小cat/sys/block/sda/queue/scheduler# 当前使用的调度器cat/sys/block/nvme0n1/queue/scheduler# NVMe 通常是 "none"# 6. 强制回写所有脏页(小心!会短时卡 IO)sync# 7. 查看 I/O 栈各层延迟分布sudobpftrace-e'kprobe:vfs_write { @start[tid] = nsecs; } kretprobe:vfs_write /@start[tid]/ { @lat = hist((nsecs - @start[tid]) / 1000); delete(@start[tid]); }'# Ctrl+C 后输出延迟分布直方图

九、总结:七层 I/O 栈全景图

write(fd, buf, 4096) │ ▼ ┌──────────────────────────────────────────────────┐ │ VFS │ 安全检查、用户空间验证、文件系统路由 │ ├──────────┼──────────────────────────────────────┤ │ FS (ext4)│ extent 映射、块分配、日志 │ ├──────────┼──────────────────────────────────────┤ │ Page Cache│ 脏页标记、合并写入、延迟分配 │ ├──────────┼──────────────────────────────────────┤ │ Block Layer│ bio 构建、请求合并、blk-mq 分配 │ ├──────────┼──────────────────────────────────────┤ │ I/O Scheduler│ 排序(HDD)或无排序直接下发(NVMe) │ ├──────────┼──────────────────────────────────────┤ │ Driver │ NVMe/SATA 命令构建、DMA 设置 │ ├──────────┼──────────────────────────────────────┤ │ Hardware │ 磁盘控制器接收命令、NAND 写入/磁头寻道 │ └──────────┴──────────────────────────────────────┘

记住这三句话就够了:

  1. Page Cache 是 I/O 性能的根基——99% 的写入优化发生在这一层。
  2. 合并比调度更重要——减少 I/O 次数比排序 I/O 顺序收益大。
  3. SSD 改变了 I/O 调度的设计哲学——从「减少寻道」变成「降低命令开销 + 利用并行」。

参考来源:

  • Linux Kernel Documentation: block
  • Linux Kernel Documentation: NVMe
  • Love, R. (2010):Linux Kernel Development(3rd ed.), Addison-Wesley
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/22 1:13:03

Web 安全入门实战教程|Web 基础精讲(第一篇)

『Web安全』入门级实战教程——Web基础(一) 这是一个为开发、运维及安全从业者构建的: 系统的拆解Web安全的关键领域。 内容涵盖 “原理深入-实战驱动-体系构建” 为你提供一条清晰的进阶路径。 使你在面任何新型漏洞时,迅速定…

作者头像 李华
网站建设 2026/5/22 1:12:02

n8n工作流实战:用可视化编辑器打造你的第一个AI自动化流程

📅 2026年5月21日 | 💡 选题灵感:开源AI Agent热点 | 👤 作者:AI技术教程博主 前言 还在为重复性工作熬夜加班?或者花大价钱买各种自动化SaaS服务? 今天介绍一个让技术人和非技术人都能偷懒的神器——n8n。 n8n是什么?简单说,它是一个开源的工作流自动化工具,你…

作者头像 李华
网站建设 2026/5/22 1:09:06

torchtitan-npu:大模型训练框架快速上手实战

前言 去年帮一个高校实验室把Llama-3-70B的训练从8卡GPU迁移到64卡昇腾NPU集群,踩了整整两周的坑。最开始用的是原生PyTorch DDP,64卡跑起来NPU利用率只有42%,通信开销大到离谱。后来切换到torchtitan-npu这个框架,同样是64卡&…

作者头像 李华
网站建设 2026/5/22 1:03:02

图片怎么一键去水印?2026年免费去水印app排行榜实测推荐

在日常收藏美图、整理素材、编辑图文时,图片上的水印总是一大烦恼。从社交媒体平台下载的视频、截图的图片上,水印遮挡画面、影响美观,直接降低了内容的使用价值。特别是对自媒体创作者来说,批量处理带水印素材更是耗时又低效。随…

作者头像 李华
网站建设 2026/5/22 1:00:00

创业公司如何做好用户反馈管理

创业公司如何做好用户反馈管理 前言 我们产品上线第一个月,收到了很多用户反馈,有好的,有差的,有时候甚至同一天收到截然相反的意见。 一开始我们很迷茫:到底应该听谁的?后来我意识到,用户反馈不…

作者头像 李华