为减少内核态与用户态之间大量的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()