nullb driver分析3-读文件过程(禁用调度器)

目录

 

0.前言

1.通过文件系统读取nullb上的文件(禁用调度器)

new_sync_read

ext4_file_read_iter

generic_file_buffered_read

page_cache_async_readahead

blk_finish_plug

blk_mq_sched_insert_requests

blk_mq_try_issue_list_directly

null_queue_rq

blk_mq_complete_request

2.数据结构

3.参考文档

 


0.前言

内核版本:4.19

文档目的: 主要以null_dev为例来研究多队列的工作机制。本文主要以ext4文件系统为例,介绍null block dev读流程。

注意本文所述流程是在关闭blk-mq调度器的情况下进行的。

1.通过文件系统读取nullb上的文件(禁用调度器)

通过在null blk drv的copy_from_nullb中加dump_stack可以读调用流程

 

通过perf也可以看到类似的流程


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)

#new_sync_read:对于ext4的文件系统,由于没有定义file->f_op->read,而是定义了file->f_op->read_iter回调,因此会调用new_sync_read函数。
这里查看了Documentation/filesystems/vfs.txt文件中关于read_iter的说明。这里说read_iter有可能是一种异步的读,并且会读取到iov_iter
read_iter: possibly asynchronous read with iov_iter as destination
##init_sync_kiocb:用于初始化kiocb,包括文件描述符,标记,文件位置等。kiocb保存了文件描述符file的位置等信息,相当于由kiocb控制对IO的读写操作,所以称为内核IO控制块
##iov_iter_init:iov_iter是一个向量迭代器,指向一个iovec向量数组,用于推进io的读写, iov_iter_init用于初始化此向量迭代器;
##call_read_iter: 将执行file->f_op->read_iter(kio, iter),最终调用了ext4_file_read_iter(kio, iter), 执行文件读操作

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)
 

ext4_file_read_iter实际会分为direct io的处理和非direct io的处理,这里只分析非direct io的处理,主要调用generic_file_buffered_read

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;

先来看一下generic_file_buffered_read的总体情况,根据前面的读取调用栈可知道实际调用了page_cache_async_readahead,执行异步预读操作。

#page_cache_async_readahead:执行异步预读操作。
#wait_on_page_locked_killable:重新检查页面是否已经被锁定,如果被锁定,则将被阻塞,直到读取完毕唤醒进程(通过end_io回调?)
#readpage:对于页面没有被锁定的情况,说明并未启动页面读取,则通过readpage回调执行磁盘读取

page_cache_async_readahead

根据调用栈流程,下面重点介绍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)

#ondemand_readahead:执行预读算法,其中offset为page在mapping所处的index,req_size为请求预读的大小
##__do_page_cache_readahead: 真正执行读取一个disk chunk。它会先通过radix_tree_lookup查找page在mapping->i_pages中是否存在,如果不存在它会预先分配page, 然后通过read_pages来执行页面读取,如果存在则直接通过read_pages读取页面;
###blk_start_plug: 初始化进程blk_plug
###ext4_readpages: 通过看ftrace, 调用ext4_mpage_readpages,最终从块设备中读取多个page, 主要通过submit_bio下发到了进程的plug队列
###blk_finish_plug: flush plug队列的请求(flush plug有哪些时机?TODO)

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_add_tail(&rq->queuelist, &ctx_list)
                    \-- blk_mq_sched_insert_requests(this_q, this_ctx, &ctx_list,from_schedule)


blk_finish_plug: 将进程plug队列向下派发
         
#blk_flush_plug_list:此处没有使用调度器,因此from_schedule参数为false,由于plug->mq_list非空,因此会调用blk_mq_flush_plug_list
##blk_mq_flush_plug_list:  将plug->mq_list合并进list,下发到硬件队列直接处理或暂存到软队列
###list_splice_init:将plug->mq_list合并进list
###list_sort:对list进行排序
###list_add_tail:通过while循环,将list下所有的request移入ctx_list链表
###blk_mq_sched_insert_requests:根据是否有调度队列,及当前硬队列是否busy来将ctx_list中的request派发给硬件队列进行直接处理 或 插入到软队列暂存,此处没有采用调度处理,且当前硬队列处于非busy状态,因此直接将ctx_list中的request派发给了硬队列进行直接处理

blk_mq_sched_insert_requests

blk_mq_sched_insert_requests(this_q, this_ctx, &ctx_list,from_schedule)
    |--hctx = blk_mq_map_queue(q, ctx->cpu)
    \--if (e && e->type->ops.mq.insert_requests)
           e->type->ops.mq.insert_requests(hctx, list, false)
        else
           if (!hctx->dispatch_busy && !e && !run_queue_async)
               blk_mq_try_issue_list_directly(hctx, list)
           blk_mq_insert_requests(hctx, ctx, list)
           blk_mq_run_hw_queue(hctx, run_queue_async)

blk_mq_sched_insert_requests:如果没有使用调度器且当前硬队列非busy,则将request直接派发给硬件队列进行直接处理,剩余的request将插入到软队列暂存;此处没有采用调度处理,且当前硬队列处于非busy状态,因此直接将ctx_list中的request派发给了硬队列进行直接处理

#blk_mq_map_queue:获取软队列映射的硬队列
#如果e && e->type->ops.mq.insert_requests,则将请求插入到调度队列
#blk_mq_try_issue_list_directly:如果满足!hctx->dispatch_busy && !e && !run_queue_async,将通过blk_mq_try_issue_list_directly直接将request发送给硬件队列处理,对于null block dev,当前就是处于这样的状态;
#blk_mq_insert_requests:如果还有剩余的request,将通过blk_mq_insert_requests将其插入到软队列ctx
#blk_mq_run_hw_queue: 处理软对列中的request(TODO)

blk_mq_try_issue_list_directly

blk_mq_try_issue_list_directly(hctx, list)
    \--while (!list_empty(list))
            blk_mq_request_issue_directly(rq)
                |--blk_mq_get_dispatch_budget(hctx)
                |--hctx = blk_mq_map_queue(rq->q, ctx->cpu)
                \--__blk_mq_try_issue_directly(hctx, rq, &unused_cookie, true)    
                       |--blk_mq_get_driver_tag(rq)  
                       \--__blk_mq_issue_directly(hctx, rq, cookie) 
                              \--q->mq_ops->queue_rq(hctx, &bd)
                                  \--null_queue_rq(hctx, &bd)

blk_mq_try_issue_list_directly通过while循环将list链表中的所有request,直接发送到硬队列进行处理

null_queue_rq

null_queue_rq(hctx, &bd)
    \--blk_mq_start_request(bd->rq)
          \--null_handle_cmd(cmd)
              |--null_handle_rq(cmd)
              |       req_for_each_segment(bvec, rq, iter)
              |           null_transfer(nullb, bvec.bv_page, len, bvec.bv_offset...)
              |               |--copy_from_nullb(nullb, page, off, sector, len)
              |               \--flush_dcache_page(page)
              \--blk_mq_complete_request(cmd->rq)

null_queue_rq对请求进行处理,主要是从内存模拟的磁盘中拷贝到bio对应的bio_vec描述的page segment,处理完成后通过bio end回调通知完成

#blk_mq_complete_request:负责上报request及bio完成,并执行对应的回调

blk_mq_complete_request

blk_mq_complete_request(cmd->rq)
      \--__blk_mq_complete_request(rq)
            \--rq->q->softirq_done_fn(rq)
                   \--null_softirq_done_fn(rq)
                          \--end_cmd(blk_mq_rq_to_pdu(rq))
                                  \--blk_mq_end_request(cmd->rq, cmd->error)
                                       |--blk_update_request(rq, error, blk_rq_bytes(rq))
                                       |      |--while(req->bio)
                                       |            req_bio_endio(req, bio, bio_bytes, error)
                                        \--__blk_mq_end_request(rq, error)
                                             \--if (rq->end_io)
                                                    rq->end_io(rq, error)
                                                 else
                                                     blk_mq_free_request(rq)

blk_mq_complete_request负责上报request及bio完成,并执行对应的回调

#null_softirq_done_fn: softirq_done_fn回调为null_softirq_done_fn
#此处的rq->end_io为空, 因此会执行blk_mq_free_request
#blk_update_request:遍历req的每个bio,执行bio endio回调,unlock_page解锁bio所关联的所有page
这样generic_file_buffered_read->wait_on_page_locked_killable的进程就会被唤醒了
#__blk_mq_end_request:由于rq->end_io为空,会执行blk_mq_free_request

2.数据结构

struct iovec {
    const struct iovec *iov;// 指向iovec向量数组
    unsigned long nr_segs;// iovec向量数组剩余的io向量个数
    size_t iov_offset;//当前io向量偏移
    size_t count;//剩余的字节数
 }


/*内核IO控制块*/
struct kiocb {
    struct file             *ki_filp;//读写文件描述符
    loff_t                  ki_pos;//读写文件的位置
    void (*ki_complete)(struct kiocb *iocb, long ret, long ret2);
    void                    *private;
    int                     ki_flags;
    u16                     ki_hint;
    u16                     ki_ioprio; /* See linux/ioprio.h */
} __randomize_layout;


3.参考文档


1.Documentation/filesystems/vfs.txt

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值