文件系统

文件系统

文件系统主要负责管理和组织计算机存储设备上的文件和目录,其功能包括以下几个方面:

文件系统结构

Linux 文件系统会为每个文件分配两个数据结构:索引节点(index node)和目录项(directory entry),主要用来记录文件的元信息和目录层次结构

由于索引节点唯一标识一个文件,而目录项记录着文件的名字,所以目录项和索引节点的关系是多对一,比如硬链接的实现就是多个目录项中的索引节点指向同一个文件

目录项是内核缓存在内存的数据结构,而目录是持久化存储在磁盘的文件
内核会把已经读过的目录用目录项缓存在内存以提高效率,同时目录项也可以表示文件

磁盘读写的最小单位是扇区,扇区的大小只有 512B,文件系统把多个扇区组成了一个逻辑块(数据块) 作为每次读写的最小单位以提高读写效率
Linux 中的逻辑块大小为 4KB,也就是一次性读写 8 个扇区

目录项和索引关系图.png (1172×842) (xiaolincoding.com)|750

磁盘进行格式化的时候,会被分成三个存储区域

虚拟文件系统

操作系统在用户层与文件系统层引入了中间层以屏蔽不同文件系统的差异,提供统一接口,这个中间层就称为虚拟文件系统(Virtual File System,VFS)

|675

根据存储位置的不同,可以把文件系统分为三类:

文件使用

文件系统的基本操作单位是数据块
当用户进程从文件读取 1 个字节大小的数据时,文件系统需要获取字节所在的数据块,再返回数据块对应的用户进程所需的数据部分
当用户进程把 1 个字节大小的数据写进文件时,文件系统则找到需要写入数据的数据块的位置,然后修改数据块中对应的部分,最后再把数据块写回磁盘

操作系统通过为每个进程维护一个打开文件表来跟踪进程打开的所有文件
打开文件表的每一项都是一个文件描述符,其中包含打开文件的状态和信息:

文件存储

连续空间存放

文件存放在磁盘连续的物理空间中,文件头(inode)里需要指定起始块的位置和长度

优点:读写效率高
缺点:容易产生磁盘碎片,文件长度不易扩展

非连续空间存放

Unix 实现

早期 Unix 文件系统采用组合方式存放文件,根据文件的大小采用不同的存放方式:

该方法对于小文件可使用直接查找的方式减少索引数据块的开销,但对于大文件采取的多级索引方式需要大量的 IO 查询,效率较低

目录存储

普通文件块里保存文件数据,目录文件块里保存目录里面一项一项的文件信息

目录文件块中最简单的保存格式是列表,将目录下的每项文件信息(如文件名、文件 inode、文件类型等)在列表中顺序存储,每一项代表该目录下的文件的文件名和对应的 inode,通过 inode 可以找到真正的文件

Linux 系统的 ext 文件系统采用哈希表来保存目录的内容,对文件名进行哈希计算并存储,查找时通过哈希值可实现迅速查找

可通过在内存中缓存当前使用的文件目录以降低磁盘操作次数,提高效率

软链接&硬链接

空闲空间管理

Linux 采用位图法管理数据空闲块和 inode 空闲块,利用二进制的一位来表示磁盘中一个盘块的使用情况,磁盘上所有的盘块都有一个二进制位与之对应

文件 IO

常见的文件 IO 分类包括:

缓冲/非缓冲 IO

缓冲 I/O 利用标准库的缓存实现文件的加速访问,而标准库通过系统调用访问文件
非缓冲 I/O,直接通过系统调用访问文件,不经过标准库缓存

缓冲 IO 可以减少系统调用的次数,进而减少 CPU 上下文切换的开销

直接/非直接 IO

直接 IO 直接经过文件系统访问磁盘,不会发生内核缓存和用户程序之间数据复制
非直接 IO 读操作时,数据从内核缓存中拷贝给用户程序,写操作时,数据从用户程序拷贝给内核缓存(PageCache),由内核决定写入数据到磁盘的时机

在使用文件操作类的系统调用函数时指定 O_DIRECT 标志则表示使用直接 IO,默认使用非直接 IO

非直接 IO 写入时机:

Page Cache

程序运行时具有局部性,PageCache 用于缓存最近被访问的数据,当空间不足时淘汰最久未被访问的缓存

PageCache 还包括预读功能,会先预读与被访问数据物理相邻的数据

对于大文件传输 PageCache 不适用,应使用异步 IO+直接 IO 来传输大文件
对于小文件传输,可使用 PageCache 实现 零拷贝技术

阻塞/非阻塞 IO&同步/异步 IO

当用户执行 read
阻塞 IO 等待内核数据准备完成数据从内核态拷贝到用户态两个过程后返回数据
非阻塞 IO 在数据未准备好的情况下立即返回,同时应用系统不断轮询内核直到数据准备完成,再等待内核将数据拷贝到应用程序缓冲区后返回数据
基于非阻塞 IO 的 IO 多路复用可以在一个线程内同时处理多个 socket 的 IO 请求

访问管道或 socket 时设置 O_NONBLOCK 标志使用非阻塞 IO ,默认使用阻塞 IO

阻塞 IO 和非阻塞 IO 都为同步 IO,都需要等待内核将数据由内核空间拷贝至用户空间
异步 IO 中内核自动将数据从内核空间拷贝到应用程序空间,之后再通知应用程序处理数据

举个你去饭堂吃饭的例子,你好比用户程序,饭堂好比操作系统
阻塞 IO 好比,你去饭堂吃饭,但是饭堂的菜还没做好,然后你就一直在那里等啊等,等了好长一段时间终于等到饭堂阿姨把菜端了出来(数据准备的过程),但是你还得继续等阿姨把菜(内核空间)打到你的饭盒里(用户空间),经历完这两个过程,你才可以离开
非阻塞/O 好比,你去了饭堂,问阿姨菜做好了没有,阿姨告诉你没,你就离开了,过几十分钟,你又来饭堂问阿姨,阿姨说做好了,于是阿姨帮你把菜打到你的饭盒里,这个过程你是得等待的
基于非阻塞的/O 多路复用好比,你去饭堂吃饭,发现有一排窗口,饭堂阿姨告诉你这些窗口都还没做好菜,等做好了再通知你,于是等阿等(se1ct 调用中),过了一会阿姨通知你菜做好了,但是不知道哪个窗口的菜做好了,你自己看吧。于是你只能一个一个窗口去确认,后面发现5号窗口菜做好了,于是你让5号窗口的阿姨帮你打菜到饭盒里,这个打菜的过程你是要等待的,虽然时间不长。打完菜后,你自然就可以离开了
异步 IO 好比,你让饭堂阿姨将菜做好并把菜打到饭盒里后,把饭盒送到你面前,整个过程你都不需要任何等待

DMA

过去 IO 过程中由 CPU 将数据从设备拷贝至 PageCache,再由 PageCache 拷贝至用户缓冲区

DMA 技术下在进行 I/O 设备和内存的数据传输的时候,数据拷贝工作全部由 DMA 控制器执行,CPU 仅指定数据和传输位置