块设备驱动是Linux 三大驱动类型之一。
1、块设备的概念
块设备是针对存储设备的,比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等。因此块设备驱动其实就是这些存储设备驱动。
块设备驱动相比字符设备驱动的主要区别如下:
块设备 | 字符设备 |
---|---|
以块为单位进行读写访问 | 以字节为单位进行数据传输 |
使用缓冲区来暂存数据,可以随机访问 | 顺序的数据流设备,不需要缓冲区,实时访问 |
2、块设备驱动框架
2.1、block_device结构体
linux 内核使用 block_device 表示块设备 ,block_device 为 一 个结构体 , 定义在include/linux/fs.h 文件中。重点成员变量:struct gendisk *bd_disk;
内核使用 block_device 来表示一个具体的块设备对象,比如一个硬盘或者分区,如果是硬盘的话 bd_disk 就指向通用磁盘结构 gendisk。
与字符设备驱动一样,需要向内核注册新的块设备、注销块设备。
int register_blkdev(unsigned int major, const char *name)
void unregister_blkdev(unsigned int major, const char *name)
2.2、gendisk结构体
linux 内核使用 结构体gendisk 来描述一个磁盘设备,定义在 include/linux/genhd.h中,
1 struct gendisk {
5 int major; /* 磁盘设备的主设备号 */
6 int first_minor; /* 磁盘的第一个次设备号 */
7 int minors;//minors 为磁盘的次设备号数量,也就是磁盘的分区数量,这些分区的主设备号一样,次设备号不同
10 char disk_name[DISK_NAME_LEN]; /* name of major driver */
11 char *(*devnode)(struct gendisk *gd, umode_t *mode);
12
13 unsigned int events; /* supported events */
14 unsigned int async_events; /* async events, subset of all */
21 struct disk_part_tbl __rcu *part_tbl;//为磁盘对应的分区表,disk_part_tbl 的核心是一个 hd_struct 结构体指针数组,此数组每一项都对应一个分区信息
22 struct hd_struct part0;
23
24 const struct block_device_operations *fops;//fops 为块设备操作集,和字符设备操作集 file_operations 一样,是块设备驱动中的重点!
25 struct request_queue *queue;//queue 为磁盘对应的请求队列,所以针对该磁盘设备的请求都放到此队列中,驱动程序需要处理此队列中的所有请求。
26 void *private_data;
27
28 int flags;
29 struct device *driverfs_dev; // FIXME: remove
30 struct kobject *slave_dir;
31
32 struct timer_rand_state *random;
33 atomic_t sync_io; /* RAID */
34 struct disk_events *ev;
35 #ifdef CONFIG_BLK_DEV_INTEGRITY
36 struct blk_integrity *integrity;
37 #endif
38 int node_id;
编写块的设备驱动的时候需要分配并初始化一个 gendisk,linux 内核提供了一组 gendisk 操作函数,
struct gendisk *alloc_disk(int minors);//申请 gendisk
void del_gendisk(struct gendisk *gp);//删除 gendisk
void add_disk(struct gendisk *disk);//将 gendisk 添加到内核
void set_capacity(struct gendisk *disk, sector_t size);//设置 gendisk 容量
//内核会通过 get_disk 和 put_disk 这两个函数来调整 gendisk 的引用计数
truct kobject *get_disk(struct gendisk *disk)//增加 gendisk 的引用计数
void put_disk(struct gendisk *disk)//减少 gendisk 的引用计数
2.3、 block_device_operations 结构体
块设备的操作集。和字符设备的 file_operations操作集基本类似。
2.4、 块设备I/O请求过程
在 block_device_operations 结构体中并没有找到 read 和 write 这样的读写函数,那么块设备是怎么从物理块设备中读写数据?这里就引出了块设备驱动中非常重要的 request_queue、request 和 bio。
内核将对块设备的读写都发送到请求队列 request_queue 中,request_queue 中是大量的request(请求结构体),而 request 又包含了 bio,bio 保存了读写相关数据,比如从块设备的哪个地址开始读取、读取的数据长度,读取到哪里,如果是写的话还包括要写入的数据等。