linux IO 简介
文章目录
sector
硬盘上的数据最小单位是 sector,每个 sector 大小为 512 byte。所有的文件在硬盘上占用的大小都是 512 byte 的整数倍,一个文件在硬盘上可能是连续的 sector,也可能不连续的。比如文件 A 一开始写入 sector 1-10,然后写入文件 B,占用了 sector 11-20。之后文件 A 追加新的内容,这时因为 sector 11-20 已经被占用,这样文件 A 对应的 sector 就变成 1-10 和 21-30 了。这种不连续的 sector 分配就被称为硬盘碎片。
硬盘读写连续的 sector 速度比不连续的 sector 要快,因此文件尽量放入连续的 sector,读写时尽量连续的读写。系统可以在空闲时,重新整理硬盘的 sector 分配,将文件 A 的 sector 变成 1-20,将文件 B 变成 21-30,这就是碎片整理。
现代的文件系统在分配文件时,一般会把文件分散在硬盘上,互相留出空间,这样在追加内容时,就不会互相交错了。
比如文件 A 在创建时,尽管只有 10 个 sector 的数据,但仍然占用 100 个 sector。这样后续的增加的数据仍然时连续分布的。
block
kernel 处理数据的单位称为 block,每个 block 包含若干个连续的 sector 大小的内存。block 的大小为 512b,1k 或者 4k 之一,也就是说一个 page 可能包含 8 个,4 个或 1 个 block。
当要写数据到硬盘时,系统会先为要写入的数据创建 block,建立 block 和硬盘 sector 之间的映射关系(这个 block 中的数据应该写入硬盘的哪些 sectors),然后数据被写入 block,在适当的时候写入硬盘对应的 sector,这个从 block 写入硬盘的过程称为回写。
buffer_head
buffer_head 保存了在硬盘上保存数据的 sector 与在内存中保存数据的 block 的映射关系。每个 block 都有一个对应的 buffer_head。
buffer_head 保存的是某个 block 的位置,以及这个 block 应该写入的硬盘上的位置。
|
|
可以看出来,每个 block 由三个参数标识:
- 这个 block 所在的 page
- 这个 block 在 page 中的开始位置
- 这个 block 的大小
bio
buffer_head 对应了一个 block,但是每次读写操作,可以包含多个不连续的 block。这是因为每个读写操作,不是用 buffer_head,而是由另一个数据结构: bio 来代表的。
每个 bio 包含了一个或多个要在本次 IO 操作读/写入硬盘的内存段(momory segment),每个内存段由一个 bio_vec 结构来代表。
|
|
每个 bio_vec 包含的内存必须是连续的,但是一个 bio 中包含的 bio_vec,在内存上不连续,而是通过 buffer_head 映射之后,得到的 sector 在硬盘上连续。

硬盘控制器可以将不连续内存的数据一次写入多个连续的 sector,这种能力就叫做 scatter gather。
request
一个或多个读写操作(bio)组成了一个读写请求(request)。因为硬盘控制器可以将多个不连续的内存写入多个连续的 sector,所以一个 request 可以包含多个 bio。
每个硬盘设备都会有对应的 request 队列,设备驱动负责从队列中取出 request 并处理。为了提高效率,linux kernel 实现了调度器机制。调度器的基本工作是 merge 和 sort。
IO request merge
如果有多个 request 写入硬盘上连续的 sector,那么调度器将它们合并为一个 request,这样硬盘控制器就可以一次处理这个 requst 中包含的 bio。
iostat 中显示的 rqm/s 就是这个设备在自己的 request 队列中,1 秒内 merge 的request。合并之后的 request 被 device 处理, r/s 表示的是 1 秒内 device 处理的 request 数量, areq-sz 是一秒内处理的 request 包含的平均数据大小。所以 r/s * areq-sz /s 得到的就是 一秒内写入的数据大小。
IO request 排序
调度器会根据 request 写入硬盘的 sector 位置进行排序,尽量将临近的 request 排到一起,这样可以减少硬盘寻址的事件,提高效率。
文章作者 Griffin
上次更新 2020-12-31