nullb driver分析4-读文件过程(使能调度器)

0.前言

内核版本:4.19
文档目的: 主要以null_dev为例来研究多队列的工作机制。本文主要以ext4文件系统为例,介绍null block dev读流程。注意本文所述流程是在开启blk-mq调度器的情况下进行的,开启方法为null blk驱动
中g_no_sched设为0。

1.通过文件系统读取nullb上的文件

new_sync_read

vfs_read
    |--rw_verify_area(READ, file, pos, count)
    \--__vfs_read(file, buf, count, pos)
        \--if (file->f_op->read)
              file->f_op->read(file, buf, count, pos)
           else if (file->f_op->read_iter)
              new_sync_read(file, buf, count, pos)
                  |--init_sync_kiocb(&kiocb, filp)
                  |--iov_iter_init(&iter, READ, &iov, 1, len)
                  \--call_read_iter(filp, &kiocb, &iter)
                       \--ext4_file_read_iter(kio, iter)

如上调用流程同 nullb driver分析2-读文件过程(禁用调度器)

ext4_file_read_iter

ext4_file_read_iter(kio, iter)
    \--generic_file_read_iter(iocb, to)
        \--if (iocb->ki_flags & IOCB_DIRECT) 
               对DIRECT IO的处理(TODO)
            else
                generic_file_buffered_read(iocb, iter, retval)

如上调用流程同 nullb driver分析2-读文件过程(禁用调度器)

generic_file_buffered_read

generic_file_buffered_read
|--if (PageReadahead(page))
|      page_cache_async_readahead//异步读
|--wait_on_page_locked_killable(page)//如果页面锁定则说明正在读,block等待end bio唤醒即可
|--if (!trylock_page(page)) //如果页面没有被锁定,则跳转到page_not_up_to_date,锁定页面
|      goto page_not_up_to_date
|--page_not_up_to_date:lock_page_killable(page)
|--mapping->a_ops->readpage(filp, page)//从磁盘读入缓存,end bio唤醒
\--if (PageUptodate(page)) 
            unlock_page(page);
            goto page_ok;

如上调用流程同 nullb driver分析2-读文件过程(禁用调度器)

page_cache_async_readahead

page_cache_async_readahead
    \--ondemand_readahead(mapping, ra, filp, true, offset, req_size)
        \--__do_page_cache_readahead(mapping, filp, offset, req_size, 0)
            \--read_pages(mapping, filp, &page_pool, nr_pages,gfp_mask)
                |--blk_start_plug(&plug)
                |--ext4_readpages(filp, mapping, pages, nr_pages)
                |     \--ext4_mpage_readpages()
                \--blk_finish_plug(&plug)

如上调用流程同 nullb driver分析2-读文件过程(禁用调度器)

blk_finish_plug

blk_finish_plug(&plug)
\--blk_flush_plug_list(plug, false)
   \--if (!list_empty(&plug->mq_list))
            blk_mq_flush_plug_list(plug, from_schedule)
            |--list_splice_init(&plug->mq_list, &list)
            |--list_sort(NULL, &list, plug_ctx_cmp) 
            |--while (!list_empty(&list))
            |       list_del_init(&rq->queuelist)
            |       list_add_tail(&rq->queuelist, &ctx_list)
            \-- blk_mq_sched_insert_requests(this_q, this_ctx, &ctx_list,from_schedule)

blk_finish_plug: 将进程plug队列向下派发,一般是派发给调度队列

blk_mq_flush_plug_list: 将plug->mq_list合并进list

list_splice_init:将plug->mq_list合并进list
list_sort:对list进行排序
list_del_init: 将req从list移除
list_add_tail:通过while循环,将从list下移除的request移入ctx_list链表
blk_mq_sched_insert_requests:对于开启调度器的情况下,将ctx_list链表中的 request尝试与调度队列进行合并

blk_mq_sched_insert_requests

blk_mq_sched_insert_requests
    |--dd_insert_requests (hctx, list, false)
    |        \--while (!list_empty(list))
    |               rq = list_first_entry(list, struct request, queuelist)
    |               list_del_init(&rq->queuelist);
    |               dd_insert_request(hctx, rq, at_head);
    |               |--if (blk_mq_sched_try_insert_merge(q, rq))
    |               |        return;
    |               \--list_add(&rq->queuelist, &dd->dispatch)  or deadline_add_rq_rb(dd, rq)
    \--blk_mq_run_hw_queue(hctx, run_queue_async)

blk_mq_sched_insert_requests:把plug队里的请求下发给调度器

dd_insert_requests: 当使能调度器的情况下,从初始化函数可以知道,对于null_dev使用的是deadline调度器(默认均使用此调度器),因此e->type->ops.mq.insert_requests为dd_insert_requests,因此会调用dd_insert_requests尝试将plug list中的request与调度器队列进行合并

blk_mq_sched_try_insert_merge: 通过循环调用elv_rqhash_find(q, blk_rq_pos(rq))快速查找是否有可以与req合并的请求,如果有就执行合并
如果无法与调度队列合并则通过list_add(&rq->queuelist, &dd->dispatch) or deadline_add_rq_rb(dd, rq) 暂存到调度队列
其中:
.list_add(&rq->queuelist, &dd->dispatch)会将request暂存到dd->dispatch链表
.deadline_add_rq_rb(dd, rq)会将request暂存到dd->sort_list[rq_data_dir(rq)]红黑树,同时如果request支持merger,也会通过elv_rqhash_add(q, rq)加入到hash表,下次可以快速查询新的rq是否可与暂存的requests执行merge

blk_mq_run_hw_queue:处理请求

blk_mq_run_hw_queue

在这里插入图片描述

blk_mq_run_hw_queue
\--__blk_mq_delay_run_hw_queue(hctx, async, 0)
   \--blk_mq_run_work_fn
       \--__blk_mq_run_hw_queue(hctx)
          \--blk_mq_sched_dispatch_requests(hctx)
             |--if (!list_empty_careful(&hctx->dispatch))
             |       list_splice_init(&hctx->dispatch, &rq_list)
             \--if (!list_empty(&rq_list)) 
                     blk_mq_dispatch_rq_list(q, &rq_list, false)
                else if (has_sched_dispatch)
                     blk_mq_do_dispatch_sched(hctx)
                     \--__dd_dispatch_request
                        \--do { 
                                rq = e->type->ops.mq.dispatch_request(hctx)
                                list_add(&rq->queuelist, &rq_list);
                            } while (blk_mq_dispatch_rq_list(q, &rq_list, true))
               else if (hctx->dispatch_busy)
                     blk_mq_do_dispatch_ctx(hctx)
                     \--do {
                            rq = blk_mq_dequeue_from_ctx(hctx, ctx);
                            list_add(&rq->queuelist, &rq_list);
                            ctx = blk_mq_next_ctx(hctx, rq->mq_ctx)
                         } while (blk_mq_dispatch_rq_list(q, &rq_list, true))
              else
                     blk_mq_flush_busy_ctxs(hctx, &rq_list)
                     blk_mq_dispatch_rq_list(q, &rq_list, false)

blk_mq_run_hw_queue真正将请求下发给驱动

blk_mq_sched_dispatch_requests:派发请求

list_splice_init(&hctx->dispatch, &rq_list):优先处理之前在硬队列中没有派发的请求, 将其移入到rq_list

分几种情况:
(1) blk_mq_dispatch_rq_list:优先派发之前在硬件度列中没有派发的请求到驱动;
(2) blk_mq_do_dispatch_sched:将调度队列中的请求移入rq_list,然后调用blk_mq_dispatch_rq_list,派发到驱动
(3)blk_mq_do_dispatch_ctx: 在硬队列busy的情况下,直接将软队列的rq移入到rq_list,然后派发给驱动, 公平起见,会考虑到轮流对多个软队列执行派发
(4) blk_mq_dispatch_rq_list: 其它情况将rq_list中请求派发给驱动处理

blk_mq_dispatch_rq_list

blk_mq_dispatch_rq_list(q, &rq_list, false)
\--do {
        null_queue_rq(hctx, &bd)
   } while (!list_empty(list))

blk_mq_dispatch_rq_list将rq_list中请求派发给驱动处理, 对于null blk dev, 主要是调用了null_queue_rq进行请求的处理

2. 总结

当在null block dev中使能调度时,流程变得复杂好多。总体来说,进程的plug队列首先会下发到调度队列,尝试与调度队列的请求进行合并,调度队列会直接下发到驱动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值