Linux IO Stack 分析及调优实践

本文首先介绍Linux IO Stack 的整体链路,然后结合相关工具,跟踪IO的性能,并且根据实践中的场景,介绍如何进行IO的调优。 Linux IO Stack 文件读写的方式 根据上图,我们总结下文件读写的几种方式。 使用标准库的文件读写函数 直接根据文件描述符进行读写。先说明写数据的情况。主要两种情形: 在不使用Fsync 的情况下,实际写入Page Cache 就成功返回。 后续会由内核的线程异步将数据刷入磁盘。在系统内存充足的情况下,写入速度很快。但是内存紧张的情况下,可能涉及脏页的写入&内存的页替换,会有延迟的情况。如果写完数据,立即进行读取的话,速度也会很快,读取也是优先从Page Cache 读取。 如果涉及到WAL日志文件,防止意外断电情况下的数据丢失,会使用Fsync进行写入。这时也会写入到Page Cache ,但是会阻塞,直到提交到块IO,并且完成实际磁盘设备的写入才返回。这里有个优化的点,可以使用DSync 的模式,这样只会写入数据,而不进行元数据的更新,从而减少IO的调用。 在数据读取时,会先从Page Cache 读取。如果Page Cache 中没有,才会从磁盘读取。读取的性能一般会很高,尤其是顺序读取的情况下。 系统会进行预读,从而提前加载数据到Page Cache。 直接IO (O_DIRECT) 在打开文件时,设置 O_DIRECT 标志位,即可开启直接IO。Direct IO 的写入,不会经过Page Cache, 直接进行块IO的提交。这里会减少系统内核函数的调用。简化IO链路。这里不会涉及预读,需要上层应用控制。比如数据库软件都会使用Direct IO 来进行文件读写。 O_DIRECT 的写入,需要内存地址的对齐,以及IO的大小也需要对齐。否则会报错。 这里也可以单独设置Fsync的标志,如果没有设置,提交到块IO就返回了。否则需要等待磁盘的写入完成才返回。 使用 mmap 系统调用 mmap 系统调用可将文件映射到进程的虚拟地址空间,允许通过内存指针直接读写文件内容。 当使用 MAP_SHARED 模式写入时,数据会修改内核的 Page Cache 中对应的页面,并将其标记为“脏页”。 随后,内核的回写线程(如 flusher)会在后台异步地将这些脏页写入磁盘。 若需确保数据已持久化,应用程序应显式调用 msync()。 使用mmap 实现了零拷贝的(Zero Copy)机制,避免了数据在用户空间和内核空间之间的复制。 在读取文件时,使用 mmap 的方式居多。 当读取文件时,在内存中不存在的情况下,触发缺页中断,加载之后再返回。当内存紧张时,内存页可能被换出。 在mmap打开的内存地址,可以进行madvise调用,灵活的调整访问方式。 上面介绍的几种读写方式都是同步IO的方式,如果使用异步AIO,可以参考io_uring的使用,这里有介绍,即使使用AIO,也同样复用上图的IO Stack。 Block Layer 不管是通过Page Cache, 还是Direct IO, 最终都是提交到块IO。当数据写入到Page Cache 后,达到一定的阈值(内核脏页占比,内核脏页字节数量,过期时间)等等,会有内核线程把数据提交到块IO。在现代Linux内核中,块IO是由MQ(多队列)实现的。...

January 11, 2026 · 2 min · 366 words

io_uring 学习

io_uring 是 Linux 内核提供的异步 IO 接口,它可以用于替代传统的同步 IO 接口,如 read、write 等。io_uring 提供了一种基于事件循环的异步 IO 模型,能够在单个线程中处理多个 IO 操作,从而提高 IO 操作的并发性和效率。io_uring 从内核5.1版本开始引入,在这之前,AIO 提供了异步IO 的功能。但是存在诸多的限制,包括 只能作用于文件IO, 不能作用于网络IO. 在文件IO 中,只能使用O_DIRECT标记打开文件,无法使用buffer cache 。这样使用的范围比较少,只有在数据库领域有应用。 不能完全提供异步功能,如果元数据不可用时,也得同步等待 不能完全做到zero-copy 在某些设备场景中,无法做到真正异步,也得同步等待 io_uring 使用统一的接口解决异步IO问题,包括文件IO 和 网络IO。非阻塞的接口,减少了线程上下文的切换。并且可以使用系统的buffer cache 。 io_uring 主要的数据结构包括两个ring buffer 。 Submission Queue (SQ):用于提交IO请求的环形缓冲区。应用程序将IO请求放入SQ中,内核从SQ中获取请求并处理。 IO 请求放入tail 中, 内核通过head 读取进行处理。 Completion Queue (CQ):用于存储IO操作完成的结果。内核将IO操作的完成结果放入tail中,应用程序从head中获取结果并处理。 io_uring 提供一系列系统调用的接口,目前可以通过liburing 库来更方便的使用。 在这个库的examples 目录下自带了很多的使用例子。具体的例子也可以通过这里有更多的解释。 liburing-cp 这是一个文件IO的例子,使用liburing 实现的 cp 命令。从这里 看到最新的代码。 初始化 io_uring 实例, entries 是 SQ 的大小, 一般设置为 2 的幂次方。 通常情况下CQ 是SQ 大小的2倍。...

October 22, 2025 · 2 min · 365 words