块驱动设备
与字符驱动设备对比
-
性能要求更高
-
请求处理更加复杂(调度策略)
-
存在着缓冲区,且以固定大小读写,支持随机读写
访问存储介质的架构
-
虚拟文件系统
-
磁盘文件系统 原始块设备
-
块
I/O
调度层 -
块设备驱动
-
硬件存储器
I/O
调度器
-
Noop I/O
调度器,即简单的FIFO
队列,进行基本的合并,适合Flash
的存储器。 -
Anticipatory I/O
调度器,试图推迟满足请求,以完成对请求的排序。对读临近区的请求在延时的几个微秒中一并执行。已被删除 -
Deadline I/O
调度器,尽可能的把每次请求的延迟降至最低。适合读取比较多的场合。 -
CFQ I/O
为所有任务分配均匀的I/O
带宽。 -
改变内核调度算法
-
内核传参改变调度算法
kernel elevator=deadline
-
使用如下命令改变内核调度算法
echo SCHEDULER > /sys/block/DEVICE/queue/scheduler
-
设备文件与普通文件的区别
- 概念上
普通文件
: 文本,可执行程序、媒体文件等常规文件设备文件
: 就是I/O设备。如挂载在内核/dev
上的设备,也能通过文件系统读写。- 类unix操作系统都是基于文件概念的,文件是由字节序列而构成的信息载体。那么就可以把
设备文件
当作可读写的I/O设备
。 - 设备文件通常分为
字符设备
、块设备
、网络设备
- 类unix操作系统都是基于文件概念的,文件是由字节序列而构成的信息载体。那么就可以把
- 寻址空间上
- 普通文件: 内核虚拟地址空间,普通文件比块设备文件多一层文件系统的地址转换机构。
- 设备文件:物理寻址空间
存储结构的划分
-
PAGE
页:内存映射的最小单位
-
Segment
段:在
PAGE
中被操作的单位,由若干个块组成 -
BLOCK
块:逻辑上进行数据存储的最小单位。
逻辑块的大小是在格式化的时候确定的, 一个
Block
最多仅能容纳一个文件(即不存在多个文件同一个Block
的情况.Block
是VFS
和文件系统传送数据的基本单位- Linux内核要求
Block_Size = Sector_Size *
$$2^n$$,并且Block_Size <= 内存的Page_Size(页大小)
- block对应磁盘上的一个或多个相邻的扇区,而
VFS
将其看成是一个单一的数据单元.
- Linux内核要求
- 块设备的
block
- 块设备的block的大小不是唯一的,创建一个磁盘文件系统时,管理员可以选择合适的扇区的大小,同一个磁盘的几个分区可以使用不同的块大小。
- 块设备文件的每次读或写操作是一种"原始"访问,因为它绕过了磁盘文件系统,内核通过使用最大的块
(4096)
执行该操作。
-
Sector
扇区:硬件I/O
设备存储数据的基本单位这个Sector就是
512byte
,和实际物理存储介质设备上的概念不一样。如果实际的设备的sector不是512byte,而是4096byte(eg SSD),那么只需要将多个内核sector对应一个ssd sector即可
块设备驱动核心结构
block_device
gendisk
- 一个物理磁盘或分区在内核中的描述
hd_struct
- 描述一个具体的磁盘分区
block_device_operations
- 描述磁盘的操作方法集
request_queue
- 表示针对一个gendisk对象的所有请求的队列,是相应gendisk对象的一个域
request
- 表示经过IO调度之后的针对一个gendisk(磁盘)的一个"请求",是request_queue的一个节点。多个request构成了一个request_queue
bio
- 表示应用程序对一个gendisk(磁盘)原始的访问请求,一个bio由多个bio_vec,多个bio经过IO调度和合并之后可以形成一个request。
bio_vec
- 描述的应用层准备读写一个gendisk(磁盘)时需要使用的内存页page的一部分,即上文中的"段",多个bio_vec和bio_iter形成一个bio
bio_iter
- 描述一个bio_vec中的一个sector信息
块设备核心方法
-
set_capacity()
设置gendisk
对应的磁盘的物理参数 -
blk_init_queue()
分配+初始化+绑定一个有IO调度的gendisk的requst_queue
,处理函数是void (request_fn_proc) (struct request_queue *q);
类型 -
blk_alloc_queue()
分配+初始化一个没有IO调度的gendisk的request_queue
-
blk_queue_make_request()
绑定处理函数到一个没有IO调度的request_queue
,处理函数函数是void (make_request_fn) (struct request_queue q, struct bio bio);
类型 -
__rq_for_each_bio()
遍历一个request
中的所有的bio
-
bio_for_each_segment()
遍历一个bio
中所有的segment
-
rq_for_each_segment()
遍历一个request
中的所有的bio
中的所有的segment
最后三个遍历算法都是用在request_queue绑定的处理函数中,这个函数负责对上层请求的处理。
3.16版本源代码
block_device
原型
1 | struct block_device { |
block_device是伪文件系统bdevfs中对块设备或设备分区的抽象,它唯一的对应于一个设备号(对分区来说,主设备号相同,次设备号不同)。
它的详细内容如上(include/linux/fs.h)
gendisk
原型
1 | 165 struct gendisk { |
1 | struct gendisk |
同样是面向对象的设计方法,Linux内核使用gendisk对象描述一个系统的中的块设备,类似于Windows系统中的磁盘分区和物理磁盘的关系,OS眼中的磁盘都是逻辑磁盘,也就是一个磁盘分区,一个物理磁盘可以对应多个磁盘分区,在Linux中,这个gendisk就是用来描述一个逻辑磁盘,也就是一个磁盘分区
1 | struct gendisk *alloc_disk(int minors); |
hd_struct
磁盘分区描述
hd_struct
用于描述一个具体的磁盘分区,其详细内容如下(include/linux/genhd.h)
1 | struct hd_struct { |
block_device_operations
原型
1 | //include/linux/blkdev.h |
1 | struct block_device_operations |
request_queue
原型
每一个gendisk对象都有一个request_queue对象,前文说过,块设备有两种访问接口,一种是/dev下,一种是通过文件系统,后者经过IO调度在这个gendisk->request_queue上增加请求,最终回调与request_queue绑定的处理函数,将这些请求向下变成具体的硬件操作.
从驱动模型的角度来说, 块设备主要分为两类需要IO调度的和不需要IO调度的, 前者包括磁盘, 光盘等, 后者包括Flash, SD卡等, 为了保证模型的统一性 , Linux中对这两种使用同样的模型但是通过不同的API来完成上述的初始化和绑定
1 | 294 struct request_queue { |
1 | struct request_queue |
request
原型
1 | struct request { |
1 | struct request |
bio
原型
bio用来描述单一的I/O请求,它记录了一次I/O操作所必需的相关信息,如用于I/O操作的数据缓存位置,,I/O操作的块设备起始扇区,是读操作还是写操作等等
1 |
|
1 | struct bio |
bio_vec bio_iter
1 | 25 struct bio_vec { |
1 | struct bio_vec |
遍历函数
1 | 738 |
测试步骤
源代码可参考sbull
一个ramdisk块设备实现
接下来即可进行测试
ll /dev | grep sbull
- 分区:
fdisk /dev/sbull
- 格式化,指定文件系统:
mkfs.ext4 /dev/sbull
- 挂载:
mount /dev/sbull /mnt
- 查看磁盘物理分区信息:
cat /proc/partitions
- 查看磁盘分区占用情况:
df -ahT
API
的更改
由于内核代码的不断修改,许多函数的接口都已经发生变化。编译起来报错不断。一个个慢慢goolge修改。参考了以下资料链接。
__bio_kmap_atomic
被删除
This helper doesn’t buy us much over calling kmap_atomic directly.
In fact in the only caller it does a bit of useless work as the
caller already has the bvec at hand, and said caller would even
buggy for a multi-segment bio due to the use of this helper.
So just remove it.
Signed-off-by: default avatarChristoph Hellwig hch@lst.de
Signed-off-by: default avatarJens Axboe axboe@kernel.dk
struct request
中 cmd_type
被删除
From Christoph Hellwig <>
Subject [PATCH 08/10] block: introduce blk_rq_is_passthrough
Dat Tue, 31 Jan 2017 16:57:29 +0100
This can be used to check for fs vs non-fs requests and basically
removes all knowledge of BLOCK_PC specific from the block layer,
as well as preparing for removing the cmd_type field in struct request.
Signed-off-by: Christoph Hellwig hch@lst.de
bio_rw
has been removed
changed to bio_datadir()
1 | /* |
blk_queue_bounce_limit(BLK_BOUNCE_ANY)
has been set to default
BLK_BOUNCE_ANY is the defauly now, so the call is superflous.
blk_queue_ordered()
changed
deprecate barrier and replace blk_queue_ordered() with blk_queue_flush()
replaced with simpler blk_queue_flush().
but blk_queue_flush
is also removed
Updateto use the newer blk_queue_write_cache()
struct bio
changed some values
-
struct block_device bio->bi_bdev
changed tobio->gendisk
-
sector of
bio
has moved tostruct bio_iter
-
and the definetion of bio has moved to
blk_types.h