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
    评论
Linux IO数据通道: 1.虚拟文件系统层:屏蔽下层具体文件系统操作的差异,为上层的操作提供一个统一的接口。因为有了这个层次,所以可以把设备抽象成文件,使得操作设备就像操作文件一样简单。 2.具体的文件系统层:不同的文件系统(例如 ext2 和 NTFS)具体的操作过程也是不同的。每种文件系统定义了自己的操作集合。 3.引入 cache 层的目的是为了提高 linux 操作系统对磁盘访问的性能。 Cache 层在内存中缓存了磁盘上的部分数据。当数据的请求到达时,如果在 cache 中存在该数据且是最新的,则直接将数据传递给用户程序,免除了对底层磁盘的操作,提高了性能。 4.通用块层:接收上层发出的磁盘请求,并最终发出 IO 请求。该层隐藏了底层硬件块设备的特性,为块设备提供了一个通用的抽象视图。 5. IO 调度层:接收通用块层发出的 IO 请求,缓存请求并试图合并相邻的请求(如果这两个请求的数据在磁盘上是相邻的)。并根据设置好的调度算法,回调驱动层提供的请求处理函数,以处理具体的 IO 请求。 6.驱动层:驱动程序对应具体的物理块设备,它从上层中取出 IO 请求,并根据该 IO 请求中指定的信息,通过向具体块设备的设备控制器发送命令的方式,来操纵设备传输数据。 7.设备层是具体的物理设备。定义了操作具体设备的规范。
以下是一些常用的 Linux IO 命令及其示例用法: 1. ls:列出目录内容 ``` ls -l # 列出详细信息,包括权限、所有者、文件大小等 ls -a # 显示所有文件,包括隐藏文件 ls -lh # 以易的方式显示文件大小 ``` 2. cp:复制文件或目录 ``` cp file1.txt file2.txt # 将 file1.txt 复制为 file2.txt cp -r dir1 dir2 # 递归复制目录 dir1 到 dir2 ``` 3. mv:移动或重命名文件或目录 ``` mv file1.txt file2.txt # 将 file1.txt 重命名为 file2.txt mv file1.txt dir1/file1.txt # 将 file1.txt 移动到目录 dir1 ``` 4. rm:删除文件或目录 ``` rm file.txt # 删除文件 file.txt rm -r dir # 删除目录 dir 及其所有内容 rm -rf dir # 强制删除目录 dir 及其所有内容,不进行确认提示 ``` 5. cat:显示文件内容 ``` cat file.txt # 显示文件 file.txt 的内容 cat file1.txt file2.txt # 合并并显示多个文件的内容 ``` 6. head:显示文件的开头部分 ``` head file.txt # 显示文件 file.txt 的前几行(默认为前10行) head -n 5 file.txt # 显示文件 file.txt 的前5行 ``` 7. tail:显示文件的结尾部分 ``` tail file.txt # 显示文件 file.txt 的后几行(默认为后10行) tail -n 5 file.txt # 显示文件 file.txt 的后5行 tail -f file.txt # 实时追踪文件 file.txt 的内容变化 ``` 8. touch:创建空文件或修改文件时间戳 ``` touch file.txt # 创建空文件 file.txt touch -c file.txt # 修改文件 file.txt 的时间戳为当前时间,如果文件不存在则不创建 ``` 这些是常见的 Linux IO 命令及其示例用法。还有其他很多命令和选项可以用于处理文件、目录和IO操作,你可以通过阅读相关文档或使用命令的帮助文档来了解更多详细信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值