热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux内核io体系之磁盘io

文章目录架构图前言名词解释IO体系VFS层superblockinodedentryfilePageCache层脏页刷盘预读策略映射层通用块层IO调度层块设备驱动层物理设备层FAQ


文章目录

    • 架构图
    • 前言
    • 名词解释
    • IO体系
    • VFS层
      • superblock
      • inode
      • dentry
      • file
    • PageCache层
      • 脏页刷盘
      • 预读策略
    • 映射层
    • 通用块层
    • IO调度层
    • 块设备驱动层
    • 物理设备层
    • FAQ


架构图

在这里插入图片描述
在这里插入图片描述


前言

Linux I/O体系是Linux内核的重要组成部分,主要包含网络IO、磁盘IO等。基本所有的技术栈都需要与IO打交道,分布式存储系统更是如此。本文主要简单分析一下磁盘IO,看看一个IO请求从发起到完成到底经历了哪些流程。


名词解释

Buffered I/O:缓存IO又叫标准IO,是大多数文件系统的默认IO操作,经过PageCache。
Direct I/O:直接IO,By Pass PageCache。offset、length需对齐到block_size。
Sync I/O:同步IO,即发起IO请求后会阻塞直到完成。缓存IO和直接IO都属于同步IO。
Async I/O:异步IO,即发起IO请求后不阻塞,内核完成后回调。通常用内核提供的Libaio。
Write Back:Buffered IO时,仅仅写入PageCache便返回,不等数据落盘。
Write Through:Buffered IO时,不仅仅写入PageCache,而且同步等待数据落盘。


IO体系

我们先看一张总的Linux内核存储栈图片:
在这里插入图片描述
Linux IO存储栈主要有以下7层:
在这里插入图片描述


VFS层

我们通常使用open、read、write等函数来编写Linux下的IO程序。接下来我们看看这些函数的IO栈是怎样的。在此之前我们先简单分析一下VFS层的4个对象,有助于我们深刻的理解IO栈。

VFS层的作用是屏蔽了底层不同的文件系统的差异性,为用户程序提供一个统一的、抽象的、虚拟的文件系统,提供统一的对外API,使用户程序调用时无需感知底层的文件系统,只有在真正执行读写操作的时候才调用之前注册的文件系统的相应函数。

VFS支持的文件系统主要有三种类型:
基于磁盘的文件系统:Ext系列、XFS等。
网络文件系统:NFS、CIFS等。
特殊文件系统:/proc、裸设备等。

VFS主要有四个对象类型(不同的文件系统都要实现):
superblock:整个文件系统的元信息。对应的操作结构体:struct super_operations。
inode:单个文件的元信息。对应的操作结构体:struct inode_operations。
dentry:目录项,一个文件目录对应一个dentry。对应的操作结构体:struct dentry_operations。
file:进程打开的一个文件。对应的操作结构体:struct file_operations


superblock

superblock结构体定义了整个文件系统的元信息,以及相应的操作。


inode

inode结构体定义了文件的元数据,比如大小、最后修改时间、权限等,除此之外还有一系列的函数指针,指向具体文件系统对文件操作的函数,包括常见的open、read、write等,由i_fop函数指针提供。


dentry

dentry是目录项,由于每一个文件必定存在于某个目录内,我们通过路径查找一个文件时,最终肯定找到某个目录项。在Linux中,目录和普通文件一样,都是存放在磁盘的数据块中,在查找目录的时候就读出该目录所在的数据块,然后去寻找其中的某个目录项
在我们使用Linux的过程中,根据目录查找文件的例子无处不在,而目录项的数据又都是存储在磁盘上的,如果每一级路径都要读取磁盘,那么性能会十分低下。所以需要目录项缓存,把dentry放在缓存中加速。

VFS把所有的dentry放在dentry_hashtable哈希表里面,使用LRU淘汰算法。


file

用户程序能接触的VFS对象只有file,由进程管理。我们常用的打开一个文件就是创建一个file对象,并返回一个文件描述符。出于隔离性的考虑,内核不会把file的地址返回,而是返回一个整形的fd。

file对象是由内核进程直接管理的。每个进程都有当前打开的文件列表,放在files_struct结构体中。


PageCache层

在HDD时代,由于内核和磁盘速度的巨大差异,Linux内核引入了页高速缓存(PageCache),把磁盘抽象成一个个固定大小的连续Page,通常为4K。对于VFS来说,只需要与PageCache交互,无需关注磁盘的空间分配以及是如何读写的。

当我们使用Buffered IO的时候便会用到PageCache层,与Direct IO相比,用户程序无需offset、length对齐。是因为通用块层处理IO都必须是块大小对齐的。

Buffered IO中PageCache帮我们做了对齐的工作:如果我们修改文件的offset、length不是页大小对齐的,那么PageCache会执行RMW的操作,先把该页对应的磁盘的数据全部读上来,再和内存中的数据做Modify,最后再把修改后的数据写回磁盘。虽然是写操作,但是非对齐的写仍然会有读操作。

Direct IO由于跳过了PageCache,直达通用块层,所以需要用户程序处理对齐的问题。


脏页刷盘

如果发生机器宕机,位于PageCache中的数据就会丢失;所以仅仅写入PageCache是不可靠的,需要有一定的策略将数据刷入磁盘。通常有几种策略:

手动调用fsync、fdatasync刷盘,可参考浅谈分布式存储之sync详解。
脏页占用比例超过了阈值,触发刷盘。
脏页驻留时间过长,触发刷盘。
Linux内核目前的做法是为每个磁盘都建立一个线程,负责每个磁盘的刷盘。


预读策略

从VFS层我们知道写是异步的,写完PageCache便直接返回了;但是读是同步的,如果PageCache没有命中,需要从磁盘读取,很影响性能。如果是顺序读的话PageCache便可以进行预读策略,异步读取该Page之后的Page,等到用户程序再次发起读请求,数据已经在PageCache里,大幅度减少IO的次数,不用阻塞读系统调用,提升读的性能。


映射层

映射层是在PageCache之下的一层,由多个文件系统(Ext系列、XFS等,打开文件系统的文件)以及块设备文件(直接打开裸设备文件)组成,主要完成两个工作:

内核确定该文件所在文件系统或者块设备的块大小,并根据文件大小计算所请求数据的长度以及所在的逻辑块号。
根据逻辑块号确定所请求数据的物理块号,也即在在磁盘上的真正位置。
由于通用块层以及之后的的IO都必须是块大小对齐的,我们通过DIO打开文件时,略过了PageCache,所以必须要自己将IO数据的offset、length对齐到块大小。

我们使用的DIO+Libaio直接打开裸设备时,跳过了文件系统,少了文件系统的种种开销,然后进入通用块层,继续之后的处理。


通用块层

通用块层存在的意义也和VFS一样,屏蔽底层不同设备驱动的差异性,提供统一的、抽象的通用块层API。


IO调度层

Linux调度层是Linux IO体系中的一个重要组件,介于通用块层和块设备驱动层之间。IO调度层主要是为了减少磁盘IO的次数,增大磁盘整体的吞吐量,会队列中的多个bio进行排序和合并,并且提供了多种IO调度算法,适应不同的场景。

Linux内核目前提供了以下几种调度策略:

Deadline:默认的调度策略,加入了超时的队列。适用于HDD。
CFQ:完全公平调度器。
Noop:No Operation,最简单的FIFIO队列,不排序会合并。适用于SSD、NVME。


块设备驱动层

每一类设备都有其驱动程序,负责设备的读写。IO调度层的请求也会交给相应的设备驱动程序去进行读写。大部分的磁盘驱动程序都采用DMA的方式去进行数据传输,DMA控制器自行在内存和IO设备间进行数据传送,当数据传送完成再通过中断通知CPU。

通常块设备的驱动程序都已经集成在了kernel里面,也即就算我们直接调用块设备驱动驱动层的代码还是要经过内核。

spdk实现了用户态、异步、无锁、轮询方式NVME驱动程序。块存储是延迟非常敏感的服务,使用NVME做后端存储磁盘时,便可以使用spdk提供的NVME驱动,缩短IO流程,降低IO延迟,提升IO性能。


物理设备层

物理设备层便是我们经常使用的HDD、SSD、NVME等磁盘设备了


FAQ

1、write返回成功数据落盘了吗?
Buffered IO:write返回数据仅仅是写入了PageCache,还没有落盘。

Direct IO:write返回数据仅仅是到了通用块层放入IO队列,依旧没有落盘。

此时设备断电、宕机仍然会发生数据丢失。需要调用fsync或者fdatasync把数据刷到磁盘上,调用命令时,磁盘本身缓存(DiskCache)的内容也会持久化到磁盘上。

2、write系统调用是原子的吗?
write系统调用不是原子的,如果有多线程同时调用,数据可能会发生错乱。可以使用O_APPEND标志打开文件,只能追加写,这样多线程写入就不会发生数据错乱。

3、mmap相比read、write快在了哪里?
mmap直接把PageCache映射到用户态,少了一次系统调用,也少了一次数据在用户态和内核态的拷贝。

mmap通常和read搭配使用:写入使用write+sync,读取使用mmap。

4、为什么Direct IO需要数据对齐?
DIO跳过了PageCache,直接到通用块层,而通用块层的IO都必须是块大小对齐的,所以需要用户程序自行对齐offset、length。

5、Libaio的IO栈?
write()—>sys_write()—>vfs_write()—>通用块层—>IO调度层—>块设备驱动层—>块设备

6、为什么需要 by pass pagecache?
当应用程序不满Linux内核的Cache策略,有更适合自己的Cache策略时可以使用Direct IO跳过PageCache。例如Mysql。

7、为什么需要 by pass kernel?
当应用程序对延迟极度敏感时,由于Linux内核IO栈有7层,IO路径比较长,为了缩短IO路径,降低IO延迟,可以by pass kernel,直接使用用户态的块设备驱动程序。例如spdk的nvme,阿里云的ESSD。

8、为什么需要直接操作裸设备?
当应用程序仅仅使用了基本的read、write,用不到文件系统的大而全的功能,此时文件系统的开销对于应用程序来说是一种累赘,此时需要跳过文件系统,接管裸设备,自己实现磁盘分配、缓存等功能,通常使用DIO+Libaio+裸设备。例如Ceph FileStore的Journal、Ceph BlueStore。


推荐阅读
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • Linux的uucico命令使用方法及工作模式介绍
    本文介绍了Linux的uucico命令的使用方法和工作模式,包括主动模式和附属模式。uucico是用来处理uucp或uux送到队列的文件传输工具,具有操作简单快捷、实用性强的特点。文章还介绍了uucico命令的参数及其说明,包括-c或--quiet、-C或--ifwork、-D或--nodetach、-e或--loop、-f或--force、-i或--stdin、-I--config、-l或--prompt等。通过本文的学习,读者可以更好地掌握Linux的uucico命令的使用方法。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • Centos下安装memcached+memcached教程
    本文介绍了在Centos下安装memcached和使用memcached的教程,详细解释了memcached的工作原理,包括缓存数据和对象、减少数据库读取次数、提高网站速度等。同时,还对memcached的快速和高效率进行了解释,与传统的文件型数据库相比,memcached作为一个内存型数据库,具有更高的读取速度。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • HTML学习02 图像标签的使用和属性
    本文介绍了HTML中图像标签的使用和属性,包括定义图像、定义图像地图、使用源属性和替换文本属性。同时提供了相关实例和注意事项,帮助读者更好地理解和应用图像标签。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
author-avatar
mobiledu2502883787
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有