block io生命历程

作为存储业务的一个重要组成部分,block IO是非易失存储的唯一路径,它的生命历程每个阶段都直接关乎我们手机的性能、功耗、甚至寿命。本文试图通过block IO的产生、调度、下发、返回的4个阶段,阐述一个block IO的生命历程。

一、什么是块设备和块设备层

c767b5ae202084273161e51dc627f9a8.png

从计算机诞生开始,就有了IO设备,IO设备大致分为两类,块设备和字符设备,块设备的2个重要特性就是:块存储和可寻址。而块设备层,就是通过组织管理,使得向块设备下发的请求能够高效合理的完成的一种软件逻辑层。如上图所示,在传统的磁盘结构中,减少吊杆/磁头的移动(机械动作),容易找到目标地址,就能提升IO性能。

块设备层在整个存储栈中的位置如下图所示,上承文件系统,下接具体的块设备驱动(UFS,EMMC驱动):

595f3e2dc75b1c903e0be7e851a3e50f.png

通过blktrace这个开源工具可以用来分析IO轨迹和性能,从AQGP开始创建线程的plug,再到后面的AQM完成了线程内部plug的merge合并,最后IUDC完成了线程内部plug的下发和返回。

ca327358f3eb9870f2869c36bc559280.png

二、  block IO的产生

大到手机里面每一个应用程序的打开,小到很多人学生时代写过的一个C语言程序,都会伴随block IO的产生。究其本质,只要调用了libc库中的open, read, close,write, fsync, sync这些库函数,都可能产生blockIO。

1. 用户态常用文件操作

aaf424f6e62e0af3e3d83de35ddc0003.png

fdf8b694022eeae664564cd8828f9f5d.png

2.文件系统IO~预读

daa7376dba18375a47c0eee663ad97b8.png

3.文件系统IO~脏页回写

5564b6a24dd261239c6e37031b9c56e0.png

4.文件系统IO~fsync & sync

79b933184744a0b84a6500860604b345.png

5.文件系统IO~dm设备写

通过下面的命令获取dm设备(253:26)的轨迹:

./blktrace -d /dev/block/dm-26 -o - |./blkparse -i -

5bc9fa0f66408c86dc3ec62065e4279b.png

由于dm设备到真实的物理设备,有一层映射,对于同一个逻辑地址8898352通过下面的命令获取针对这个逻辑地址的block IO在其映射的物理设备(259:41)的轨迹,在物理设备中,块地址经过remap从8898352变成了33294128。

./blktrace -d /dev/block/sdc57 -o - | ./blkparse -i -

911253bc0891527183bb2483abe7dcee.png

这里以verity类型的dm设备为例,其block io的产生路径:

1c7b13f9402620e583d02cfbcd216947.png

6.direct-IO的产生路径

上面的预读,脏页回写,sync操作,都是经过了page cache,有些跑分软件如androbench不会经过page cache,更关注直接的底层存储性能,会采用direct-IO的方式。下图是direct-IO的block IO产生路径。

54d1bf7d1123eda698b914ec972e1437.png

三、block IO的调度

1.IO调度整体框架

调度在我们日常生活中会经常遇到,如电梯,或者打车司机派单拼车,错峰吃饭,错峰上下班等,都是为了更好的整体性能和能耗。

600f752b9b82073d93ba271d54db7aba.png

61570c6b42db0072712b3847bca01363.png

前面列举了一些block IO的产生场景,当这些IO产生后,为了更好的整体性能和能耗,它们也需要合适的调度机制。从IO产生后,经过软队列,调度器,硬队列,最终完成派发。

a229a9bb838416405b442b2269f5c9ad.png

2.关键数据结构bio,request,page,sector的关系

一个或多个bio最后合并为一个request,每个request作为面向存储器件驱动的直接数据结构,里面携带了内存页虚拟地址和存储介质逻辑地址,它是易失介质和非易失介质进行数据交互的桥梁。

每个bio里面包含了一个bio_vec数组,每个数组元素指向一个内存page。

每个bio里面包含了一个bvec_iter,包含了这个io指向的存储介质的sector。

内存page没有连续的要求,但是存储介质的sector必须是首尾相连的,因为在驱动代码中,sglist可以包含多个离散点,而存储介质中的sector地址不连续,那么就会导致磁头反复调整,极大降低性能。而sglist由于是内存总线寻址访问,不存在性能的问题。在flash介质,虽然不涉及磁头调整,但如果不连续,编程速度也会大大降低。

9f024d3db39c25605de3c11ce4d5dd1b.png

3.bio -> request

一个bio经历split,plug merge,电梯merge,最后获取merge到一个已有的request或者获取一个全新的request。

88c8a3b30ef576c900a98e07672f698b.png

4.block IO所在dev的remap

为了更好的理解block IO的remap,merge操作,这里说一下块设备和分区表的概念。

每个设备(比如LU0,LU1)都有一个gendisk的结构体,但是一个LU(Logical Unit)经常会被分割为多个分区。gendisk包含了这个设备的分区表,对于每个被分割的分区,都可以独立挂载自己的文件系统,并在文件系统内从0开始寻址。当形成IO下发到器件时,由于对于器件内部的地址管理是以LU为单位,因此,就需要通过找到先找到改分区在分区表中的偏移,再加上文件系统内部的偏移,才构成面向LU的寻址逻辑地址。

37857d3ec2f9a5e9ecc65de9cd26e228.png

5608d2673f80019d28e009c49af46176.png

5.bio的split

一个bio有自己可以承受着的最大数据量,如果超过,则会被拆分,下图是512KB为边界的一个拆分示意图。

d9c405736fb81a199d00c25105739410.png

6.  bio的merge

bio会尝试在本线程自身plug中merge一次,如果没有merge成功,则继续尝试本队列对应的电梯队列里面进行merge,对于mq(multiqueue),也可以在soft-queue里面尝试merge。

2b8ce3a8cbf4f21d4ff4fb4306dc9808.png

7.线程的plug和unplug

Merge和plug都是蓄流,减少请求向存储器件下发的频率,plug的等待也会增加merge的机会,结伴而行才能提升整体IO性能。在schedule, io_schedule,或线程显性调用blk_finish_plug的时候,才会开闸。

e1de151d4d524c16cf379e9e88afee68.png

8.  电梯调度算法

每个队列可以配置一个io scheduler,即IO调度器,常见的有noop, deadline, cfq等,电梯调度进一步把request请求进行合并和排序,根据所选择的算法(根据时间片,进程优先级,同步异步等因素),决定下一个dispatch的request请求。

b48e7678154edb086233b463502fc1d2.png

9.  queues之间的关系

Linux块设备层已逐步切换到multiqueue , Linux5.0以后单队列代码已被完全移除。multiqueue核心思路是为每个CPU分配一个软件队列,为存储设备的每个硬件队列分配一个硬件派发队列,大大减少锁竞争。在整个block IO的生命历程中,有3个常见的队列类型,分别是: request_queue, blk_mq_ctx, blk_mq_hw_ctx。

每个块设备有1个request_queue,包含设备相关的队列属性,可以理解为队列大管家。

4526db732561995b3dc793133389a5c7.png

每个块设备有1个或者多个硬队列,用于面向底层驱动,如tag的管理。request_queue,blk_mq_ctx,blk_mq_hw_ctx之间可以相互遍历。

d5aefb673e2f409cd1b1a1fcb5556669.png

eba76aefc13d31d710b56d6418d812af.png

四、 block IO的下发

1.生产者和消费者模型

经历过漫长的产生,调度环节,一个request终于开始向器件下发了。这里以常见的生产者消费者模型抽象一下,每个设备都有自己的生产者队列(电梯队列,software queue),但消费者队列(hardwardqueue)却可能是共享的,在单硬件队列场景中(当前UFS,eMMC)。

4a5d8128e245df66ac9531cfa477de79.png

2. scsi子系统和ufs设备

ufs设备采用了scsi协议定义的通用的命令集(读,写,unmap等),以及scsi子系统通用的异常处理等各种流程,所以ufs驱动在初始化后注册到scsi子系统里面,作为一个scsi设备。

81b5ccc0a587ab98137d9e0cbe09a575.png

前面看到的sdc,sdc1,sdc2就是在上面的sd_probe函数中完成的。

9f7065d00d324dde76d33959c13b2031.png

sd_probe中同时也会解析出这个设备对应的分区表,并且把这个设备对应的每个分区添加到块设备里面。

3. scsi设备的关键结构

每个scsi_device共用一个host,通过这个host可以找到所有的scsi设备。

   Ufs注册的scsi设备scsi_device中,所有的scsi_device共用一个hostdata,即ufs_hba。

不同scsi_device有自己的request_queue。

另外1个scsi_device对应1个LU,如下图的W-LUN和LUN属于不同的scsi_device。

以某平台的6个scsi_device(3个LUN+3个W-LUN)为例,其数据结构对应关系:

8cdff33f38fafa7bff5652a83d2429f9.png

4a12fa71b03afba57bdbab2f89b414fd.png

4. IO的获取和下发

作为消费者,从生产者中获取request请求,这个获取的过程会有个优先级排序,先从哪个链表里面获取,取决于不同的策略。

d3a31b941c668487d861f64d01c33529.png

5. mmc和ufs底层驱动对接

每个块设备在生成时,会设置自己的request_queue及其属性,回调函数,比如mmc和ufs设备分别设定了mmc_request_fn和scsi_request_fn作为这个request_queue的request_fn,从而实现了block层和设备驱动层的解耦。

cdd9916a47dce90204b727b297f02e6d.png

6. scsi_device busy等异常路径处理

请求进入到scsi层时,会对scsi target和scsi host的各种状态进行判断,检查其是否满足下发的条件。当出现IO出现异常时,从block层的timeout回调开始,调用到scsi层,进一步再调到ufs驱动层的异常处理函数,如ufshcd_abort,ufshcd_eh_device_reset_handler。

f004d6e60f2b24a52ed6d9157fcf59b9.png

五、 block IO的返回

1. 控制器的硬中断

存储器件处理完request请求后,由controller向GIC上报中断,进入中断上半部处理,再到softirq的raise,整体流程如下。有些request请求指定了某个cpu,这里会涉及当前上半部接受中断cpu和其指定cpu之间的cache共享问题,如果共享,即便两者不同,也会在当前上半部接受所在cpu触发当前cpu的softirq,主要是考虑性能问题。

c9ba548661540b307acbeada3efd2b6b.png

2. 软中断

3e6e6f3bbd61fb1a04c942c79f75c648.png

3.  文件系统回调

每个进程在下发IO请求后,会把自己置于wait队列,当IO请求返回时,通过自下而上的层层回调,最后调到wait_on_page_locked把当前page的waiter进程唤醒,从而完成了整个block IO的生命历程。

b52176465243c1256a9ac6c7a626cff0.png

六、总结

性能问题中经常会遇到某个进程iowait时间过长的问题,那么这个时间的究竟是怎么统计的,涵盖了哪个范围?从cpu角度看,就是该进程被dequeue到被重新enqueue的时间范围。从block IO角度看,就是涵盖了该进程所发起的整个block IO的生命历程的时间范围。通过这个blockIO的4个生命历程阶段,可以进一步细化了解iowait这种耗时的分布。

46103fc53d4ebe3ddfd127bd287257a9.png

参考文献:

1、  https://blog.csdn.net/qq_22865601/article/details/105782739

2、  https://blog.csdn.net/g382112762/article/details/79606485

3、  https://m.sohu.com/a/193179172_505795

4、https://blog.csdn.net/weixin_30399155/article/details/95344026

a5a0dd2963e8f4563be9f9ceae7bb22c.gif

长按关注内核工匠微信


Linux 内核黑科技 | 技术文章 | 精选教程
  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

内核工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值