通用块层,scsi_cmd的产生及发送,mmc block实现.

1. 引言
在我前面的博客中详细分析了Linux页面缓存的实现机制,包括各种数据结构以及之间的关联。本篇专栏中我们将会详细讨论文件系统如何从磁盘上读出一个页面。
我们知道,文件系统以页面(page,默认大小4096字节)为单位缓存文件数据,而早期的Linux中是以buffer head结构组织文件缓存的。每个buffer head数据大小与文件系统块大小相同,在当前版本操作系统中,page和buffer_head的关系如下图描述(图例中的页幁大小为4096字节,buffer_head数据大小为1024字节):
Linux文件系统从磁盘读页面 - tracymacding - tracymacding的博客
图1 page与buffer head关系图

因为Linux使用内存缓存文件数据,每次应用程序读写文件时首先必然在内存中进行,读时会首先从内存中查找当前读页面是否存在,若页面不存在或者当前页面的数据并非出于uptodata状态,那么VFS必须启动一次读页面流程。
若文件系统处理流程检测当前读页面不存在(尚未缓存)或者页面状态与磁盘不一致,此时需要从磁盘上读出页面内容。具体来说,调用具体文件系统的struct address_space_operations中的readpage方法,对于ext2文件系统来说,该方法被实例化为ext2_readpage,而其又是mpage_readpage的封装。mpage_readpage对于页面的读出会根据页面中的块在磁盘中是否连续而不同。具体来说,如果一个页面中的buffer_head对应的磁盘块在磁盘上连续,那么其实该page是无需创建buffer_head与其相关联,只有当页面中保存的磁盘块物理位置不连续,此时才需要创建多个buffer_head并在buffer_head结构中记录每一个逻辑块在磁盘中的物理块号。
mpage_readpage调用do_mpage_readpage完成具体的读页面工作。函数的参数一为需读出的页面信息,参数二为文件逻辑块至物理磁盘块的映射方法,因具体文件系统而异。


int mpage_readpage(struct page *page, get_block_t get_block)
{
struct bio *bio = NULL;
sector_t last_block_in_bio = 0;
struct buffer_head map_bh;
unsigned long first_logical_block = 0;

map_bh.b_state = 0;
map_bh.b_size = 0;
bio = do_mpage_readpage(bio, page, 1, &last_block_in_bio, &map_bh, &first_logical_block, get_block);
if (bio)
mpage_bio_submit(READ, bio);
return 0;
}

struct bio是文件系统与底层IO子系统连接件, 文件系统读/写页面其实就是向底层IO子系统发送struct bio请求。关于IO子系统会在别的章节中讨论,此处略过。mpage_readpage向调用者传入了多个参数,“1”表示仅读入一个页面。

static struct bio * do_mpage_readpage(struct bio *bio, struct page *page, unsigned nr_pages,
sector_t *last_block_in_bio, struct buffer_head *map_bh,
unsigned long *first_logical_block, get_block_t get_block)
{
struct inode *inode = page->mapping->host;
const unsigned blkbits = inode->i_blkbits;
const unsigned blocks_per_page = PAGE_CACHE_SIZE >> blkbits;
const unsigned blocksize = 1 << blkbits;
sector_t block_in_file;
sector_t last_block;
sector_t last_block_in_file;
sector_t blocks[MAX_BUF_PER_PAGE];
unsigned page_block;
unsigned first_hole = blocks_per_page;
struct block_device *bdev = NULL;
int length;
int fully_mapped = 1;
unsigned nblocks;
unsigned relative_block;

if (page_has_buffers(page))
goto confused;
......
confused:
if (bio)
bio = mpage_bio_submit(READ, bio);
if (!PageUptodate(page))
       block_read_full_page(page, get_block);
else
unlock_page(page);
goto out;
}


mpage_bio_submit-> submit_bio->generic_make_request->make_request_fn==blk_queue_bio
int elv_merge(struct request_queue *q, struct request **req, struct bio *bio)
queuebio首先看是否能与最后一次的reqest合并,条件是
    if (blk_rq_pos(rq) + blk_rq_sectors(rq) == bio->bi_iter.bi_sector)
        return ELEVATOR_BACK_MERGE;
    else if (blk_rq_pos(rq) - bio_sectors(bio) == bio->bi_iter.bi_sector)
        return ELEVATOR_FRONT_MERGE;
    return ELEVATOR_NO_MERGE;
如果不能则寻找其他的request是否能合并
    /*
     * See if our hash lookup can find a potential backmerge.
     */
    __rq = elv_rqhash_find(q, bio->bi_iter.bi_sector);
    if (__rq && elv_bio_merge_ok(__rq, bio)) {
        *req = __rq;
        return ELEVATOR_BACK_MERGE;
    }
如果都不能合并,则
    req = get_request(q, bio_data_dir(bio), rw_flags, bio, GFP_NOIO);
    init_request_from_bio(req, bio);//申请新的请求
    add_acct_request(q, req, where);//把新的请求添加进队列
   __blk_run_queue(q);//run 队列
        __blk_run_queue_uncond(q);
           q->request_fn(q);

 调用q的request_fn是设备,在初始化generic disk 的时候初始化 queue 时候调用blk_init_queue传入的
     q = blk_init_queue(request_fn, NULL);
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
{
    return blk_init_queue_node(rfn, lock, NUMA_NO_NODE);
}
EXPORT_SYMBOL(blk_init_queue);
blk_init_queue_node->    q->request_fn        = rfn;

对于scsi来说
    q = __scsi_alloc_queue(sdev->host, scsi_request_fn);

   1590 struct request_queue *scsi_alloc_queue(struct scsi_device *sdev)

   1591 {

   1592         struct request_queue *q;

   1593

   1594         q = __scsi_alloc_queue(sdev->host, scsi_request_fn);

   1595         if (!q)

   1596                 return NULL;

   1597

   1598         blk_queue_prep_rq(q, scsi_prep_fn);

   1600         blk_queue_softirq_done(q, scsi_softirq_done);

   1601         return q;

   1602 }

    

    143 void blk_queue_prep_rq(request_queue_t *q, prep_rq_fn *pfn)

    144 {

    145         q->prep_rq_fn = pfn;//scsi_prep_fn/这个函数会把request转换城scsi_cmd

    146 }


    173 void blk_queue_softirq_done(request_queue_t *q, softirq_done_fn *fn)

    174 {

    175         q->softirq_done_fn = fn;scsi_softirq_done/应该是完成request的时候会调

    176 }


从request到scsi_cmd,什么时候呢,就是在request_fn的时候调用struct request *blk_peek_request去获得一个request的时候,会把这个request做转换
static struct scsi_cmnd *scsi_get_cmd_from_req(struct scsi_device *sdev,
        struct request *req)
{
    struct scsi_cmnd *cmd;

    if (!req->special) {
        /* Bail if we can't get a reference to the device */
        if (!get_device(&sdev->sdev_gendev))
            return NULL;

        cmd = scsi_get_command(sdev, GFP_ATOMIC);
        if (unlikely(!cmd)) {
            put_device(&sdev->sdev_gendev);
            return NULL;
        }
        req->special = cmd;
    } else {
        cmd = req->special;
    }

    /* pull a tag out of the request if we have one */
    cmd->tag = req->tag;
    cmd->request = req;

    cmd->cmnd = req->cmd;
    cmd->prot_op = SCSI_PROT_NORMAL;

    return cmd;
}
所以scsi_prep_fn函数来到 执行本函数中最重要的过程,drv->init_command。这个drv是啥?来自gendisk的private_data字段。还记得sd_probe吗?我们在其中把它赋值给了对应scsi_disk结构的driver字段,就是前面那个sd_template常量,别告诉我你又忘了。如果真忘了,那就好好从头开始,从scsi磁盘驱动的初始化函数init_sd开始。

整个块设备驱动层的处理就结束了,我还是在网上找到一个图,正好可以总结上面的过程:

从前面分析可以看出,请求队列queue是top level与middle level之间的纽带。上层请求会在请求队列中维护,处理函数的方法由上下各层提供。在请求队列的处理过程中,将普通的块设备请求转换成标准的scsi命令,然后再通过middle level与low level之间的接口将请求递交给scsi host。


sd_init_command完事後,request_fn就會调用        rtn = scsi_dispatch_cmd(cmd);,这个scsi_dispatch_cmd会最终调用host的

    571                 rtn = host->hostt->queuecommand(cmd, scsi_done);


对于mmc的block来说,
    mq->queue = blk_init_queue(mmc_request_fn, lock);
static void mmc_request_fn(struct request_queue *q)
{
    struct mmc_queue *mq = q->queuedata;
    struct request *req;
    unsigned long flags;
    struct mmc_context_info *cntx;

    if (!mq) {
        while ((req = blk_fetch_request(q)) != NULL) {
            req->cmd_flags |= REQ_QUIET;
            __blk_end_request_all(req, -EIO);
        }
        return;
    }

    cntx = &mq->card->host->context_info;
    if (!mq->mqrq_cur->req && mq->mqrq_prev->req) {
        /*
         * New MMC request arrived when MMC thread may be
         * blocked on the previous request to be complete
         * with no current request fetched
         */
        spin_lock_irqsave(&cntx->lock, flags);
        if (cntx->is_waiting_last_req) {
            cntx->is_new_req = true;
            wake_up_interruptible(&cntx->wait);
        }
        spin_unlock_irqrestore(&cntx->lock, flags);
    } else if (!mq->mqrq_cur->req && !mq->mqrq_prev->req)
        wake_up_process(mq->thread);
}

static int mmc_queue_thread(void *d)
{
    struct mmc_queue *mq = d;
    struct request_queue *q = mq->queue;

参考:http://blog.csdn.net/hs794502825/article/details/8719034
http://blog.csdn.net/yunsongice/article/details/6171308
http://blog.csdn.net/yunsongice/article/details/6171299
http://blog.csdn.net/yangjianghua/article/details/12346309

    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->mqrq_cur->req = req;
        spin_unlock_irq(q->queue_lock);

        if (req || mq->mqrq_prev->req) {
            set_current_state(TASK_RUNNING);
            mq->issue_fn(mq, req);
            cond_resched();
            if (mq->flags & MMC_QUEUE_NEW_REQUEST) {
                mq->flags &= ~MMC_QUEUE_NEW_REQUEST;
                conti

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值