导语 | 本文主要以一张图为基础,向大家介绍Linux在I/O上做了哪些事情,即Linux中直接I/O原理,希望本文的经验和思路能为读者提供一些帮助和思考。

引言

我们先看一张图:

这张图大体上描述了Linux系统上,应用程序对磁盘上的文件进行读写时,从上到下经历了哪些事情。这篇文章就以这张图为基础,介绍Linux在I/O上做了哪些事情。

一、文件系统

(一)什么是文件系统

文件系统,本身是对存储设备上的文件,进行组织管理的机制。组织方式不同,就会形成不同的文件系统。比如常见的Ext4、XFS、ZFS以及网络文件系统NFS等等。

但是不同类型的文件系统标准和接口可能各有差异,我们在做应用开发的时候却很少关心系统调用以下的具体实现,大部分时候都是直接系统调用open, read, write, close来实现应用程序的功能,不会再去关注我们具体用了什么文件系统(UFS、XFS、Ext4、ZFS),磁盘是什么接口(IDE、SCSI,SAS,SATA等),磁盘是什么存储介质(HDD、SSD)应用开发者之所以这么爽,各种复杂细节都不用管直接调接口,是因为内核为我们做了大量的有技术含量的脏活累活。

开始的那张图看到Linux在各种不同的文件系统之上,虚拟了一个VFS,目的就是统一各种不同文件系统的标准和接口,让开发者可以使用相同的系统调用来使用不同的文件系统。

(二)文件系统如何工作(VFS)

  • Linux系统下的文件

在Linux中一切皆文件。不仅普通的文件和目录,就连块设备、套接字、管道等,也都要通过统一的文件系统来管理。

用 ls -l 命令看最前面的字符可以看到这个文件是什么类型brw-r--r-- 1 root    root    1, 2 4月  25 11:03 bnod // 块设备文件
crw-r--r-- 1 root    root    1, 2 4月  25 11:04 cnod // 符号设备文件
drwxr-xr-x 2 wrn3552 wrn3552    6 4月  25 11:01 dir // 目录
-rw-r--r-- 1 wrn3552 wrn3552    0 4月  25 11:01 file // 普通文件
prw-r--r-- 1 root    root       0 4月  25 11:04 pipeline // 有名管道
srwxr-xr-x 1 root    root       0 4月  25 11:06 socket.sock // socket文件
lrwxrwxrwx 1 root    root       4 4月  25 11:04 softlink -> file // 软连接
-rw-r--r-- 2 wrn3552 wrn3552 0 4月  25 11:07 hardlink // 硬链接(本质也是普通文件)

Linux文件系统设计了两个数据结构来管理这些不同种类的文件:

  • inode(index node):索引节点

  • dentry(directory entry):目录项

  • inode

inode是用来记录文件的metadata,所谓metadata在Wikipedia上的描述是data of data,其实指的就是文件的各种属性,比如inode编号、文件大小、访问权限、修改日期、数据的位置等。

wrn3552@novadev:~/playground$ stat file文件:file大小:0               块:0          IO 块:4096   普通空文件
设备:fe21h/65057d      Inode:32828       硬链接:2
权限:(0644/-rw-r--r--)  Uid:( 3041/ wrn3552)   Gid:( 3041/ wrn3552)
最近访问:2021-04-25 11:07:59.603745534 +0800
最近更改:2021-04-25 11:07:59.603745534 +0800
最近改动:2021-04-25 11:08:04.739848692 +0800
创建时间:-

inode和文件一一对应,它跟文件内容一样,都会被持久化存储到磁盘中。所以,inode同样占用磁盘空间,只不过相对于文件来说它大小固定且大小不算大。

  • dentry

dentry用来记录文件的名字、inode指针以及与其他dentry的关联关系。

wrn3552@novadev:~/playground$ tree
.
├── dir
│   └── file_in_dir
├── file
└── hardlink
  • 文件的名字:像dir、file、hardlink、file_in_dir这些名字是记录在dentry里的。

  • inode指针:就是指向这个文件的inode。

  • 与其他dentry的关联关系:其实就是每个文件的层级关系,哪个文件在哪个文件下面,构成了文件系统的目录结构。

不同于inode,dentry是由内核维护的一个内存数据结构,所以通常也被叫做dentry cache。

(三)文件是如何存储在磁盘上的

这里有张图解释了文件是如何存储在磁盘上的:

首先,磁盘再进行文件系统格式化的时候,会分出来3个区:Superblock、inode blocks、data blocks。(其实还有boot block,可能会包含一些bootstrap代码,在机器启动的时候被读到,这里忽略)

其中inode blocks放的都是每个文件的inode,data blocks里放的是每个文件的内容数据。

这里关注一下Superblock,它包含了整个文件系统的metadata,具体有:

  • inode/data block 总量、使用量、剩余量。

  • 文件系统的格式,属主等等各种属性。

Superblock对于文件系统来说非常重要,如果Superblock损坏了,文件系统就挂载不了了,相应的文件也没办法读写。

既然Superblock这么重要,那肯定不能只有一份,坏了就没了,它在系统中是有很多副本的,在Superblock损坏的时候,可以使用fsck(File System Check and repair)来恢复。

回到上面的那张图,可以很清晰地看到文件的各种属性和文件的数据是如何存储在磁盘上的:

  • dentry里包含了文件的名字、目录结构、inode指针。

  • inode指针指向文件特定的inode(存在inode blocks里)

  • 每个inode又指向data blocks里具体的logical block,这里的logical block存的就是文件具体的数据。

这里解释一下什么是logical block:

  • 对于不同存储介质的磁盘,都有最小的读写单元 /sys/block/sda/queue/physical_block_size。

  • HDD叫做sector(扇区),SSD叫做page(页面)

  • 对于hdd来说,每个sector大小512Bytes。

  • 对于SSD来说每个page大小不等(和cell类型有关),经典的大小是4KB。

  • 但是Linux觉得按照存储介质的最小读写单元来进行读写可能会有效率问题,所以支持在文件系统格式化的时候指定block size的大小,一般是把几个physical_block拼起来就成了一个logical block /sys/block/sda/queue/logical_block_size。

  • 理论上应该是logical_block_size>=physical_block_size,但是有时候我们会看到physical_block_size=4K,logical_block_size=512B情况,其实这是因为磁盘上做了一层512B的仿真(emulation)(详情可参考512e和4Kn)

二、ZFS

这里简单介绍一个广泛应用的文件系统ZFS,一些数据库应用也会用到 ZFS。

先看一张ZFS的层级结构图:

这是一张从底向上的图:

  • 将若干物理设备disk组成一个虚拟设备vdev(同时,disk 也是一种vdev)

  • 再将若干个虚拟设备vdev加到一个zpool里。

  • 在zpool的基础上创建zfs并挂载(zvol可以先不看,我们没有用到)

(一)ZFS的一些操作

  • 创建zpool

root@:~ # zpool create tank raidz /dev/ada1 /dev/ada2 /dev/ada3 raidz /dev/ada4 /dev/ada5 /dev/ada6
root@:~ # zpool list tank
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
tank     11G   824K  11.0G        -         -     0%     0%  1.00x  ONLINE  -
root@:~ # zpool status tankpool: tankstate: ONLINEscan: none requested
config:NAME        STATE     READ WRITE CKSUMtank        ONLINE       0     0     0raidz1-0  ONLINE       0     0     0ada1    ONLINE       0     0     0ada2    ONLINE       0     0     0ada3    ONLINE       0     0     0raidz1-1  ONLINE       0     0     0ada4    ONLINE       0     0     0ada5    ONLINE       0     0     0ada6    ONLINE       0     0     0
  • 创建了一个名为tank的zpool

  • 这里的raidz同RAID5

除了raidz还支持其他方案:

  • 创建ZFS

root@:~ # zfs create -o mountpoint=/mnt/srev tank/srev
root@:~ # df -h tank/srev
Filesystem    Size    Used   Avail Capacity  Mounted on
tank/srev     7.1G    117K    7.1G     0%    /mnt/srev
  • 创建了一个ZFS,挂载到了/mnt/srev。

  • 这里没有指定ZFS的quota,创建的ZFS大小即zpool大小。

  • 对ZFS设置quota

root@:~ # zfs set quota=1G tank/srev
root@:~ # df -h tank/srev
Filesystem    Size    Used   Avail Capacity  Mounted on
tank/srev     1.0G    118K    1.0G     0%    /mnt/srev

(二)ZFS特性

  • Pool存储

上面的层级图和操作步骤可以看到ZFS是基于zpool创建的,zpool可以动态扩容意味着存储空间也可以动态扩容。而且可以创建多个文件系统,文件系统共享完整的zpool空间无需预分配。

  • 事务文件系统

ZFS的写操作是事务的,意味着要么就没写,要么就写成功了,不会像其他文件系统那样,应用打开了文件,写入还没保存的时候断电,导致文件为空。

ZFS保证写操作事务采用的是copy on write的方式:

当block B有修改变成B1的时候,普通的文件系统会直接在block B原地进行修改变成B1。

ZFS则会再另一个地方写B1,然后再在后面安全的时候对原来的B进行回收。这样结果就不会出现B被打开而写失败的情况,大不了就是B1没写成功。

这个特性让ZFS在断电后不需要执行fsck来检查磁盘中是否存在写操作失败需要恢复的情况,大大提升了应用的可用性。

  • ARC缓存

ZFS中的ARC(Adjustable Replacement Cache) 读缓存淘汰算法,是基于IBM的ARP(Adaptive Replacement Cache) 演化而来。

在一些文件系统中实现的标准LRU算法其实是有缺陷的:比如复制大文件之类的线性大量I/O操作,导致缓存失效率猛增(大量文件只读一次,放到内存不会被再读,坐等淘汰)

另外,缓存可以根据时间来进行优化(LRU,最近最多使用),也可以根据频率进行优化(LFU,最近最常使用),这两种方法各有优劣,但是没办法适应所有场景。

ARC的设计就是尝试在LRU和LFU之间找到一个平衡,根据当前的I/O workload来调整用LRU多一点还是LFU多一点

ARC定义了4个链表:

  • LRU list:最近最多使用的页面,存具体数据。

  • LFU list:最近最常使用的页面,存具体数据。

  • Ghost list for LRU:最近从LRU表淘汰下来的页面信息,不存具体数据,只存页面信息。

  • Ghost list for LFU:最近从LFU表淘汰下来的页面信息,不存具体数据,只存页面信息。

ARC工作流程大致如下:

  • LRU list和LFU list填充和淘汰过程和标准算法一样。

  • 当一个页面从LRU list淘汰下来时,这个页面的信息会放到LRU ghost表中。

  • 如果这个页面一直没被再次引用到,那么这个页面的信息最终也会在LRU ghost表中被淘汰掉。

  • 如果这个页面在LRU ghost表中未被淘汰的时候,被再一次访问了,这时候会引起一次幽灵(phantom)命中。

  • phantom命中的时候,事实上还是要把数据从磁盘第一次放缓存。

  • 但是这时候系统知道刚刚被LRU表淘汰的页面又被访问到了,说明LRU list太小了,这时它会把LRU list长度加一,LFU长度减一。

  • 对于LFU的过程也与上述过程类似。

三、磁盘类型

磁盘根据不同的分类方式,有各种不一样的类型。

(一)磁盘的存储介质

根据磁盘的存储介质可以分两类(大家都很熟悉):HDD(机械硬盘)和SSD(固态硬盘)

(二)磁盘的接口

根据磁盘接口分类:

  • IDE (Integrated Drive Electronics)

  • SCSI (Small Computer System Interface)

  • SAS (Serial Attached SCSI)

  • SATA (Serial ATA)

  • ...

不同的接口,往往分配不同的设备名称。比如,IDE设备会分配一个hd前缀的设备名,SCSI和SATA设备会分配一个sd前缀的设备名。如果是多块同类型的磁盘,就会按照a、b、c等的字母顺序来编号。

(三)Linux对磁盘的管理

其实在Linux中,磁盘实际上是作为一个块设备来管理的,也就是以块为单位读写数据,并且支持随机读写。每个块设备都会被赋予两个设备号,分别是主、次设备号。主设备号用在驱动程序中,用来区分设备类型;而次设备号则是用来给多个同类设备编号。

g18-"299" on ~# ls -l /dev/sda*
brw-rw---- 1 root disk 8,  0 Apr 25 15:53 /dev/sda
brw-rw---- 1 root disk 8,  1 Apr 25 15:53 /dev/sda1
brw-rw---- 1 root disk 8, 10 Apr 25 15:53 /dev/sda10
brw-rw---- 1 root disk 8,  2 Apr 25 15:53 /dev/sda2
brw-rw---- 1 root disk 8,  5 Apr 25 15:53 /dev/sda5
brw-rw---- 1 root disk 8,  6 Apr 25 15:53 /dev/sda6
brw-rw---- 1 root disk 8,  7 Apr 25 15:53 /dev/sda7
brw-rw---- 1 root disk 8,  8 Apr 25 15:53 /dev/sda8
brw-rw---- 1 root disk 8,  9 Apr 25 15:53 /dev/sda9
  • 这些sda磁盘主设备号都是8,表示它是一个sd类型的块设备。

  • 次设备号0-10表示这些不同sd块设备的编号。

四、Generic Block Layer

可以看到中间的Block Layer其实就是Generic Block Layer。

在图中可以看到Block Layer的I/O调度分为两类,分别表示单队列和多队列的调度:

  • I/O scheduler

  • blkmq

(一)I/O调度

老版本的内核里只支持单队列的I/O scheduler,在3.16版本的内核开始支持多队列blkmq。

这里介绍几种经典的I/O调度策略:

  • 单队列I/O scheduler

  • NOOP:事实上是个FIFO的队列,只做基本的请求合并。

  • CFQ:Completely Fair Queueing,完全公平调度器,给每个进程维护一个I/O调度队列,按照时间片来均匀分布每个进程I/O请求。

  • DeadLine:为读和写请求创建不同的I/O队列,确保达到deadline的请求被优先处理。

  • 多队列blkmq

  • bfq:Budget Fair Queueing,也是公平调度器,不过不是按时间片来分配,而是按请求的扇区数量(带宽)

  • kyber:维护两个队列(同步/读、异步/写),同时严格限制发到这两个队列的请求数以保证相应时间。

  • mq-deadline:多队列版本的deadline。

具体各种I/O调度策略可以参考IOSchedulers

(https://wiki.ubuntu.com/Kernel/Reference/IOSchedulers)

关于blkmq可以参考Linux Multi-Queue Block IO Queueing Mechanism (blk-mq) Details_Details)

多队列调度可以参考Block layer introduction part 2:the request layer

五、性能指标

一般来说I/O性能指标有这5个:

  • 使用率:ioutil,指的是磁盘处理I/O的时间百分比,ioutil只看有没有I/O请求,不看I/O请求的大小。ioutil越高表示一直都有I/O请求,不代表磁盘无法响应新的I/O请求。

  • IOPS:每秒的I/O请求数。

  • 吞吐量/带宽:每秒的I/O请求大小,通常是MB/s或者GB/s为单位。

  • 响应时间:I/O请求发出到收到响应的时间。

  • 饱和度:指的是磁盘处理I/O的繁忙程度。这个指标比较玄学,没有直接的数据可以表示,一般是根据平均队列请求长度或者响应时间跟基准测试的结果进行对比来估算(在做基准测试时,还会分顺序/随机、读/写进行排列组合分别去测IOPS和带宽)

上面的指标除了饱和度外,其他都可以在监控系统中看到。Linux也提供了一些命令来输出不同维度的I/O状态

  • iostat-d-x:看各个设备的I/O状态,数据来源/proc/diskstats。

  • pidstat-d:看近处的I/O。

  • iotop:类似top,按I/O大小对进程排序。

 作者简介

王睿

腾讯云游戏解决方案架构师

腾讯云游戏解决方案架构师,毕业于中山大学。目前负责腾讯云游戏行业解决方案设计等工作,有丰富的游戏运维及开发经验。

超专业解析!10分钟带你搞懂Linux中直接I/O原理相关推荐

  1. 五分钟学会python函数_五分钟带你搞懂python 迭代器与生成器

    前言 大家周末好,今天给大家带来的是Python当中生成器和迭代器的使用. 我当初第一次学到迭代器和生成器的时候,并没有太在意,只是觉得这是一种新的获取数据的方法.对于获取数据的方法而言,我们会一种就 ...

  2. 15个示例让你搞懂Linux中的cd命令

    15个示例让你搞懂Linux中的cd命令 在Linux中,cd(更改目录)命令是新手和系统管理员最重要且使用最广泛的命令之一.对于没有头绪的管理员来说,cd是导航到其他目录以检查日志,执行程序/应用程 ...

  3. 三分钟带您搞懂代理模式

    全文共1439字,预计阅读时间:10分钟 定义: 代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问. 代理模式是一种比较贴近于生活的 设计模式,现实生活中也有很多代理模式的例子: ...

  4. 【前端知识点】promise简书-30分钟带你搞懂promise面试必备

    前言 写作初衷 本书的目的是以目前还在制定中的ECMAScript 6 Promises规范为中心,着重向各位读者介绍JavaScript中对Promise相关技术的支持情况. 通过阅读本书,我们希望 ...

  5. 10张图22段代码,万字长文带你搞懂虚拟内存模型和malloc内部原理

    成功是急不来的.不计较眼前得失,将注意力真正着眼于正在做的事情本身,持续付出努力,才能一步步向前迈进,逐渐达到理想的目标.不着急,才能从容不迫,结果自会水到渠成. 大家好,我是程序喵! 摊牌了,不装了 ...

  6. 【HTTP】10分钟带你快速了解HTTP中常见的状态码(内附大量实例)

    目录 前言 ▶ 为什么要了解状态码? 正题 ▶ 状态码的类别 ▶ 状态码--200(OK) ▶ 状态码--204(Not Content) ▶ 状态码--206(Partial Content) ▶ ...

  7. 【超分辨率】3分钟带你读懂

    内容概述:超分辨率技术是指从观测到的低分辨率图像重建出相应的高分辨率图像,随着深度学习技术的发展,超分辨率技术在电影.医疗影像.游戏等领域的应用,也愈发广泛.在本文中,帝视科技将深入探讨超分辨率的背景 ...

  8. pycharm导入jieba包_3分钟带你搞懂Python模块、包的区别和使用

    一.模块和包的定义 模块的定义:任何*.py 的文件都可以当作模块使用import 导入 包的定义:包含一个__init__.py和其他模块.其他子包的一个目录 实际项目中,所谓的包和模块分别代表什么 ...

  9. python 包和模块的区别_3分钟带你搞懂Python模块、包的区别和使用

    一.模块和包的定义 模块的定义:任何*.py 的文件都可以当作模块使用import 导入 包的定义:包含一个__init__.py和其他模块.其他子包的一个目录 实际项目中,所谓的包和模块分别代表什么 ...

最新文章

  1. 共抗疫情,飞书助力学校、企业等组织机构高效远程协作
  2. 人脑动态功能网络连接模式能够鉴别个体并预测其认知功能
  3. 浅谈博客园的初使用体验
  4. 004_Bean标签
  5. 全球及中国生物柴油行业产量规模及市场消费需求预测报告2021-2027年
  6. 趣学python编程第六章答案_Python核心编程-第六章-习题
  7. UCOS 操作系统 安装配置环境
  8. 如何衡量两个词的相关度
  9. spark sql合并小文件_Spark SQL小文件问题在OPPO的解决方案
  10. abstract类中不可以有private的成员_C++ 类:声明成员函数与实现
  11. 【英语学习】【English L06】U02 Food L4 Food around the world
  12. Leetcode-952 Largest Component Size by Common Factor(按公因数计算最大组件大小)
  13. Linux 学习笔记
  14. 关于win10自带邮箱绑定163邮箱的设置
  15. 【光线追踪系列十四】蒙特卡洛积分与重要性采样
  16. 解决虚拟专用网络拨号后本地上网流量都走此网络的问题
  17. UEFI开发与调试---edk2中的Module
  18. 网络爬虫 测试代理IP
  19. Zero-shot Learning / One-shot Learning
  20. 服务容错 - Hystrix

热门文章

  1. MySQL触发器的使用规则
  2. 低学历学什么技术有前途可以月入过万?
  3. java重要基础知识点_必看 | 新人必看的Java基础知识点大梳理
  4. [0x17基本数据结构-二叉堆]-Supermarket
  5. Codeforces Round #699 (Div. 2) F - AB Tree(贪心、树上DP)超级清晰,良心题解,看不懂来打我 ~
  6. 【网络流】解题报告:luogu P2740 [USACO4.2]草地排水Drainage Ditches(Edmonds-Karp增广路,最大流模板)
  7. 解题报告:P3834 【模板】可持久化线段树 2(主席树)详解
  8. 文件服务器raid1设置,文件服务器raid1设置
  9. 17.电话号码的组合
  10. c语言 左补1,转专业后对于C语言补修的一些体会(1)