从文件read/write一个字节的过程和所发生的磁盘IO
从以下介绍可看出,“太阳底下无新鲜事”,很多以为很复杂的业务层系统的实现原理实际上在OS内核中都有原型。因此,底层原理领会了,就能游刃有余应万变需求!!!
IO时(不管是磁盘IO还是网络IO)的过程整体上看有两个操作(write过程与read过程相反):
1 将数据从外设读入内核态内存,如从网卡读入到内存Ring Buffer。此过程为DMA read,不需要CPU参与,完成后通过中断通知CPU。我们通常说IO操作耗时,就是这步耗时。
2 从内核态内存复制到用户态内存(通常就是应用程序进程的内存)。此过程为CPU read,需要CPU参与。之所以要有这一步而不是将外设数据直接复制到用户态内存,是因为OS出于安全考虑不允许用户态应用程序直接访问IO设备。
但深入追究的话该过程相当复杂,概要图如下:
详见下文。
读过程概要:
从上面两图可总结 用户层(系统调用)、内核层(VFS、PageCache、FS)、块层(通用块管理、IO调度器、磁盘驱动)、硬件层(磁盘缓存、磁盘) 四层,分别有1、3、3、2个过程。
note:
OS支持多种文件系统,一个磁盘上可以有多个分区,每个分区可以格式化成一种文件系统,不同分区格式化成的文件系统可以不一致;OS提供了虚拟文件系统(VFS)以屏蔽对不同文件系统的访问,页缓存(PageCache)位于VFS和实际的FS之间。
磁盘自身数据读取以扇区(通常为512B)为单位、文件系统以块block(通常为4KB)为单位管理数据、PageCache以页Page为单位(通常为4KB)管理数据。可见:
一次磁盘IO的数据有多少?虽然用户只需要一个字节,但实际上至少一页(即8扇区)的数据会被从磁盘读取到内存PageCache(即使只需要一个字节);
是否一定会有磁盘IO?PageCache在内存中暂存这些Page,若下一次访问时PageCache中存在需要的数据则直接从内存取而不需要读磁盘,因此,由于PageCache的存在,读取文件一个字节并不一定会导致磁盘IO。
从上述过程看,若写数据时写入到了PageCache,则此时用户的写磁盘操作从效果上看是异步的。
详情参阅文章 read 文件一个字节实际会发生多大的磁盘IO
写过程概要:
note:
写的过程与读类似,先后经历 用户态写、系统调用写、VFS系统写、写PageCache、实际FS写、磁盘驱动写 等过程。
有多种写的方法,如同步写、异步写等,区别在于写方法是否等数据真正写入磁盘才返回。对于异步写,数据写入PageCache(此时该PageCache称为脏页)写方法就立即返回。
脏页数据由内核Worker线程周期性地扫描,将符合条件(脏页数据的 比例达到阈值、总量达到阈值、存在时间达到阈值 至少一个,阈值可配置)的脏页数据持久化到磁盘,这才是真正的“写”。持久化的时机:
数据会在如下三个时机下被真正发起写磁盘IO请求:
第一种情况,如果write系统调用时,如果发现PageCache中脏页占比太多,超过了dirty_ratio或dirty_bytes,write就必须等待了。
第二种情况,write写到PageCache就已经返回了。worker内核线程异步运行的时候,再次判断脏页占比,如果超过了dirty_background_ratio或dirty_background_bytes,也发起写回请求。
第三种情况,这时同样write调用已经返回了。worker内核线程异步运行的时候,虽然系统内脏页一直没有超过dirty_background_ratio或dirty_background_bytes,但是脏页在内存中呆的时间超过dirty_expire_centisecs了,也会发起会写
由于PageCache位于内存,而异步写时会优先考虑往PageCache写,故异步写的效率很高;但也由于PageCache在内存发生断电等异常情况时数据可能还没来得及持久化,故数据可能丢失。
详情参阅文章 write 文件一个字节实际会发生多大的磁盘IO
拓展:
从上述读写过程可知,VFS是内核中的一层重要抽象,用于屏蔽不同FS的差异,从而让OS支持各种FS。
在Linux Kernel中,有一种特殊的FS,叫做 FUSE(File system in UserSpace)
A filesystem in which data and metadata are provided by an ordinary userspace process. The filesystem can be accessed normally through the kernel interface.
FUSE is a userspace filesystem framework. It consists of a kernel module (fuse.ko), a userspace library (libfuse.*) and a mount utility (fusermount).
对FUSE的介绍及基于FUSE的demo,可参阅公众号文章 奇伢云存储-FUSE。
简而言之:FUSE是一个特殊的文件系统,其没有文件系统的具体实现,而是一个框架,借助该框架可以在用户态提供文件系统的具体实现(fuse为用户态文件系统提供了与VFS交互的通道)。
从数据流向看,就是把用户态到内核态的读写请求转发回【用户态上用户自己实现的文件系统(假设为A)】,即 用户态 -> 内核态 -> VFS -> FUSE -> A,返回时原路返回。
从框架实现上看,其定义了一套数据协议和协议的封装/解析库,该协议就是 用户态和内核态间、内核态和A 间的数据交互协议。具体而言(其实很简单),框架包含三个模块:
内核模块 fuse.ko :用来接收 vfs 传递下来的 IO 请求,并且把这个 IO 封装之后通过管道发送到用户态;
用户态 lib 库 libfuse :解析内核态转发出来的协议包,拆解成常规的 IO 请求;
mount 工具 fusermount :用于挂载FUSE文件系统的实现,即A。
基于FUSE实现的FS有:cepthfs、glusterfs、sshfs、nfs(网络文件系统)等。