linux驱动由浅入深系列:块设备驱动之二(从用户空间的read、write到实际设备物理操作整体架构分析)
linux驱动由浅入深系列:块设备驱动之三(块设备驱动结构分析,以mmc为例)
块设备驱动的模型还是基本基于字符设备驱动的,可以简单理解为块设备仅仅增加了操作缓冲区,对用户操作请求进行队列重排。因此只在有了字符驱动框架的基础上,本文重点介绍块设备独有的队列操作相关函数。
重要结构体
struct gendisk是块设备通用结构体,定义如下:
linux-3.16.56 / include / linux / genhd.h
相关函数
1,int register_blkdev(unsigned int major, const char *name);
创建一个块设备,当major==0时,表示动态创建,创建成功会返回一个主设备号
2,struct gendisk *alloc_disk(int minors);
分配一个gendisk结构,minors为分区数,填1表示不分区
3,request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);
分配一个request_queue请求队列,分配成功返回一个request_queue结构体
rfn: request_fn_proc结构体,用来执行放置在队列中的请求的处理函数
lock:队列访问权限的自旋锁(spinlock),该锁通过DEFINE_SPINLOCK()来定义
4,static DEFINE_SPINLOCK(spinlock_t lock);
定义一个自旋锁(spinlock)
5,static inline void set_capacity(struct gendisk *disk, sector_t size);
设置gendisk结构体的扇区数
6,void add_disk(struct gendisk *gd);
向内核中注册gendisk结构体
7,struct request *blk_fetch_request(request_queue_t *q);
获取申请队列中未完成的申请,获取成功返回一个request结构体,不成功返回NULL
linux2.6内核中该函数为 elv_next_request()
mmc块设备驱动分析
1,设备的定义一般位于DTS文件中,开机会遍历DTS树,注册设备,这部分就不展示了,DTS相关内容欢迎查看本博客文章:
Linux DTS(Device Tree Source)设备树详解之一
我们从probe函数开始分析:
linux-3.16.56/drivers/mmc/card/block.c
a,其中mmc_blk_alloc_parts()函数会分配gendisk结构体,创建请求队列等
b,之后mmc_add_disk()中会调用add_disk(md->disk);(文章开头介绍的常用函数之一)向内核注册块设备。
linux-3.16.56 / drivers / mmc / card / block.c
其中遍历磁盘上的分区,逐一调用mmc_blk_alloc_part()分配gendisk结构体。(mmc_blk_data中含有gendisk成员)
3,mmc_blk_alloc_part()函数定义在:
linux-3.16.56/drivers/mmc/card/block.c
4,实际分配空间的函数在mmc_blk_alloc_req(),这是比较核心的一个初始化函数,定义在:
linux-3.16.56/drivers/mmc/card/block.c
其中md->disk = alloc_disk(perdev_minors);(文章开头介绍的常用函数之一)分配了gendisk块设备通用结构体,然后填充了相关信息,比较重要的有:
a,md->disk->major = MMC_BLOCK_MAJOR mmc设备定义的主设备号为179.
b,md->disk->queue = md->queue.queue;赋值请求队列。该请求队列是又上面
ret = mmc_init_queue(&md->queue, card, &md->lock, subname);创建的
5,mmc_init_queue()函数定义在:
linux-3.16.56/drivers/mmc/card/queue.c
其中调用了blk_init_queue()(文章开头介绍的常用函数之一)初始化一个块设备驱动的请求队列。
6,blk_init_queue的第一个参数mmc_request_fn就是mmc驱动的队列处理函数,定义为:
linux-3.16.56/drivers/mmc/card/queue.c
其中blk_fetch_request()(文章开头介绍的常用函数之一)会取出队列中的请求,之后调度mmc驱动中的队列请求处理线程:wake_up_process(mq->thread)。
linux-3.16.56 / drivers / mmc / card / queue.c
mmc_queue_thread()线程体函数的定义在
linux-3.16.56 / drivers / mmc / card / queue.c
其中对队列中的请求一通折腾先忽略,最终是通过调用mq->issue_fn(mq, req);去做实际的mmc读写操作的。
linux-3.16.56 / drivers / mmc / card / block.c
其中mmc_blk_issue_rq()函数的定义在:
linux-3.16.56 / drivers / mmc / card / block.c
其中会调用mmc_blk_issue_rw_rq()函数向物理mmc设备发起读写请求。
linux-3.16.56/drivers/mmc/card/block.c
其中调用mmc_start_req()发起读写请求,函数定义在:
其中__mmc_start_data_req()定义在:
其中mmc_start_request()定义在:
linux-3.16.56/drivers/mmc/core/core.c
mmc_start_request()函数最后会调用相应mmc host注册的request函数发起读写请求,全文完。