Linux mmc framework2:基本组件之block
1.前言
本文主要block组件的主要流程,在介绍的过程中,将详细说明和block相关的流程,涉及到其它组件的详细流程再在相关文章中说明。
2.主要数据结构和API
2.1 struct mmc_card
Elemete Name | struct mmc_card |
Path | include/linux/mmc/card.h |
Responsiblities |
是对mmc device的抽象,由于定义了mmc_bus_type类型的总线,此处mmc_card是与mmc_bus_type配套 |
Attributions |
|
Operations |
2.2 struct mmc_driver
Elemete Name | struct mmc_driver |
Path | include/linux/mmc/card.h |
Responsiblities |
mmc driver,由于定义了mmc_bus_type类型的总线,此处mmc_driver是与mmc_bus_type配套 |
Attributions |
|
Operations |
设置总线类型,并将drv加入到设备驱动模型中
将drv从设备驱动模型中移除 |
2.3 struct mmc_blk_data
Elemete Name | struct mmc_blk_data |
Path | drivers/mmc/card/block.c |
Responsiblities |
mmc_blk_data为block的核心结构体,用于存放mmc block的一些数据,与mmc slot对应 |
Attributions |
|
Operations |
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/16=16,每个此设备号对应一个分区?
- 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处理函数进行处理
- ()分配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 (1<<0)
- #define MMC_BLK_DATA_AREA_BOOT (1<<1)
- #define MMC_BLK_DATA_AREA_GP (1<<2)
- #define MMC_BLK_DATA_AREA_RPMB (1<<3)
- () alloc_disk(perdev_minors):分配gendisk结构体保存到md中,gendisk与磁盘设备对应
- (3)mmc_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_rq,mmc_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_card,mmc_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的时候,看看我们的假设是否正确,继续往下看
(2)mmc_alloc_card, mmc_add_card
通过浏览代码,我们发现在mmc/core/bus_c中有mmc_alloc_card和mmc_add_card
mmc_alloc_card:mmc_attach_mm->mmc_init_card初始化并分配一个新的mmc_card结构体,实际上是创建device设备;
mmc_add_card:mmc_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[])进行设置,主要包括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进行packed- 2. mmc_blk_rw_rq_prep:正常情况下执行mmc_blk_rw_rq_prep函数,从request构造mmc_request,毕竟下发给host请求,是mmc_request,而不是block层通用的request。
如果支持packed功能,那么就用pack_list来构造mmc_request- 3. mmc_start_req:mmc_start_req 启动一个非阻塞的request,这个函数会等待前一个request完成,然后启动当前requeset,并立刻返回
- 如果mmc_start_req返回的areq不为空,说明完成了上一次的request
- mmc_start_req
- mmc_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_rcv=true,这正好是唤醒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返回的状态
- . 如果是MMC_BLK_SUCCESS或者MMC_BLK_PARTIAL,需要调用blk_end_request通知block设备层,完成了本次读写request。
- . 如果是MMC_BLK_CMD_ERR,那么调用mmc_blk_reset复位host。调用mmc_blk_cmd_err尝试blk_end_request,如果发现reuqest未完成,说明本次操作失败,反之成功start_new_req
TODO
Linux mmc framework2:基本组件之block的更多相关文章
- Linux MMC framework2:基本组件之core
1.前言 本文主要core组件的主要流程,在介绍的过程中,将详细说明和core相关的流程,涉及到其它组件的详细流程再在相关文章中说明. 2.主要数据结构和API TODO 3. 主要流程 3.1 mm ...
- Linux mmc framework2:基本组件之queue
1.前言 本文主要介绍card下queue组件的主要流程,在介绍的过程中,将详细说明和queue相关的流程,涉及到其它组件的详细流程再在相关文章中说明. 2.主要数据结构和API 2.1 struct ...
- Linux MMC framework2:基本组件之host
声明:本文很多内容和思路参考了http://www.wowotech.net/comm/mmc_host_driver.html,对原作者表示感谢! 1.前言 本文是Linux MMC framewo ...
- Linux mmc framework2:基本组件之mmc
1.前言 本文主要mmc组件的主要流程,在介绍的过程中,将详细说明和mmc相关的流程,涉及到其它组件的详细流程再在相关文章中说明. 2.主要数据结构和API TODO 3. 主要流程 3.1 mmc_ ...
- Linux mmc framework1:软件架构
[部分内容来自] http://www.wowotech.net/comm/mmc_framework_arch.html 1. 前言 由eMMC基础技术1:MMC简介中MMC.SD.SDIO的介绍可 ...
- [MMC]Linux MMC/SD/SDIO驱动分析
转自:http://www.cnblogs.com/cslunatic/p/3678045.html 一.SD/MMC/SDIO概念区分 SD(SecureDigital)与 MMC(Multimed ...
- linux驱动基础系列--Linux mmc sd sdio驱动分析
前言 主要是想对Linux mmc子系统(包含mmc sd sdio)驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.块设备驱动.设备模型等也不进行详细说明原 ...
- Linux MMC 驱动子系统简述(源码剖析)
1. Linux MMC 驱动子系统 块设备是Linux系统中的基础外设之一,而 MMC/SD 存储设备是一种典型的块设备.Linux内核设计了 MMC子系统,用于管理 MMC/SD 设备. MMC ...
- 关于Linux开源项目基础组件make编译流程
关于Linux开源项目基础组件make编译流程 非常多Linux开源项目都会用到编译出可运行文件的make.这个是有一套流程的. 首先,GNU构建系统:https://en.wikipedia. ...
随机推荐
- 机器学习工作流程第一步:如何用Python做数据准备?
这篇的内容是一系列针对在Python中从零开始运用机器学习能力工作流的辅导第一部分,覆盖了从小组开始的算法编程和其他相关工具.最终会成为一套手工制成的机器语言工作包.这次的内容会首先从数据准备开始. ...
- git 28原则
一.流程 $ git init # 创建一个新的仓库 sublime 编写文本,不要使用win自带文本编辑器 $ git add file1 # 将文件添加到暂存区 $ git add file2 $ ...
- 【POJ1741】Tree
题目大意:给定一棵 N 个节点的无根树,边有边权,统计树上边权和不大于 K 的路径数. 对于每条树上路径,对于每一个点来说,该路径只有经过该点和不经过该点两种情况,对于不经过该点的情况,可以转化成是否 ...
- 再谈一次关于Java中的 AIO(异步IO) 与 NIO(非阻塞IO)
今天用ab进行压力测试时,无意发现的: Requests per second: xxx [#/sec] (mean) ab -n 5000 -c 1000 http://www:8080/up ...
- 有趣的filter
js中的filter就是过滤的意思,比如,我们以什么样的方式进行过滤,得到我们想要的结果. 对,我们要的就是这个结果. 给定一个数组,我们要的是Burger(汉堡) const restaurants ...
- java.sql.SQLException: Prepared or callable statement has more than 2000 parameter markers及解决方案
1. 问题 最近在项目中修bug的时候,碰到这样一个错误: Caused by: java.sql.SQLException:Prepared or callable statement has mo ...
- 实现在某一指定位置的div在窗口滚动到指定位置的时候fixed定位
HTML: <div class="top"> ·····内容 </div> <div class="scroll">< ...
- 【Maven】基础概念、仓库、构建与部属
1.常见的自动化构建工具有: make.ant.maven.gradle,gradle是目前最新的,maven是目前最常用的. Eclipse是一种半自动化构建工具,主要体现在把:java文件-> ...
- go 数组与切片
数组概念 1.数组:是同一种数据类型的固定长度的序列. 2.数组定义:var a [len]int,比如:var a[5]int,一旦定义,长度不能变 3.长度是数组类型的一部分,因此,var a[5 ...
- Linux记录-Linux Swap分区虚拟内存相关解决方案
Swap用途:Swap意思是交换分区,通常我们说的虚拟内存,是从硬盘中划分出的一个分区.当物理内存不够用的时候,内核就会释放缓存区(buffers/cache)里一些长时间不用的程序,然后将这些程序临 ...