在内核态Block Layer访问块设备

为减少内核态与用户态之间大量的copy_from/to_user 内存拷贝以及定制I/O软件流程,可以在内核态直接访问块设备。

代码示例

struct block_device *blkdev = blk_get_by_path("/dev/block/by-name/boot", FMODE_READ, THIS_MODULE);

loff_t blkdev_size = i_size_read(blkdev->bd_inode);

#define MAX_BIOVEC 256
struct bio_vec bio_vec[MAX_BIOVEC];
struct page* page_buffer[MAX_BIOVEC];

for (i = 0; i < MAX_BIOVEC; i++) {
    page_buffer[i] = alloc_page(GFP_KERNEL);
}

注:每个 struct bio_vec 只能指向一个 page 或者多个物理内存连续的 pages;

struct bio *bio = bio_alloc(GFP_KERNEL, 1);

loff_t page_count = blkdev_size / PAGE_SIZE;
for (page_off = 0; page_off < page_count; page_off += MAX_BIOVEC) {
    loff_t read_pages = (page_off+MAX_BIOVEC) > page_count? page_count - page_off : MAX_BIOVEC;
    bio_reset(bio);
    bio_init(bio, bio_vec, read_pages);
    bio_set_dev(bio, blkdev);

    for (i = 0; i < read_pages; i++) {
        bio_add_page(bio, page_buffer[i], PAGE_SIZE, 0);
    }

    bio->bi_iter.bi_sector = page_off * (PAGE_SIZE / 512);

    bio_set_op_attrs(bio, REQ_OP_READ | REQ_SYNC, 0);

    submit_bio_wait(bio);

    struct bio_vec bvec;
    struct bvec_iter iter;
    bio_for_each_bvec(bvec, bio, iter) {
        // bvec.bv_page
        // bvec.bv_len
    }
}

重要的数据结构和函数

// include/linux/blk_types.h
/*
 * main unit of I/O for the block layer and lower layers (ie drivers and
 * stacking drivers)
 */
struct bio {
    struct bio            *bi_next;    /* request queue link */
    struct block_device   *bi_bdev;
    blk_opf_t             bi_opf;        /* bottom bits REQ_OP, top bits req_flags.*/
    unsigned short        bi_flags;    /* BIO_* below */
    blk_status_t          bi_status;
    struct bvec_iter      bi_iter;
    bio_end_io_t          *bi_end_io;
    void                  *bi_private;
    ......
    unsigned short        bi_vcnt;    /* how many bio_vec's */
    /*
     * Everything starting with bi_max_vecs will be preserved by bio_reset()
     */
    unsigned short        bi_max_vecs;    /* max bvl_vecs we can hold */
    struct bio_vec        *bi_io_vec;    /* the actual vec list */
};

//include/linux/bvec.h
struct bio_vec {
    struct page    *bv_page;
    unsigned int    bv_len;
    unsigned int    bv_offset;
};

struct bvec_iter {
    sector_t         bi_sector;    /* device address in 512 byte sectors */
    unsigned int     bi_size;     /* residual I/O count */
    unsigned int     bi_idx;        /* current index into bvl_vec */
    unsigned int     bi_bvec_done;    /* number of bytes completed in current bvec */
} __packed;

Immutable biovecs and biovec iterators

从 3.13 开始,biovecs 不应在提交 bio 后进行修改。相反,我们有一个新的 struct bvec_iter 表示一个 biovec 的范围——这个迭代器将在 bio 完成时被修改,而不是 biovec。

更具体地说,需要部分完成 bio 的旧代码将更新 bi_sector 和 bi_size,并将 bi_idx 推进到下一个 biovec。如果它在 biovec 中途结束,它将增加 bv_offset 并减少 bv_len,增加的字节数为该 biovec 中完成的字节数。

在新方案中,为了部分完成 bio 而必须改变的所有内容都被隔离到结构 bvec_iter 中:bi_sector、bi_size 和 bi_idx 已移到那里;而不是修改bv_offset和bv_len,struct bvec_iter有bi_bvec_done,代表当前bvec完成的字节数。

有一堆新的辅助宏用于隐藏血淋淋的细节——特别是,呈现部分完成的 biovecs 的错觉,以便普通代码不必处理 bi_bvec_done。

  • 驱动程序代码不应再直接引用 biovecs;我们现在有 bio_iovec() 和 bio_iter_iovec() 宏,它们返回文字结构 biovecs,从原始 biovecs 构造但考虑了 bi_bvec_done 和 bi_size

bio_for_each_segment() 已更新为采用 bvec_iter 参数而不是整数(对应于 bi_idx);对于很多代码,转换只需要将参数类型更改为 bio_for_each_segment()。

  • 推进 bvec_iter 是用 bio_advance_iter() 完成的; bio_advance() 是 bio_advance_iter() 的包装器,它在 bio->bi_iter 上运行,并且如果存在 bio 完整性的 iter 也会提升它。

有一个较低级别的高级函数 - bvec_iter_advance() - 它接受一个指向 biovec 的指针,而不是 bio;这是由bio完整性代码使用的。

从 5.12 开始,不支持 bv_len 为零的 bvec 段。

这一切给我们带来了什么?

拥有一个真正的迭代器,并使 biovecs 不可变,具有许多优点:

  • 以前,当您一次不只处理一个 bvec 时,遍历 bios 是非常尴尬的 - 例如,block/bio.c 中的 bio_copy_data(),它将一个 bio 的内容复制到另一个。因为 biovec 的大小不一定相同,所以旧代码非常复杂 - 它必须同时遍历两个不同的 bios,同时将 bi_idx 和 offset 都保留到当前的 biovec 中。

新代码更简单 - 看看。这种模式出现在很多地方;许多驱动程序之前本质上是开放编码的 bvec 迭代器,并且具有通用实现大大简化了很多代码。

  • 以前,任何可能需要在 bio 完成后使用 biovec 的代码(可能将数据复制到其他地方,或者如果出现错误可能需要在其他地方重新提交)必须保存整个 bvec 数组 - 同样,这在很多地方都在进行。

  • biovecs 可以在多个 bios 之间共享 - bvec iter 可以表示现有 biovec 的任意范围,从 biovecs 的中间开始和结束。这就是能够有效拆分任意 bios 的原因。请注意,这意味着我们_仅_使用 bi_size 来确定何时到达 bio 的末尾,而不是 bi_vcnt - 并且 bio_iovec() 宏在构建 biovecs 时会考虑 bi_size

  • 拆分 bios 现在要简单得多。旧的 bio_split() 甚至不能在超过一个 bvec 的 bios 上工作!现在,我们可以有效地拆分任意大小的 bios——因为新 bio 可以共享旧 bio 的 biovec。

必须注意确保 biovec 在拆分 bio 仍在使用时不被释放,以防原始 bio 先完成。拆分 bios 时使用 bio_chain() 有助于此。

  • 提交部分完成的 bios 现在完全没问题 - 这偶尔会出现在堆栈块驱动程序中,各种代码(例如 md 和 bcache)对此有一些丑陋的解决方法。

过去,提交部分完成的 bio 对_大多数_设备都可以正常工作,但由于访问原始 bvec 数组是常态,因此并非所有驱动程序都会尊重 bi_idx 并且那些会中断。现在,由于所有驱动程序_必须_通过 bvec 迭代器 - 并且已经过审核以确保它们是 - 提交部分完成的 bios 是完全可以的。

其他影响:

  • 几乎所有 bi_idx 的用法现在都是不正确的并且已被删除;相反,以前使用 bi_idx 的地方现在使用 bvec_iter,可能将其传递给辅助宏之一。

IE。不再使用 bio_iovec_idx()(或 bio->bi_iovec[bio->bi_idx]),您现在使用 bio_iter_iovec(),它接受一个 bvec_iter 并返回一个文字结构 bio_vec - 从原始 biovec 动态构建,但考虑到bi_bvec_done(和 bi_size)。

  • bi_vcnt 不能被驱动程序代码信任或依赖 - 即任何实际上不拥有 bio 的东西。原因有两个:首先,实际上不再需要迭代 bio - 我们只使用 bi_size。其次,当克隆一个 bio 并重用(一部分)原始 bio 的 biovec 时,为了计算新 bio 的 bi_vcnt,我们必须迭代新 bio 中的所有 biovec——这很愚蠢,因为它不需要。

所以,不要再使用 bi_vcnt 了。

  • 当前接口允许块层根据需要拆分 bios,因此我们可以消除很多复杂性,尤其是在堆叠驱动程序中。创建 bios 的代码可以创建方便的任何大小的 bios,更重要的是,堆栈驱动程序不必处理它们自己的 bio 大小限制和底层设备的限制。因此,无需为各个块驱动程序定义 ->merge_bvec_fn() 回调。

助手的使用:

  • 以下名称后缀为 _all 的助手只能用于non-BIO_CLONED bio。它们通常由文件系统代码使用。Drivers 不应该使用它们,因为bio可能在到达driver之前就已经分裂了。

bio_for_each_segment_all()
bio_for_each_bvec_all()
bio_first_bvec_all()
bio_first_page_all()
bio_last_bvec_all()
  • 以下助手遍历单页段。传递的 'struct bio_vec' 将在迭代期间包含一个单页 IO 向量:

bio_for_each_segment()
bio_for_each_segment_all()
  • 以下助手迭代多页 bvec。传递的 'struct bio_vec' 将在迭代期间包含一个多页 IO 向量:

bio_for_each_bvec()
bio_for_each_bvec_all()
rq_for_each_bvec()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zs.w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值