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

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

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

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


2.文件系统IO~预读

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

4.文件系统IO~fsync & sync

5.文件系统IO~dm设备写
通过下面的命令获取dm设备(253:26)的轨迹:
./blktrace -d /dev/block/dm-26 -o - |./blkparse -i -

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

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

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

资料直通车:Linux内核源码技术学习路线+视频教程内核源码
学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈
三、block IO的调度
http://1.IO调度整体框架
调度在我们日常生活中会经常遇到,如电梯,或者打车司机派单拼车,错峰吃饭,错峰上下班等,都是为了更好的整体性能和能耗。


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

2.关键数据结构bio,request,page,sector的关系
一个或多个bio最后合并为一个request,每个request作为面向存储器件驱动的直接数据结构,里面携带了内存页虚拟地址和存储介质逻辑地址,它是易失介质和非易失介质进行数据交互的桥梁。
每个bio里面包含了一个bio_vec数组,每个数组元素指向一个内存page。
每个bio里面包含了一个bvec_iter,包含了这个io指向的存储介质的sector。
内存page没有连续的要求,但是存储介质的sector必须是首尾相连的,因为在驱动代码中,sglist可以包含多个离散点,而存储介质中的sector地址不连续,那么就会导致磁头反复调整,极大降低性能。而sglist由于是内存总线寻址访问,不存在性能的问题。在flash介质,虽然不涉及磁头调整,但如果不连续,编程速度也会大大降低。

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

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


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

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

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

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

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

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


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

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

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

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)为例,其数据结构对应关系:


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

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

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

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

2. 软中断

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

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

原文作者:内核工匠

相关文章:
一文解析block io生命历程
作为存储业务的一个重要组成部分,block IO是非易失存储的唯一路径,它的生命历程每个阶段都直接关乎我们手机的性能、功耗、甚至寿命。本文试图通过block IO的产生、调度、下发、返回的4个阶段,阐述一个block IO的生命历程。 一、什么是块设备…...
Python爬虫学习之旅:从入门到精通,要学多久?
导语: 随着信息时代的发展,大量的数据和信息储存在互联网上,这为我们提供了获取和利用这些数据的机会。而Python爬虫作为一种强大的工具,可以帮助我们从网页中提取数据,并进行进一步的分析和挖掘。然而,对…...
HarmonyOS/OpenHarmony(Stage模型)卡片开发应用上下文Context使用场景一
1.获取应用文件路径 基类Context提供了获取应用文件路径的能力,ApplicationContext、AbilityStageContext、UIAbilityContext和ExtensionContext均继承该能力。应用文件路径属于应用沙箱路径。上述各类Context获取的应用文件路径有所不同。 通过ApplicationContext…...
MAE 论文精读 | 在CV领域自监督的Bert思想
1. 背景 之前我们了解了VIT和transformer MAE 是基于VIT的,不过像BERT探索了自监督学习在NLP领域的transformer架构的应用,MAE探索了自监督学习在CV的transformer的应用 论文标题中的Auto就是说标号来自于图片本身,暗示了这种无监督的学习 …...
C++中内存的分配
一个由C/C编译的程序占用的内存分为以下几个部分 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。 2、堆区(heap) — 一般由程序员分配释放, 若程序…...
Qt中的垂直布局QVBoxLayout和水平布局QHBoxLayout
文章目录 QVBoxLayoutQHBoxLayout QVBoxLayout Qt中的垂直布局(Vertical Layout)是用来将控件按垂直方向进行排列的布局管理器。下面是一些常用的Qt Vertical Layout的函数及其用法示例: QVBoxLayout类的构造函数: QVBoxLayout…...
【C#学习笔记】委托和事件
文章目录 委托委托的定义委托实例化委托的调用多播委托 为什么使用委托?官方委托泛型方法和泛型委托 事件为什么要有事件?事件和委托的区别: 题外话——委托与观察者模式 委托 在 .NET 中委托提供后期绑定机制。 后期绑定意味着调用方在你所…...
堆排序简介
概念: 堆排序是一种基于二叉堆数据结构的排序算法。它的概念是通过将待排序的元素构建成一个二叉堆,然后通过不断地取出堆顶元素并重新调整堆的结构来实现排序。 算法步骤: 构建最大堆(或最小堆):将待排…...
React Diff算法
文章目录 React Diff算法一、它的作用是什么?二、React的Diff算法1.了解一下什么是调和?2.react的diff算法3.React Diff的三大策略4.tree diff:1、如果DOM节点出现了跨层级操作,Diff会怎么办? 5. component diff:6. e…...
07 mysql5.6.x docker 启动, 无 config 目录导致客户端连接认证需要 10s
前言 呵呵 最近再一次 环境部署的过程中碰到了这样的一个问题 我基于 docker 启动了一个 mysql 服务, 然后 挂载出了 数据目录 和 配置目录, 没有手动复制配置目录出来, 所以配置目录是空的 然后 我基于 docker 启动了一个 nacos, 配置数据库设置为上面的这个 mysql 然后 启…...
GO GC
GO GC 垃圾回收(Garbage Collection,简称GC)是编程语言中提供的自动的内存管理机制,自动释放不需要的对象,让出存储器资源,无需程序员手动执行。 Golang中的垃圾回收主要应用三色标记法,GC过程和其他用户goroutine可…...
ECharts配合Node.js爬虫实现数据可视化
数据可视化简介 可视化技术是将数据和信息以图形化的方式展示出来,以便更好地理解和分析。可视化技术通常使用各种图表、图形、动画和交互式效果来呈现数据。可视化技术有以下几个基本概念: 数据:可视化技术的基础是数据。数据可以是数字、文…...
[Linux] C获取键盘,鼠标数据
键盘检测指令:cat /dev/input/event1 | hexdump 鼠标检测指令:cat /dev/input/event2 | hexdump 当键盘/鼠标有输入时,会有对应的一堆16进制输出。它其实对应着input_event结构体【24字节】。 struct input_event {struct timeval time;_…...
户外跑步用什么耳机、户外运动耳机推荐
跑步是一项简单的运动,只需要交替迈左右腿就可以进行。然而,跑步有时可能变得单调乏味。即使是意志坚定、热爱跑步的人,在这个漫长的过程中也会感到乏味,更不用说像你我这样的普通跑者了。音乐能够让跑步变得更加有趣,…...
ubuntu设置系统代理
安装trojan等代理工具并配置启动,得到端口号 例如 10.10.1.10:8080系统代理设置 我们将在/etc/profile.d/proxy.sh下添加一个shell脚本文件,这将确保设置适用于所有已登录的用户: sudo vim /etc/profile.d/proxy.sh将以下内容写到文档中&…...
java定时任务如何取消
java定时任务如何取消,并比如,我之前想每周二晚上6点自动生成一条devops流水线,现在我想停掉 答案: 在Java中,可以使用ScheduledExecutorService类来创建定时任务。要取消定时任务,可以调用ScheduledFutur…...
gitlab 9.05 版本获取合并请求的API接口报错404是为什么
gitlab 9.05 版本获取合并请求的API接口报错404是为什么 答案: 出现404错误表示请求的资源未找到。在这种情况下,可能有以下几个原因导致API接口报错404: 版本不匹配:请确保你使用的是GitLab 9.05版本的API接口,如果使…...
微服务(多级缓存)
目录 多级缓存 1.什么是多级缓存 2.JVM进程缓存 2.2.初识Caffeine 2.3.实现JVM进程缓存 2.3.1.需求 2.3.2.实现 3.Lua语法入门 3.1.初识Lua 3.1.HelloWorld 3.2.变量和循环 3.2.1.Lua的数据类型 3.2.2.声明变量 3.2.3.循环 3.3.条件控制、函数 3.3.1.函数 3.3.…...
阿里云配置MySQL-server 8.0远程登录
Ubuntu 22.04 LTS 安装MySQL-Server 8.0 # apt search mysql-server # apt install mysql-server重建服务 # service mysql stop # vi /etc/mysql/mysql.conf.d/mysqld.cnf ... bind-address 0.0.0.0 ... # service mysql start # lsof -i:3306 COMMAND PID USER FD …...
清洁能源使用的社会发展意义
应用清洁能源是转变经济增加途径的有效手段,能够在减少污染物、降低企业经营成本的同时,提高企业经济效益和社会经济效益。 应用清洁能源是保护环境的最佳方式和必然选择,改变末端治理的现状,采取以预防为主的环境保护与发展理…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...
C# winform教程(二)----checkbox
一、作用 提供一个用户选择或者不选的状态,这是一个可以多选的控件。 二、属性 其实功能大差不差,除了特殊的几个外,与button基本相同,所有说几个独有的 checkbox属性 名称内容含义appearance控件外观可以变成按钮形状checkali…...
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章 摘要: 操作系统内核的安全性、稳定性至关重要。传统 Linux 内核模块开发长期依赖于 C 语言,受限于 C 语言本身的内存安全和并发安全问题,开发复杂模块极易引入难以…...
Tauri2学习笔记
教程地址:https://www.bilibili.com/video/BV1Ca411N7mF?spm_id_from333.788.player.switch&vd_source707ec8983cc32e6e065d5496a7f79ee6 官方指引:https://tauri.app/zh-cn/start/ 目前Tauri2的教程视频不多,我按照Tauri1的教程来学习&…...
