Linux学习第49天:Linux块设备驱动实验(一):Linux三大驱动之一
Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长
本章学习Linux三大驱动之一的块设备驱动,主要应用场景为存储设备。
本章的思维导图如下:
一、什么是块设备
块设备---存储设备
以块为单位进行读写访问
在机构上可以进行随机访问,使用缓冲区来暂时存放数据。
I/O算法也不同。
二、块设备驱动框架
1.block_device结构体
1 struct block_device {
2 dev_t bd_dev; /* not a kdev_t - it's a search key */
3 int bd_openers;
4 struct inode *bd_inode; /* will die */
5 struct super_block *bd_super;
6 struct mutex bd_mutex; /* open/close mutex */
7 struct list_head bd_inodes;
8 void * bd_claiming;
9 void * bd_holder;
10 int bd_holders;
11 bool bd_write_holder;
12 #ifdef CONFIG_SYSFS
13 struct list_head bd_holder_disks;
14 #endif
15 struct block_device *bd_contains;
16 unsigned bd_block_size;
17 struct hd_struct *bd_part;
18 /*number of times partitions within this device have been opened.*/
19 unsigned bd_part_count;
20 int bd_invalidated;
21 struct gendisk *bd_disk;/*bd_disk 成员变量,此成员变量为
gendisk 结构体指针类型。内核使用 block_device 来表示一个具体的块设备对象,比如一个硬盘
或者分区,如果是硬盘的话 bd_disk 就指向通用磁盘结构 gendisk。*/
22 struct request_queue *bd_queue;
23 struct list_head bd_list;
24 /*
25 * Private data. You must have bd_claim'ed the block_device
26 * to use this. NOTE: bd_claim allows an owner to claim
27 * the same device multiple times, the owner must take special
28 * care to not mess up bd_private for that case.
29 */
30 unsigned long bd_private;
31
32 /* The counter of freeze processes */
33 int bd_fsfreeze_count;
34 /* Mutex for freeze */
35 struct mutex bd_fsfreeze_mutex;
36 };
1).注册块设备
int register_blkdev(unsigned int major, const char *name)
/*
major: 主设备号。
name: 块设备名字。
返回值: 如果参数 major 在 1~255 之间的话表示自定义主设备号,那么返回 0 表示注册成
功,如果返回负值的话表示注册失败。如果 major 为 0 的话表示由系统自动分配主设备号,那
么返回值就是系统分配的主设备号(1~255),如果返回负值那就表示注册失败。
*/
2).注销块设备
void unregister_blkdev(unsigned int major, const char *name)
/*
major: 要注销的块设备主设备号。
name: 要注销的块设备名字。
返回值: 无。
*/
2.gendisk结构体
1 struct gendisk {
2 /* major, first_minor and minors are input parameters only,
3 * don't use directly. Use disk_devt() and disk_max_parts().
4 */
5 int major; /* major number of driver *///major 为磁盘设备的主设备号。
6 int first_minor;//first_minor 为磁盘的第一个次设备号。
7 int minors; /* maximum number of minors, =1 for
8 * disks that can't be partitioned. *///minors 为磁盘的次设备号数量,也就是磁盘的分区数
//量,这些分区的主设备号一样, 次设备号不同。
9
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 */
15
16 /* Array of pointers to partitions indexed by partno.
17 * Protected with matching bdev lock but stat and other
18 * non-critical accesses use RCU. Always access through
19 * helpers.
20 */
21 struct disk_part_tbl __rcu *part_tbl;/*
part_tbl 为磁盘对应的分区表,为结构体 disk_part_tbl 类型, disk_part_tbl 的核心
是一个 hd_struct 结构体指针数组,此数组每一项都对应一个分区信息。
*/
22 struct hd_struct part0;
23
24 const struct block_device_operations *fops;/*
fops 为块设备操作集,为 block_device_operations 结构体类型。和字符设备操作
集 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;
39 };
gendisk 操作函数:
申请 gendisk:
struct gendisk *alloc_disk(int minors)
函数参数和返回值含义如下:
minors: 次设备号数量, 也就是 gendisk 对应的分区数量。
返回值: 成功:返回申请到的 gendisk,失败: NULL。
删除gendisk:
void del_gendisk(struct gendisk *gp)
函数参数和返回值含义如下:
gp: 要删除的 gendisk。
返回值: 无。
将 gendisk 添加到内核:
void add_disk(struct gendisk *disk)
函数参数和返回值含义如下:
disk: 要添加到内核的 gendisk。
返回值: 无。
设置 gendisk 容量:
void set_capacity(struct gendisk *disk, sector_t size)
函数参数和返回值含义如下:
disk: 要设置容量的 gendisk。
size: 磁盘容量大小,注意这里是扇区数量。
调整 gendisk 引用计数:get_disk 是增加 gendisk 的引用计数, put_disk 是减少 gendisk 的引用计数。
truct kobject *get_disk(struct gendisk *disk)
void put_disk(struct gendisk *disk)
3.block_device_operation结构体
1 struct block_device_operations {
2 int (*open) (struct block_device *, fmode_t);/*open 函数用于打开指定的块设备*/
3 void (*release) (struct gendisk *, fmode_t);/*release 函数用于关闭(释放)指定的块设备。*/
4 int (*rw_page)(struct block_device *, sector_t, struct page *,
int rw);/*rw_page 函数用于读写指定的页。*/
5 int (*ioctl) (struct block_device *, fmode_t, unsigned,
unsigned long);/*ioctl 函数用于块设备的 I/O 控制。*/
6 int (*compat_ioctl) (struct block_device *, fmode_t, unsigned,
unsigned long);/*
compat_ioctl 函数和 ioctl 函数一样,都是用于块设备的 I/O 控制。区别在于在 64
位系统上, 32 位应用程序的 ioctl 会调用 compat_iotl 函数。在 32 位系统上运行的 32 位应用程
序调用的就是 ioctl 函数。*/
7 long (*direct_access)(struct block_device *, sector_t,
8 void **, unsigned long *pfn, long size);
9 unsigned int (*check_events) (struct gendisk *disk,
10 unsigned int clearing);
11 /* ->media_changed() is DEPRECATED, use ->check_events() instead */
12 int (*media_changed) (struct gendisk *);
13 void (*unlock_native_capacity) (struct gendisk *);
14 int (*revalidate_disk) (struct gendisk *);
15 int (*getgeo)(struct block_device *, struct hd_geometry *);/*getgeo 函数用于获取磁盘信息,包括磁头、柱面和扇区等信息。*/
16 /* this callback is with swap_lock and sometimes page table lock
held */
17 void (*swap_slot_free_notify) (struct block_device *,
unsigned long);
18 struct module *owner;/*owner 表示此结构体属于哪个模块,一般直接设置为 THIS_MODULE。*/
19 };
4.块设备I/O请求过程
1)、请求队列 request_queue
初始化请求队列:
request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
函数参数和返回值含义如下:
rfn: 请求处理函数指针,每个 request_queue 都要有一个请求处理函数,请求处理函数
request_fn_proc 原型如下:
void (request_fn_proc) (struct request_queue *q)
请求处理函数需要驱动编写人员自行实现。
lock: 自旋锁指针,需要驱动编写人员定义一个自旋锁,然后传递进来。,请求队列会使用
这个自旋锁。
返回值: 如果为 NULL 的话表示失败,成功的话就返回申请到的 request_queue 地址。
删除请求队列:
oid blk_cleanup_queue(struct request_queue *q)
函数参数和返回值含义如下:
q: 需要删除的请求队列。
返回值: 无。
分配请求队列并绑定制造请求函数:
struct request_queue *blk_alloc_queue(gfp_t gfp_mask)
函数参数和返回值含义如下:
gfp_mask: 内存分配掩码,具体可选择的掩码值请参考 include/linux/gfp.h 中的相关宏定义,
一般为 GFP_KERNEL。
返回值: 申请到的无 I/O 调度的 request_queue。
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
函数参数和返回值含义如下:
q: 需要绑定的请求队列,也就是 blk_alloc_queue 申请到的请求队列。
mfn:需要绑定的“制造”请求函数,函数原型如下:
void (make_request_fn) (struct request_queue *q, struct bio *bio)
“制造请求”函数需要驱动编写人员实现。
返回值: 无。
一般 blk_alloc_queue 和 blk_queue_make_request 是搭配在一起使用的,用于那么非机械的存储设备、无需 I/O 调度器,比如 EMMC、 SD 卡等。 blk_init_queue 函数会给请求队列分配一个 I/O 调度器,用于机械存储设备,比如机械硬盘等。
2)、请求request
获取请求:
request *blk_peek_request(struct request_queue *q)
函数参数和返回值含义如下:
q: 指定 request_queue。
返回值: request_queue 中下一个要处理的请求(request),如果没有要处理的请求就返回
NULL。
开启请求:
void blk_start_request(struct request *req)
函数参数和返回值含义如下:
req: 要开始处理的请求。
返回值: 无。
一步到位处理请求:
1 struct request *blk_fetch_request(struct request_queue *q)
2 {
3 struct request *rq;
4 5
rq = blk_peek_request(q);
6 if (rq)
7 blk_start_request(rq);
8 return rq;
9 }
其他和请求有关的函数:
3)、bio结构
bio 结构描述了要读写的起始扇区、要读写的扇区数量、是读取还是写入、页偏移、数据长度等等信息。
bio_vec 就是“page,offset,len”组合, page 指定了所在的物理页, offset 表示所处页的偏移地址, len 就是数据长度。
遍历请求中的bio:
#define __rq_for_each_bio(_bio, rq) \
if ((rq->bio)) \
for (_bio = (rq)->bio; _bio; _bio = _bio->bi_next)
_bio 就是遍历出来的每个 bio, rq 是要进行遍历操作的请求, _bio 参数为 bio 结构体指针类
型, rq 参数为 request 结构体指针类型。
遍历bio中的所有段:
#define bio_for_each_segment(bvl, bio, iter) \
__bio_for_each_segment(bvl, bio, iter, (bio)->bi_iter)
第一个 bvl 参数就是遍历出来的每个 bio_vec,第二个 bio 参数就是要遍历的 bio,类型为
bio 结构体指针,第三个 iter 参数保存要遍历的 bio 中 bi_iter 成员变量。
通知bio处理结束 :
bvoid bio_endio(struct bio *bio, int error)
函数参数和返回值含义如下:
bio: 要结束的 bio。
error: 如果 bio 处理成功的话就直接填 0,如果失败的话就填个负值,比如-EIO。
返回值: 无
以下内容将在下一个笔记中学习:
三、使用请求队列实验
1.实验程序编写
2.运行测试
四、不使用请求队列实验
1.实验程序编写
2.运行测试
本笔记为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。