各位读者朋友们大家好,今天我们一起来聊聊关于计算机底层磁盘的读取原理。
磁盘的基本布局
磁盘的基本结构主要包含有以下几点,磁盘盘片,读写磁盘的指针,传动轴,主轴等。
每次用户程序从用户态发送一条指令给到操作系统内核,进行io读取数据的时候,cpu的ring3级别内核便会通过io总线发送一个指令给到读写磁头,然后由读写磁头定位到盘片的指定位置进行数据读写。最后再通过io总线将数据返回给到用户态的程序中。
下边是一张磁盘盘面的大致结构图:
在磁盘的盘面上通常会布有许多层的磁道,可以理解为图中的同心圆,然后每层磁道上分有不同范围的扇区,数据存储落在这些扇区的范围当中。磁盘指针头通常会落在盘道的一个位置点,然后朝一定方向旋转进行数据的搜索。
内围的磁道数据其实和外围的磁道数据是一样多的,这也就意味着外围的数据密度要比内围的小很多。现如今采用了区位记录的技术,保证了内围的磁道要比外围的磁道少,保证了密度的均匀性
影响磁盘指针定位数据的性能因素
寻道时间:
磁盘指针定位到指定的盘道耗时
旋转延迟:
指针定位到盘道之后进行旋转读取数据
数据传输:
从内核态将数据传输给到用户态的耗时
通常技术人员会通过这么一条公式来推算一块磁盘的读写性能:
iops 指标:磁盘的每秒数据输入输出量,计算思路:1000ms / (数据寻道时间 + 旋转延迟时间 + 数据传输时间) 。
如今的硬件技术开始支持一些分散聚合DMA技术,能够支持将一些原先不连续的磁道数据进行组合读取,效率性更高。由于这种设备支持的磁道信息并不是一定连续的,所以有一个新的概念叫做段。
图片
磁盘毕竟是一个物理硬件层面的东西,那么早期的发明家则通过了一些抽象的概念在程序中对磁盘做访问。下边我们来介绍下经典的文件系统设计。
文件存储
在linux操作系统中,常说的一句话就是“一切皆文件“。
文件系统更像是一周抽象化的数据结构设计,其主要分为了两大模块,inode区和数据区。下边的文章我主要以ext2这种文件系统作为案例进行分析。
在ext2文件系统中磁盘数据通常是以block为单位进行存储的,多个连续的block的组合被称之为BlockGroup。
BlockGroup内部存储的数据较多,主要包含有两个模块:Inode区,Data Block区。Data Block区比较好理解,主要就是存储真实的文件信息,下边主要介绍下Inode区。
Inode区
每一份文件在进行存储的时除了需要记录文件内部的数据外,还会记录一些额外的元数据信息,例如说创建人,创建时间,修改时间,读写权限等。这些基础信息就统一存放在inode里面。
inode区内部主要包含以下内容:
- 文件的字节数
- 文件拥有者的User ID
- 文件的Group ID
- 文件的读、写、执行权限
- 文件的时间戳 ,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。
- 链接数,即有多少文件名指向这个inode 文件数据block的位置
OS在实际打开一份文件的时候,其实并不是单纯依靠文件的名称和路径去查询对应数据的存储地址。每份文件在OS内部都会有一个指定编号对应,这个编号我们一般称之为inode 序号。
例如在OS接收用户点击鼠标打开一份word文档的时候,实际上会先根据用户点击文件的所在路径+文件名去查找对应的inode编号,根据这个编号找到inode块,再在inode块找到对应的数据指针,根据数据指针的地址获取到数据的真实信息。
查看inode的相关信息
[root@izwz9ic9ggky8kub9x1ptuz project]# stat ./log.file 文件:"./log.file" 大小:72545648 块:141704 IO 块:4096 普通文件 设备:fd01h/64769d Inode:1185980 硬链接:1 权限:(0644/-rw-r--r--) Uid:( 0/ root) Gid:( 0/ root) 最近访问:2021-03-12 15:48:32.301232824 +0800 最近更改:2021-03-26 07:52:47.916795277 +0800 最近改动:2021-03-26 07:52:47.916795277 +0800 创建时间: 复制代码
有时候如果文件名称有乱码无法删除的话,可以通过删除inode号来实现删除效果。
数据存储在多个block区域中后,通常是在inode中会有1个数据指针负责多个block的映射,如果数据非常大,就需要有多个映射block的指针,但是inode的空间有限,一般是128kb,所以这里会有一种多级映射的设计,如同下图:
操作系统在读取数据的时候并不会一个一个扇区去读取数据,这样读取太慢了,通常是一个一个block(通常一个block包含有多个扇区)去读取的。
查看操作系统内部的inode数目
[root@izwz9ic9ggky8kub9x1ptuz project]# df -i 文件系统 Inode 已用(I) 可用(I) 已用(I)% 挂载点 /dev/vda1 2621440 300043 2321397 12% / devtmpfs 233122 322 232800 1% /dev tmpfs 235465 1 235464 1% /dev/shm tmpfs 235465 391 235074 1% /run tmpfs 235465 16 235449 1% /sys/fs/cgroup tmpfs 235465 1 235464 1% /run/user/0 复制代码
硬连接和软连接
硬连接:每个文件本质都是用inode号码去识别的,所以如果我们用不同的命名指向同一个inode号码也可以达到同样效果。
ln 源文件 目标文件 复制代码
inode内部有个连接数,就是专门记录有多少个链接指向了自己。使用硬连接的方式需要对目标文件的inode内部连接数做修改+1操作。
[root@izwz9ic9ggky8kub9x1ptuz project]# ln ./log.file ./log2.file [root@izwz9ic9ggky8kub9x1ptuz project]# ls arthas echarts.min.js go idea-blog.jar.original log2.file origin-jar springboot-docker stop.sh test arthas-output files idea-blog.jar link log.file project start.sh tank 复制代码
软连接可以理解为在源文件A内部注入目标文件B的所在地址。这样就意味着查看A文件的时候就能看到B文件的信息,但是如果B文件被删除,那么A文件就会丢失无法打开。
如何使用命令创建软连接:
ln -s 源文件 目标文件 复制代码
软连接和硬连接的本质区别:目标文件的链接数是否会增加。
磁盘的IO读写
顺序读写和随机读写对于机械硬盘来说为什么性能差异巨大?
顺序读写=读取一个大文件
随机读写=读取多个小文件
顺序读写比随机读写快的原因
①顺序读写,主要时间花费在了传输时间,而这个时间两种读写可以认为是一样的。随机读写,需要多次寻道和旋转延迟。而这个时间可能是传输时间的许多倍。
②顺序读写,磁盘会预读,预读即在读取的起始地址连续读取多个页面
(现在不需要的页面也读取了,这样以后用时就不用再读取,当一个页面用到时,大多数情况下,它周围的页面也会被用到)。而随机读写,因为数据没有在一起,将预读浪费掉了。
③另一个原因是文件系统的overhead。
读写一个文件之前,得一层层目录找到这个文件,以及做一堆属性、权限之类的检查。写新文件时还要加上寻找磁盘可用空间的耗时。对于小文件,这些时间消耗的占比就非常大了。
常见的一些优化写性能的手段:
追加写
每次将数据添加到文件。由于完全是顺序的,所以可以具有非常好的写操作性能。但是这种方式也存在一些缺点:从文件中读一些数据时将会需要更多的时间:需要倒序扫描,直到找到所需要的内容。
文件合并和元数据优化
ext2文件系统在海量的小文件应用下性能表现情况其实并不佳,具体原因如下:
由于小文件数据内容较少,因此元数据的访问性能对小文件访问性能影响巨大。Ext2文件系统中Inode和Data Block分别保存在不同的物理位置上,一次读操作需要至少经过两次的独立访问。在海量小文件应用下,Inode的频繁访问,使得原本的并发访问转变为了海量的随机访问,大大降低了性能。另外,大量的小文件会快速耗尽Inode资源,导致磁盘尽管有大量Data Block剩余也无法存储文件,会浪费磁盘空间。
Ext2在Inode中使用多级指针来索引数据块。对于大文件,数据块的分配会尽量连续,这样会具有比较好的空间局部性。但是对于小文件,数据块可能零散分布在磁盘上的不同位置,并且会造成大量的磁盘碎片,不仅造成访问性能下降,还大量浪费了磁盘空间。数据块一般为1KB、2KB或4KB,对于小于4KB的小文件,Inode与数据的分开存储破坏了空间局部性,同时也造成了大量的随机I/O。
优化的策略
针对数据布局低效,采用小文件合并策略,将小文件合并为大文件。
针对元数据管理低效,优化元数据的存储和管理。针对这两种优化方式,业内也出现了许多优秀的开源软件。