块 I/O 层

嵌入式学习Day4


1.块设备

系统中能够随机(不需要按顺序)访问固定大小的数据片,称之为块设备 常见的块设备有:



内核不直接用硬件的“扇区”作为最小单位?


2.缓冲区和缓冲头

2.1为什么会有“缓冲区”

内核如果读硬盘中的数据时,都需要访问硬盘就会很慢,所以内核第一次访问硬盘时就会将读取的块数据保存到内存中,下次如果还要访问同一个块,就先看这个缓冲区里有没有。

2.2什么是缓冲头 buffer_head

光有一块内存还不够,内核还得知道:


2.3buffer_head结构体

2.3.1b_state —— 状态位,最核心

它记录这个缓冲区当前的状态,比如常见的有:


2.3.2b_blocknr —— 对应的块号

sector_t b_blocknr;

这个字段表示:这个 buffer_head 描述的是块设备上的哪一个块。

2.3.3b_bdev —— 属于哪个块设备

struct block_device *b_bdev;

光有块号还不够,因为不同设备上都可能有第 100 号块。

2.3.4b_size —— 这块缓冲区有多大

size_t b_size;

2.3.5b_data —— 真正的数据指针

char *b_data;

它指向缓冲区里真正存放块数据的位置。


2.3.5b_this_page —— 同一页中的 buffer 串起来

struct buffer_head *b_this_page;

把同一个 page 里的多个 buffer_head 串成一个链。

2.3.6b_count —— 引用计数

atomic_t b_count;

3.BIO

可以。bio 是现代 Linux 块层里比 buffer_head 更重要的一个概念。 如果说 buffer_head 更像“描述一个块”,那 bio 更像“描述一次 I/O 请求”。

3.1bio解决的是什么问题

块设备 I/O 不再只是“单块单块”做。 现代内核更希望:

所以 bio 的目标就是: 把一次 I/O 需要的内存片段和设备位置信息组织起来,交给块层往下送。


3.2bio中重要的结构体

3.2.1 I/O 目标

bi_bdev——对哪个块设备做 I/O

struct block_device *bi_bdev;

bi_sector—— 从磁盘哪个扇区开始

sector_t bi_sector;

3.2.2 I/O 性质

bi_rw —— 这是读还是写

bi_flags —— 状态和控制标志


3.2.3 描述内存数据

bi_io_vec —— 最重要的数据描述字段

struct bio_vec *bi_io_vec;

它指向一组 bio_vec,而每个 bio_vec 描述一个内存片段。

bio_vec 可以简单理解为:

这就是 bio 为什么适合现代块层:


bi_vcnt —— 有多少个 bio_vec

unsigned short bi_vcnt;

bi_idx —— 当前处理到第几个段

unsigned short bi_idx;

当前这个 bio 已经处理到 bio_vec 数组里的第几个元素。

3.2.4 描述总长度/分段情况

bi_size —— 这次 I/O 还剩多少数据

unsigned int bi_size;

这个字段通常表示:


bi_end_io —— I/O 完成回调

bio_end_io_t *bi_end_io;

这个 bio 完成之后,调用哪个函数做收尾。

bi_private —— 上下文私有数据

void *bi_private;

这个字段通常用来挂: 拥有这个 bio 的上层私有上下文。 因为回调 bi_end_io 被调用时,往往还需要知道:

这时就把一些私有信息塞进 bi_private。


4. request和bio、 bio_vec、page之间的关系

page 是数据真正待在内存里的地方,bio_vec 是对某一段 page 的描述,bio 是把多个 bio_vec 组织成“一次 I/O”,request 是块层把一个或多个 bio 再整理后交给驱动的“设备请求”。

5.I/O 调度程序

I/O 调度程序(I/O scheduler),也叫块 I/O 调度器,是在块层里专门负责“怎么把一堆 I/O 请求排队、合并、重排,再交给设备”的那一层。

I/O 调度程序的任务,不是产生 I/O,而是决定“已有的 I/O 请求按什么顺序、以什么方式发给设备”,从而兼顾吞吐量、延迟和公平性。


5.1为什么需要 I/O 调度

假设系统里同时有很多 I/O 请求:

这些请求如果“谁先来就谁先发”,通常效果不好,因为块设备有自己的物理特性。 尤其是机械硬盘:

所以 I/O 调度程序需要做几件事: 合并相邻请求/调整顺序,减少磁头来回跳/避免某个进程长期饿死/让交互型 I/O 不要延迟太大


5.2I/O 调度程序处于读取内存的哪一个部分

page -> bio_vec -> bio -> request -> I/O scheduler -> driver -> device

5.3“电梯算法”是什么

讲 I/O 调度,常会提到电梯算法(elevator algorithm)。

它的想法很像电梯:

这样比“每次都跳到最近到达的请求”更高效、更平稳。

这类思想的目标是:


5.4常见 I/O 调度器思想

5.4.1NOOP

5.4.2Deadline

5.4.3CFQ(Complete Fair Queuing)