Linux IO合并的问题

Linux IO合并的问题

工作十年,第一次写博客,因为也是最近发现我的记忆力不好了,开始记不住很多东西,而且公司有要求要把做过的东西总结分享,这才发现了总结的好处,可以帮我更加的清楚的认识自己掌握知识的程度。

我遇到了这样一个问题,当我用如下的命令时,并没有产生IO合并

fio --name=read_64k --filename=/dev/sdb --ioengine=libaio --direct=1 --bs=64k --iodepth=32 --size=5G --rw=read --numjobs=1 -group_reporting
read_64k: (g=0): rw=read, bs=(R) 64.0KiB-64.0KiB, (W) 64.0KiB-64.0KiB, (T) 64.0KiB-64.0KiB, ioengine=libaio, iodepth=32
Run status group 0 (all jobs):
   READ: bw=169MiB/s (178MB/s), 169MiB/s-169MiB/s (178MB/s-178MB/s), io=5120MiB (5369MB), run=30214-30214msec
Disk stats (read/write):
  sdb: ios=81738/0, merge=0/0, ticks=961600/0, in_queue=924779, util=90.48%

按照我的理解,顺序读应该会有多个IO合并成一个IO递交到驱动层。但是无论我把block size改成4k还是512,都没有合并,fio输出的merge 统计依然是0。

然后当我将iodepth设置成33,奇怪的事情发生了

[root@localhost ~]# fio --name=read_64k --filename=/dev/sdb --ioengine=libaio --direct=1 --bs=64k --iodepth=33 --size=5G --rw=read --numjobs=1 -group_reporting
read_64k: (g=0): rw=read, bs=(R) 64.0KiB-64.0KiB, (W) 64.0KiB-64.0KiB, (T) 64.0KiB-64.0KiB, ioengine=libaio, iodepth=33
Run status group 0 (all jobs):
   READ: bw=168MiB/s (176MB/s), 168MiB/s-168MiB/s (176MB/s-176MB/s), io=5120MiB (5369MB), run=30445-30445msec
Disk stats (read/write):
  sdb: ios=79910/0, merge=1885/0, ticks=974993/0, in_queue=935900, util=88.38%

居然产生了1885个merge。看来内核块设备层对merge的操作和我的理解是有偏差的。

用ftrace跟踪了一下内核代码,梳理了工作的流程,先总结一个流程图,而后是我写了伪代码如下:
在这里插入图片描述

io_submit()
{
    blk_make_request(bio) {
        if (plug_merge(bio))  //尝试bio能否在蓄流表中merge
            return;
        if (try_sched_bio_merge()) //尝试能否和调度器中的req合并
            return;
        bio_to_rq();  //将bio转换成 request数据结构
        add_rq_to_plug();	//将request添加到蓄流表中
    }
    blk_flush_plug_list()  {
        try_sched_insert_request()  { 	//将蓄流表中的io 插入到调度器中
            try_insert_merge(); //尝试是否能合并
            run_hw_queue() {
                if (sched_is_empty()) //如果调度器空了就退出
                    return;
                if (has_buget()) { //判断硬件队列是否有空间
                    dispatch_request();  //将调度器中的IO下发请求给驱动
                    remove_request();  // 删除请求
                }
           }
       }
    }	
}

由于ftrace抓的函数不完全,而不方便查看每一个包的情况,我在try_sched_insert_request函数里面添加了打印,然后又在remove_request 里面添加了打印。

为什么要在这里面添加打印呢,是这样的,try_sched_bio_merge()是执行合并的函数,合并后会调用merge_io_count()进行统计merge。在合并之前会去判断当前是否有发生过merge,也就是rq->last_merge这个变量,如果此变量为空,就不会进行合并。而修改这个变量的地方,正是try_sched_bio_merge()设置,remove_request()置空。

在iodepth=32的情况,这insert和remove两个打印成对出现的,而当iodepth=33时候,发现remove 的打印有时候没有,没有remove,rq->last_merge就不是空,那么下一次IO就会进入到try_sched_bio_merge()走合并流程,因为是顺序读,一定会产生合并。

如果没有remove,那么就说明has_buget()函数返回了false。深入has_buget函数,这个函数最后判断就是当前正在处理的IO个数busy和硬件队列的queue_depth的比较,如果busy 大于 queue_depth,就会返回false。于是,我打印了一下queue_depth,的确是32(其实也不需要打印,可以通过查看scsi对应的硬件队列深度即可)

cat /sys/bus/scsi/devices/1:0:0:0/queue_depth

通过这个命令查看,是32,这就能解释为什么iodepth大于32就会出现IO合并。而这个值是驱动层赋予的,与控制器硬件队列深度是相关联的。

总结

  1. IO合并并不是我们理解的多个小的连续的IO在调度器中合并,调度器是尽可能的把IO派发给驱动,而只有当驱动硬件队列满了,IO被滞留在调度器当中,才会和下一个IO尝试合并。
  2. IO合并的地方还包括蓄流表,蓄流表只有在多线程的情况下才会进行合并,因为蓄流表是一个进程才会有一个。
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值