在上一文中,作者已经将Read 整个调用过程从Linux 系统调用(SCI,system call interface)至IO调度的整个流程已经讲解的非常清晰明了,在此对作者表示致敬!
那这里我接着以Android SD卡为例子,分析IO调度后数据的处理流向! 本文代码基于linux kernel 3.0.4.
1. mmcqd
mmcqd 是kernel在/kernel/drivers/mmc/card/queue.c 的mmc_init_queue拉起的一个内核线程,主要作用是把上层IO的request一个个向具体driver发送。
mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd/%d%s",
host->index, subname ? subname : "");
可以看到
mmc_queue_thread 才是实际做事情的,所以我们先来看看他的庐山真面目。
static int mmc_queue_thread(void *d)
{
struct mmc_queue *mq = d;
struct request_queue *q = mq->queue;
current->flags |= PF_MEMALLOC;
down(&mq->thread_sem);
do {
struct request *req = NULL;
spin_lock_irq(q->queue_lock);
set_current_state(TASK_INTERRUPTIBLE);
req = blk_fetch_request(q);
mq->req = req;
spin_unlock_irq(q->queue_lock);
if (!req) {
if (kthread_should_stop()) {
set_current_state(TASK_RUNNING);
break;
}
up(&mq->thread_sem);
schedule();
down(&mq->thread_sem);
continue;
}
set_current_state(TASK_RUNNING);
mq->issue_fn(mq, req);
} while (1);
up(&mq->thread_sem);
return 0;
}
2. mmcqd 与sd driver的关联
从上面函数看,mmcqd的工作还是非常简单的, 在blk_fetch_request(q) 获取一笔request后,最终会通过 mq->issue_fn(mq, req) 把这笔request发送下去。 所以issue_fn 这个回调函数看起来应该就是同底层driver 通讯的关键函数。 那我们先来看看req 是如何通过issue_fn 发下去的。
在同目录下的block.c 中的mmc_blk_alloc -》mmc_blk_alloc_req函数中注册了这个回调函数。
md->queue.issue_fn = mmc_blk_issue_rq
md->queue.data = md;
md->disk->major = MMC_BLOCK_MAJOR;
md->disk->first_minor = devidx * perdev_minors;
md->disk->fops = &mmc_bdops;
md->disk->private_data = md;
md->disk->queue = md->queue.queue;
md->disk->driverfs_dev = parent;
set_disk_ro(md->disk, md->read_only || default_ro);
md->disk->flags = GENHD_FL_EXT_DEVT;
而mmc_blk_alloc 函数是在mmc_blk_probe 函数中被调用的,所以看起来,在mmc block driver 初始化的时候已经为mmcqd做好了准备。
static int mmc_blk_probe(struct mmc_card *card)
{
struct mmc_blk_data *md, *part_md;
int err;
char cap_str[10];
/*
* Check that the card supports the command class(es) we need.
*/
if (!(card->csd.cmdclass & CCC_BLOCK_READ))
return -ENODEV;
md = mmc_blk_alloc(card);
if (IS_ERR(md))
return PTR_ERR(md);
好了,我们还是继续看看
mmc_blk_issue_rq到底又是如何把我们的request发送给sd card driver的。
static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
{
int ret;
struct mmc_blk_data *md = mq->data;
struct mmc_card *card = md->queue.card;
#ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME
if (mmc_bus_needs_resume(card->host)) {
mmc_resume_bus(card->host);
mmc_blk_set_blksize(md, card);
}
#endif
mmc_claim_host(card->host);
ret = mmc_blk_part_switch(card, md);
if (ret) {
ret = 0;
goto out;
}
if (req->cmd_flags & REQ_DISCARD) {
if (req->cmd_flags & REQ_SECURE)
ret = mmc_blk_issue_secdiscard_rq(mq, req);
else
ret = mmc_blk_issue_discard_rq(mq, req);
} else if (req->cmd_flags & REQ_FLUSH) {
ret = mmc_blk_issue_flush(mq, req);
} else {
ret = mmc_blk_issue_rw_rq(mq, req);
}
out:
mmc_release_host(card->host);
return ret;
}
这个函数其实也是挺简单的,虽说有几个分支,但是很明显,大部分的request都是通过
ret = mmc_blk_issue_rw_rq(mq, req); 来发送的。自这个函数之后就会调用到mmc 的core部分,之后便进入真正的SD卡的数据传输。更底层的过程我就不在这次讲了,后面专门针对sd driver部分做分析!
3. mmcqd 与IO 的关联
在上一文中曾经讲到request_fn就是block曾的入口函数。
q->request_fn = rfn;
blk_queue_make_request(q, __make_request);
所以我们来看看rfn 具体是那个函数呢?
在/kernel/drivers/mmc/card/queue.c 的 mmc_init_queue 中注册了这个函数。
int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card,
spinlock_t *lock, const char *subname)
{
struct mmc_host *host = card->host;
u64 limit = BLK_BOUNCE_HIGH;
int ret;
if (mmc_dev(host)->dma_mask && *mmc_dev(host)->dma_mask)
limit = *mmc_dev(host)->dma_mask;
mq->card = card;
mq->queue = blk_init_queue(mmc_request, lock);
blk_init_queue(mmc_request, lock);在上文中已经讲过,这里就不再重复分析。所以,接下来我们看看IO实际调用的函数为
mmc_requeststatic void mmc_request(struct request_queue *q)
{
struct mmc_queue *mq = q->queuedata;
struct request *req;
if (!mq) {
while ((req = blk_fetch_request(q)) != NULL) {
req->cmd_flags |= REQ_QUIET;
__blk_end_request_all(req, -EIO);
}
return;
}
if (!mq->req)
wake_up_process(mq->thread);
}
从上面函数可以看到,如果mq->req 是空的,我们则唤醒,mq->thread 来发送一笔req 下去。 至此mmcqd与上层IO的关联也很清楚了。