自学内容网 自学内容网

Linux内核 -- Linux 的 BIO框架

深入理解 Linux 的 BIO

在 Linux 内核中,BIO(Block I/O)是块层(block layer)用于描述块设备 I/O 请求的核心数据结构。它在文件系统与块设备驱动程序之间充当“载体”,负责把数据页及相关元数据从上层提交到下层(驱动程序)并跟踪数据的读写完成情况。本文将从以下几个方面对 BIO 进行详细讲解:

  • BIO 的背景与作用
  • BIO 数据结构核心字段
  • BIO 的生命周期
  • BIO 提交与处理流程
  • BIO 的常用操作接口
  • BIO 与 Request 的关系
  • 调试与参考

1. BIO 的背景与作用

在 Linux 内核中,块 I/O 流程大体可分为以下几个层次:

  • 文件系统层:处理逻辑文件地址与块地址之间的映射,如 ext4、xfs 等。
  • 通用块层(块 I/O 层):对上层提交的 I/O 进行排队、合并、调度等处理;核心数据结构包括 struct biostruct request、I/O 调度器等。
  • 设备驱动层:与具体硬件打交道,完成真正的磁盘/SSD I/O 操作。

BIO(struct bio)最初是为实现 I/O 的“向量化”而设计的,也就是可以把多个物理页的数据聚合到同一个结构中,一起提交给下层驱动,从而减少请求次数、提高效率。BIO 通常会被进一步封装进 struct request,在完成 I/O 调度后交由驱动处理。

简言之:

  • BIO:更“轻量”,描述一次 I/O 操作中需要使用的页、偏移量,以及操作类型(读/写/丢弃等)。
  • Request:通常是基于 BIO 进行的更高层次的“请求”打包,经 I/O 调度器处理后才会提交给硬件。

2. BIO 数据结构核心字段

struct bio 定义位于内核源码的 include/linux/bio.h 中(不同内核版本可能略有差别)。下面列出一些关键字段,并简要说明其含义:

struct bio {
    struct bio          *bi_next;       /* 连接在同一个请求上的其他 BIO */
    struct block_device *bi_bdev;       /* 目标块设备(如 /dev/sda 等) */

    unsigned int        bi_flags;       /* BIO 标志位,如是否只读、是否是分散聚合等 */
    unsigned short      bi_opf;         /* 操作类型和标志(op + flags),如 READ, WRITE 等 */
    sector_t            bi_iter.bi_sector;
                                        /* 起始扇区号(相对于块设备) */

    struct bio_vec      *bi_io_vec;     /* 用于描述所涉及的物理页及其在页内偏移的数组 */
    unsigned short      bi_vcnt;        /* 当前 bio_vec 数组中实际使用的元素数量 */
    unsigned short      bi_max_vecs;    /* bio_vec 数组可容纳的最大元素数量 */

    atomic_t            __bi_remaining; /* 该 BIO 合并的多个 I/O 子请求的完成计数 */
    bio_end_io_t        *bi_end_io;     /* I/O 完成回调函数 */
    void                *bi_private;    /* 供结束回调使用的私有数据 */

    /* ... 其他字段略 ... */
};

关键字段解释

  • bi_bdev:指明目标块设备。
  • bi_opf:表示具体操作类型(读写/丢弃/刷新等)以及操作标志(同步/分散聚合等)。
  • bi_iter.bi_sector:起始扇区号(逻辑扇区),BIO 需要从设备的哪个扇区开始进行操作。
  • bi_io_vec:BIO 使用的 page + offset + length 向量数组,指向内核中实际的数据页。
    • 其中 struct bio_vec 包含 bv_page, bv_offset, bv_len
  • bi_end_io:BIO 完成时的回调函数。BIO 最终完成时需要调用此函数来通知上层操作结束。
  • __bi_remaining:用于 BIO 的引用计数或子请求计数(在存在分片/拆分时很关键)。
  • bi_private:在提交时设置的私有数据指针,通常在完成回调时用于传递上下文。

3. BIO 的生命周期

1. 申请/初始化(Allocation / Initialization)

  • 上层调用 bio_allocbio_kmalloc 等接口分配 struct bio 结构。
  • 初始化关键字段,例如 bi_bdev, bi_opf, bi_end_io 等。

2. 添加数据(Attach Data)

  • 使用 bio_add_page__bio_add_page 将数据页添加到 bio->bi_io_vec 中。
  • 如果空间不足,BIO 需要拆分成多个 BIO。

3. 提交(Submit)

  • 数据页添加完毕后,通过 submit_bio 或类似接口将 BIO 提交到块层。

4. 执行 I/O(I/O Execution in Driver)

  • 底层驱动程序从调度层获取 BIO,进行实际硬件读写操作。

5. 完成回调(Completion)

  • 硬件完成 I/O 后,驱动层调用 bio->bi_end_io 回调函数,通知操作完成。
  • 回调函数负责处理错误码、统计信息等,之后释放 BIO。

4. BIO 提交与处理流程

下面给出一个简化的 BIO 提交流程图:

       +-----------------+
       |  文件系统/上层  |
       +--------+--------+
                |
                | bio_alloc/...
                v
       +-----------------+
       |    组装 BIO     |
       |  (pages, etc.) |
       +--------+--------+
                |
                | submit_bio()
                v
       +-----------------+
       |  通用块层/队列  |  <-- 此阶段可能进行合并、调度等
       +--------+--------+
                |
                | 提交到驱动
                v
       +-----------------+
       |   硬件驱动层    |
       +--------+--------+
                |
                | 硬件完成操作
                v
       +-----------------+
       | bio->bi_end_io()|
       +-----------------+

5. BIO 的常用操作接口

1. 分配与初始化

  • struct bio *bio_alloc(gfp_t gfp_mask, int nr_iovecs)
    分配一个新的 BIO,支持最多 nr_iovecsbio_vec

  • void bio_init(struct bio *bio, struct bio_vec *table, unsigned short maxvecs)
    初始化一个已分配的 BIO。

2. 添加数据

  • int bio_add_page(struct bio *bio, struct page *page, unsigned int len, unsigned int offset)
    将一个 page 的部分范围添加到 BIO。

3. 提交 BIO

  • void submit_bio(struct bio *bio)
    将 BIO 提交给块层处理。

4. 结束回调

  • void (*bio_end_io_t)(struct bio *bio)
    BIO 完成后调用的回调函数。

6. BIO 与 Request 的关系

  • BIO 是对要传输数据的描述,而 Request 是更高层次的请求封装。
  • 一个 Request 可以包含多个 BIO,方便进行合并和调度。
  • 调度器对 Request 进行排序后,提交给硬件驱动。

7. 调试与参考

调试

  • 使用 CONFIG_DEBUG_BLOCK_EXT_DEVT 等内核调试选项。
  • 通过 ftracebpftrace 跟踪 BIO 提交和完成过程。

源码参考

  • include/linux/bio.h
  • block/blk-core.c
  • block/blk-mq.c

总结

BIO 是 Linux 块 I/O 子系统的核心数据结构,用于描述一次 I/O 操作的页、扇区及元数据。它与 Request 紧密结合,共同完成从文件系统到硬件设备的 I/O 传输任务。掌握 BIO 的结构与生命周期对开发文件系统、块设备驱动以及优化性能非常重要。


原文地址:https://blog.csdn.net/sz66cm/article/details/145307031

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!