古建设工程造价管理协会网站,网站怎么设置二级域名,wordpress模板分享,seo外链推广平台1.前言
本文主要block组件的主要流程#xff0c;在介绍的过程中#xff0c;将详细说明和block相关的流程#xff0c;涉及到其它组件的详细流程再在相关文章中说明。
2.主要数据结构和API
2.1 struct mmc_card Elemete Namestruct mmc_cardPathinclude/linux/mmc/card.hRe…
1.前言
本文主要block组件的主要流程在介绍的过程中将详细说明和block相关的流程涉及到其它组件的详细流程再在相关文章中说明。
2.主要数据结构和API
2.1 struct mmc_card Elemete Namestruct mmc_cardPathinclude/linux/mmc/card.hResponsiblities 是对mmc device的抽象由于定义了mmc_bus_type类型的总线此处mmc_card是与mmc_bus_type配套 Attributions hoststruct mmc_host *类型这个mmc device属于哪个host管理devstruct device类型代表设备驱动模型中的一个 deviceocr当前的操作电压设置rcadevice的relative card addresstype卡的类型包括MMC/SD/SDIO/COMBO(SDIOMEM)state:卡的状态在线、只读、是否使用block地址、是否是SDXC卡、卡被移除、卡在BKOPS、卡在suspendquirks:卡的一些其它怪癖属性erase_size单位sectorserase_shift可以擦除的 sectors是2的多少次方pref_erase单位sectorseg_boundaryerased_byte擦除的字节数raw_cid原始的CID值raw_csd原始的CSD值raw_scr原始的raw_scr值cidstruct mmc_cid类型卡identificationcsdstruct mmc_csd类型保存从卡的CSD寄存器读取的内容ext_csdstruct mmc_ext_csd类型卡扩展信息scr:其它的SD信息ssr更多的SD信息sw_capsswicth能力sdio_funcsSDIO功能的个数cccrstruct sdio_cccr类型卡的通常信息cisstruct sdio_cissd_bus_speedbus speed modemmc_avail_typehost和card都支持的设备类型drive_strength驱动能力用于UHS-I, HS200 or HS400debugfs_rootstruct dentry *类型用于debugfs显示根目录partstruct mmc_part类型物理分区nr_parts物理分区的个数Operations 2.2 struct mmc_driver Elemete Namestruct mmc_driverPathinclude/linux/mmc/card.hResponsiblities mmc driver由于定义了mmc_bus_type类型的总线此处mmc_driver是与mmc_bus_type配套 Attributions drvstruct device_driver类型proberemoveshutdown:mmc driver相关函数Operations int mmc_register_driver(struct mmc_driver *drv) 设置总线类型并将drv加入到设备驱动模型中 void mmc_unregister_driver(struct mmc_driver *drv)将drv从设备驱动模型中移除
2.3 struct mmc_blk_data Elemete Namestruct mmc_blk_dataPathdrivers/mmc/card/block.cResponsiblities mmc_blk_data为block的核心结构体用于存放mmc block的一些数据与mmc slot对应 Attributions lockspinlock_t类型diskstruct gendisk *类型代表一个磁盘设备queuestruct mmc_queue类型请求队列part分区链表flagsusageread_onlypart_typename_idxreset_donepart_currforce_ropower_ro_lockarea_typeOperations 3. 主要流程
3.1 mmc_blk_init mmc_blk_init- 初始化max_devices register_blkdev mmc_register_driver module_init(mmc_blk_init)会执行到此函数 初始化max_devices设定最多支持多少个mmc块设备给max_devices
每类块设备支持256个次设备号每个块设备有16个次设备号16个分区由此得出支持的最大的mmc块设备数max_devices为256/1616每个此设备号对应一个分区register_blkdev:向全局的struct blk_major_name类型的数组major_names注册本块设备的主设备号和设备名 mmc子系统对于上层block子系统来讲是首先抽象为一个普通的块设备。
通过register_blkdev向block子系统注册一个block设备主设备号为MMC_BLOCK_MAJOR设备名为“mmc”。
通过分配一个blk_major_name结构体来保存主设备号和设备名blk_major_name被保存到全局的blk_major_name数组中。
如果不指定主设备号将查询全局的blk_major_name结构体找到一个未用的主设备号来使用并将此主设备号作为返回值返回。
major_names中的信息会出现在/proc/devices中。
因此可以看出注册做的事情实际上非常少。注册完成后除了能够在/proce/devices中看到设备之外不能对设备做任何事情设备还无法使用只有当block_device与gendisk建立关联用户空间才可以访问 mmc_register_driver(mmc_driver)
设备驱动模型中通过driver_register将mmc_driver注册到mmc_bus_type上
3.2 mmc_blk_exit mmc_blk_exit- mmc_unregister_driver unregister_blkdev 在退出的时候会执行mmc_blk_exit与mmc_blk_init相反的动作主要包括
mmc_unregister_driver(mmc_driver)
从mmc_bus_type上将mmc_driver注销 unregister_blkdev(MMC_BLOCK_MAJOR, mmc);
从全局的struct blk_major_name类型的数组major_names中注销主设备号为MMC_BLOCK_MAJOR名为mmc的blk_major_name结构体
释放对应的blk_major_name结构体
3.3 mmc_blk_probe mmc_blk_probe- mmc_blk_alloc- mmc_blk_alloc_req- alloc_disk mmc_init_queue- blk_queue_prep_rq kthread_run(mmc_queue_thread, mq); mmc_blk_alloc_parts(card, md)) mmc_add_disk- block_add_disk 初始化时mmc_blk_init中会执行mmc_register_driver而前文所述执行mmc_attach_mmc时会通过mmc_add_card将mmc_card注册到mmc bus这样就触发了执行前文所述的mmc_blk_probe函数后面有详细解释mmc_blk_probe的执行过程
mmc_blk_probe最主要的是初始化了request queue初始化disk同时通过mmc_add_disk将磁盘添加到系统中使之可用
mmc_blk_alloc_req
创建并初始化请求队列启动线程循环抓取请求队列中的request调用request处理函数进行处理 1分配mmc_blk_data结构体md并初始化同时mmc_queue作为mmc_blk_data的成员也被创建
mmc_blk_data为block的核心结构体与mmc_card关联用于存放mmc_card相关数据每个mmc slot即每个mmc设备对应一个mmc_blk_data结构体。
此处会分配mmc_blk_data结构体md同时mmc_queue作为mmc_blk_data的成员也被创建。并标识dev_use的bitmap来记录已经分配的mmc device
也就是说dev_use是与实际的物理设备相对应的不是跟分区对应的dev_use的index用dev_idx来记录
注意到此处MMC_BLK_DATA_AREA_MAIN表示主分区的区域(mmc_blk_data与设备对应此处看又像是与分区对应)。
MMC分区类型包括如下几种
#define MMC_BLK_DATA_AREA_MAIN (10)
#define MMC_BLK_DATA_AREA_BOOT (11)
#define MMC_BLK_DATA_AREA_GP (12)
#define MMC_BLK_DATA_AREA_RPMB (13)2 alloc_disk(perdev_minors)分配gendisk结构体保存到md中gendisk与磁盘设备对应3mmc_init_queue(queue_c)创建并初始化请求队列
通过调用block子系统接口blk_init_queue来初始化请求队列其中mmc_request_fn为处理请求的回调函数
blk_queue_prep_rq(mq-queue, mmc_prep_request)设定requet_queue的prep回调函数
mmc_alloc_sg(host-max_segs, ret)分配max_segs个scatterlist用于request请求只是分配scatterlist并未分配存放数据的内存
返回分配的scatterlist个数
kthread_run(mmc_queue_thread, mq) 起一个kennel thread运行mmc_queue_thread来处理上层发送下来的request对每个reqeust执行issue_fn回调注issue_fn回调在下面指定为mmc_blk_issue_rq
4指定issue_fn回调为mmc_blk_issue_rqmmc_blk_issue_rq是具体的mmc request处理函数 mmc_blk_alloc_parts(card, md)) mmc_add_disk
为了将一个磁盘添加到系统中对系统可用必须初始化磁盘数据结构并调用add_disk方法。
需要特别注意的是一旦调用了add_disk磁盘就被“激活”了系统随时都可能会调用该磁盘提供的各种方法
甚至在该函数返回之前就会调用因而在完成磁盘结构的初始化之前不要调用add_disk。 3.4 mmc_add_disk mmc_add_disk- device_add_disk device_add_disk的原型为void device_add_disk(struct device *parent, struct gendisk *disk) 它完成的工作主要包括
(1)根据磁盘的主次设备号信息为磁盘分配设备号
(2)调用disk_alloc_events初始化磁盘的事件alloc|add|del|release处理机制。在最开始磁盘事件会被设置为被阻塞的。
(3)调用bdi_register_dev将磁盘注册到bdi_list注bdi用于将page_cache或buffer_cache中的脏数据刷新到磁盘
(4)调用blk_register_region将磁盘添加到bdev_map中通过设备号可以获取kobject从而得到包含它的父对象进行操作
(5)调用register_disk将磁盘添加到系统中。
(6)调用blk_register_queue注册磁盘的请求队列。主要是为队列和队列的调度器在设备的sys文件系统目录中创建相应的sys目录/文件并且发出uevent事件。
(7)调用disk_add_events完成在/sys文件系统的设备目录下创建磁盘的事件属性文件将磁盘事件添加到全局链表disk_events中解除对磁盘事件的阻塞。 关于probe函数是如何被调用到的 一般我们认为mmc_blk_probe的执行一定需要mmc_driver与mmc_device的匹配才可以实际上没有mmc_device, 而是有mmc_cardmmc_blk_probe的执行经历如下历程 1先来看mmc_register_driver的流程 mmc_register_driver- driver_register- driver_find//bus查看driver是否已经注册如果已经注册则退出,否则bus add driver bus_add_driver- driver_attach- bus_for_each_dev//此处由于还没有device注册因此会退出 显然mmc_blk_probe的执行不是在mmc_register_driver的时候那么肯定是在device_register的时候,看看我们的假设是否正确继续往下看 2mmc_alloc_card mmc_add_card 通过浏览代码我们发现在mmc/core/bus_c中有mmc_alloc_card和mmc_add_card mmc_alloc_cardmmc_attach_mm-mmc_init_card初始化并分配一个新的mmc_card结构体实际上是创建device设备 mmc_add_cardmmc_attach_mmc-mmc_add_card时调用通过调用device_add(card-dev)来完成设备的注册过程如下 mmc_add_card- device_add- bus_probe_device- device_attach- __device_attach- driver_match_device- mmc_bus_match//此函数的特殊之处在于总是返回值为1 driver_probe_device- really_probe- mmc_bus_probe- mmc_blk_probe mmc_blk_probe的执行不是靠device和driver的匹配而是将匹配函数mmc_bus_match总是返回1如下 static int mmc_bus_match(struct device *dev, struct device_driver *drv) { return 1; } 这样就可以执行到mmc_bus_type的probe函数进而执行到mmc_blk_probe。 3.5 mmc_queue_thread
线程处理函数用于循环抓取请求队列中的request并交给请求处理函数进行处理 mmc_queue_thread- blk_fetch_request issue_fn(mmc_blk_issue_rq)- mmc_blk_issue_rw_rq- mmc_blk_rw_rq_prep mmc_start_req – mmc_wait_for_data_req_done- mmc_blk_err_check host-ops-request mmc_queue_thread是在mmc_init_quene中起的线程主要作用是完成上层发送的请求进行处理 blk_fetch_request
从请求队列中取出一个request
issue_fn
由前面可知issue_fn在mmc_blk_probe-mmc_blk_alloc_req时将issue_rq初始化为mmc_blk_issue_rq,请求有几种包括discard, flush, 以及rw
mmc_blk_issue_rw_rq
首先通过mmc_blk_rw_rq_prep来做一些准备工作获取命令号、命令参数等然后通过mmc_start_req发起请求
mmc_start_req
通过mmc_wait_for_data_req_done发起真正的请求并等待请求结束。
mmc_wait_for_data_req_done会回调控制器的request函数发起请求然后mmc_blk_err_check检查是否有错误发生
如果有错误发生将尝试recovery进行修复开始新的传输
3.6 mmc_blk_issue_rq mmc_blk_issue_rq- mmc_claim_host mmc_blk_part_switch mmc_blk_issue_rw_rq- mmc_blk_prep_packed_list mmc_blk_rw_rq_prep mmc_start_req- __mmc_start_data_req mmc_queue_bounce_post 检查mmc_start_req返回的状态 mmc_blk_issue_rq对发送的mmc request进行具体的处理。
mmc_claim_host
实际上是声明当前进程占有host controller如果有其它进程占有则需要等待详细的可参考Linux mmc framework2基本组件之core
mmc_blk_part_switch
通过MMC_SWITCH命令对EXT_CSD寄存器的PARTITION_CONFIG(bit[179])进行设置主要包括boot是否使能、用哪个分区做boot分区、选择要访问的分区。
如果MMC_SWITCH命令出错将通过blk_end_request_all终止request
mmc_blk_issue_rw_rq
根据req-cmd_flags的命令做不同的事情。REQ_SANITIZE、REQ_DISCARD、REQ_FLUSH分别为
. mmc_blk_issue_secdiscard_rq 和mmc_blk_issue_discard_rq
. mmc_blk_issue_flush
. mmc_blk_issue_rw_rq(这个是我们要分析的读写数据流程 1. mmc_blk_prep_packed_list尝试把当前request和队列中的其他request合并以增强性能。是否可以合并要依赖于
控制器支持packed功能;
device的MAX_PACKED_WRITES 大于0;
只对写request进行packed2. mmc_blk_rw_rq_prep:正常情况下执行mmc_blk_rw_rq_prep函数从request构造mmc_request毕竟下发给host请求是mmc_request而不是block层通用的request。
如果支持packed功能那么就用pack_list来构造mmc_request3. mmc_start_reqmmc_start_req 启动一个非阻塞的request这个函数会等待前一个request完成然后启动当前requeset并立刻返回
如果mmc_start_req返回的areq不为空说明完成了上一次的request mmc_start_reqmmc_start_req 启动一个非阻塞的request这个函数会等待前一个request完成然后启动当前requeset并立刻返回 如果mmc_start_req返回的areq不为空说明完成了上一次的request 1. 首先它会执行到mmc_wait_for_data_req_done函数等待上一次的命令的完成如果上一次未完成就会将当前进程加入等待队列休眠等待被唤醒。 当上一次完成后会立即返回并将上一次命令执行的状态返回给mmc_blk_issue_rw_rq。
2、if (host-areq) { err mmc_wait_for_data_req_done(host, host-areq-mrq, areq);
host-areq不为空说明有正在处理的reuqest函数mmc_wait_for_data_req_done用来等待这个host-areq有两个条件会唤醒该MMC上下文: is_done_rcv和is_new_req
3. if (!err areq) start_err __mmc_start_data_req(host, areq-mrq);
进入__mmc_start_data_req(host, areq-mrq);
(1)首先会将函数指针mmc_wait_data_done赋给mrq-done.
mmc_wait_data_done会设置context_info-is_done_rcvtrue这正好是唤醒mmc_wait_for_data_req_done的条件之一然后调wake_up_interruptible(context_info-wait);唤醒之。
(2)然后会调用mmc_start_request(host, mrq); mmc_start_reuqest实际调用host-ops-request方法进入了平台特定的request函数
进入特定的平台之后会进入相应的中断对硬件进行读写的命令的执行当命令执行完毕后会进行函数回调调到刚才的mmc_wait_data_done唤醒等待的进程进行下一次命令的执行。 mmc_queue_bounce_post
如果使用了bounce buffer那么需要把传输结果从bounce buffer复制会sg buffer。
所谓bounce buffer是因为某些DMA控制器只能处理连续物理内存此时需要通过bounce buffer来达到物理内存连续性。
检查mmc_start_req返回的状态
1. 如果是MMC_BLK_SUCCESS或者MMC_BLK_PARTIAL需要调用blk_end_request通知block设备层完成了本次读写request。
2. 如果是MMC_BLK_CMD_ERR那么调用mmc_blk_reset复位host。调用mmc_blk_cmd_err尝试blk_end_request如果发现reuqest未完成说明本次操作失败反之成功start_new_req