热门标签 | 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。


推荐阅读
  • 深入浅出:Hadoop架构详解
    Hadoop作为大数据处理的核心技术,包含了一系列组件如HDFS(分布式文件系统)、YARN(资源管理框架)和MapReduce(并行计算模型)。本文将通过实例解析Hadoop的工作原理及其优势。 ... [详细]
  • 本文详细介绍了如何在PHP中使用Memcached进行数据缓存,包括服务器连接、数据操作、高级功能等。 ... [详细]
  • H5技术实现经典游戏《贪吃蛇》
    本文将分享一个使用HTML5技术实现的经典小游戏——《贪吃蛇》。通过H5技术,我们将探讨如何构建这款游戏的两种主要玩法:积分闯关和无尽模式。 ... [详细]
  • 计算机视觉初学者指南:如何顺利入门
    本文旨在为计算机视觉领域的初学者提供一套全面的入门指南,涵盖基础知识、技术工具、学习资源等方面,帮助读者快速掌握计算机视觉的核心概念和技术。 ... [详细]
  • Golang与微服务架构:构建高效微服务
    本文探讨了Golang在微服务架构中的应用,包括Golang的基本概念、微服务开发的优势、常用开发工具以及具体实践案例。 ... [详细]
  • 时序数据是指按时间顺序排列的数据集。通过时间轴上的数据点连接,可以构建多维度报表,揭示数据的趋势、规律及异常情况。 ... [详细]
  • 本文旨在介绍一系列提升工作效率的浏览器插件和实用小工具,帮助用户在日常工作中更加便捷高效。内容由原作者授权发布。 ... [详细]
  • 本文详细介绍了在 Windows 7 上安装和配置 PHP 5.4 的 Memcached 分布式缓存系统的方法,旨在减少数据库的频繁访问,提高应用程序的响应速度。 ... [详细]
  • 本文介绍如何通过Java代码调用阿里云短信服务API来实现短信验证码的发送功能,包括必要的依赖添加和关键代码示例。 ... [详细]
  • 本文探讨了使用Python实现监控信息收集的方法,涵盖从基础的日志记录到复杂的系统运维解决方案,旨在帮助开发者和运维人员提升工作效率。 ... [详细]
  • Java虚拟机及其发展历程
    Java虚拟机(JVM)是每个Java开发者日常工作中不可或缺的一部分,但其背后的运作机制却往往显得神秘莫测。本文将探讨Java及其虚拟机的发展历程,帮助读者深入了解这一关键技术。 ... [详细]
  • 本文基于Java官方文档进行了适当修改,旨在介绍如何实现一个能够同时处理多个客户端请求的服务端程序。在前文中,我们探讨了单客户端访问的服务端实现,而本篇将深入讲解多客户端环境下的服务端设计与实现。 ... [详细]
  • 本文详细探讨了在Java中如何将图像对象转换为文件和字节数组(Byte[])的技术。虽然网络上存在大量相关资料,但实际操作时仍需注意细节。本文通过使用JMSL 4.0库中的图表对象作为示例,提供了一种实用的方法。 ... [详细]
  • 本文详细介绍了在 CentOS 系统中如何创建和管理 SWAP 分区,包括临时创建交换文件、永久性增加交换空间的方法,以及如何手动释放内存缓存。 ... [详细]
  • 二维码的实现与应用
    本文介绍了二维码的基本概念、分类及其优缺点,并详细描述了如何使用Java编程语言结合第三方库(如ZXing和qrcode.jar)来实现二维码的生成与解析。 ... [详细]
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社区 版权所有