在上一文中,作者已经将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;
- }
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_request
- static 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的关联也很清楚了。