Virtual File System
1. Everything is a file
Unix及其衍生的操作系统贯彻一条理念: 万物皆文件. 该理念用于处理资源的输入和输出, 如文档, 硬盘, modem, 键盘, 打印机, 进程间通信; 该概念的优点在于, 用户可使用相同的API操作不同类型的资源. 但也存在一些例外, 如shared memory(共享内存), semaphores(信息量), symbolic link(符号链接), process(进程), 上述资源并不作为文件形式呈现.
2. File descriptor
打开一个文件时, UNIX会为其创建对应的file descriptor(简称fd). Fd作为文件的唯一标识符, 进程对文件的一切操作都需要fd作为接口, 因此也称为Everything is a file descriptor.
UNIX会为每个进程分配一个file descriptor table, 该table包含进程打开的所有文件, 每个entry包含以下信息:
- Controlling flags: 进程调用
open()
时附加的file creation flag和file status flag, 如O_APPEND
,O_ASYNC
,O_CLOEXEC
,O_CREAT
- 指向file table一条entry的指针
其中, file table是系统层面的一个数据结构, 每一条entry代表一个打开的文件:
- File offset
- File status flags: 控制进程操作文件的行为, 如文件为只读或只写, 向文件追加或覆盖数据
- File access mode: 文件权限
- File position: 进程读写文件的位置
- 指向inode的指针
inode table是系统层面的一个数据结构, 包含以下信息:
- File type(普通文件, FIFO, socket等)
- File permission
- File properties(文件大小, 时间戳等)
需要注意的是, file descriptor table中的多个entry可指向file table的同一entry(调用dup()
), file table中的多个entry可指向inode table中的同一entry(同一文件被打开多次).
Linux中, 可通过/proc/PID/fd/
查看一个进程拥有的所有file descriptor, 其中/proc/PID/fd/0
为stdin, /proc/PID/fd/1
为stdout, /proc/PID/fd/2
为stderr.
Kernel可使用file descriptor以及上述table跟踪进程打开的文件, 并记录文件的相关信息, 如inode number, file mode, file position等.
3. File System
File system作为操作系统中重要的一环, 用于管理数据的分段及其命名. 若没有file system, 存储设备中的数据只是一团二进制, 无法分辨一段数据的开始和结束位置. 将数据分为多段, 并为每段数据分配名字, 便诞生出file的概念. File system会以不同标准分为不同实现:
- 不同操作系统:
FAT
(DOS和Windows),ext
(Linux),UFS
(Solaris和BSD) - 内置checksum和冗余备份: Btrfs
- 专门为固态设备优化: APFS, exFAT
- 分布式file system: Amazon S3
- peer-to-peer file system: Cleversafe
- 加密文件: eCryptfs
除了保存在存储设备上的文件, 还有一些文件只存在于内存中, 这类文件的file system称为pseudo file system(伪文件系统), 如procfs
, 该file system以文件的形式展示系统内的进程信息, 用户可通过读取对应文件来获取进程信息.
UNIX支持在一个系统内使用多个file system, 也因此引入一个问题: 如何让用户随时访问不同file system中的文件. UNIX的方案为virtual file system(简称为VFS), VFS将不同file system中的文件放入一个统一的hierarchy, 也就是说, 系统中只有一个root directory, 且所有file system的文件都在其中. 虽然UNIX会为每个存储设备一个名称, 但访问设备中的文件并不需要设备名, 只需知道文件在directory tree中的位置.
4. Virtual File System
VFS作为file system之上的一层抽象层, 为用户提供了一套统一的访问不同类型file system的方式. VFS制定了一套kernel与file system的接口, 因此, 只要新的file system符合接口标准, 就可加入kernel.
4.1 Directory Entry Cache
对于open()
, stat()
, chmod()
等系统调用, directory entry cache(简称为dentry cache
)可帮助VFS通过pathname
参数快速找到对应文件, 因此, dentry cache是一种连接pathname和inode的机制. 若VFS访问文件时, 该文件未被缓存到dentry cache中, 则VFS会为其分配dentry object
(简称为dentry
). 假设查询路径为/home/user/name
, VFS会生成四个dentry
: /
, home
, name
和user
.
struct dentry { |
Dentry通过d_parent
和d_subdirs
实现了文件系统的层级结构, 整个系统存在一个root dentry, 且root dentry不存在d_parent
; 其他dentry则必须拥有一个d_parent
.
一个dentry存在三种状态:
- used:
d_inode
指向一个inode, 且d_count > 0
, 表示该dentry正在被VFS使用 - unused:
d_inode
指向一个inode, 且d_count = 0
, 表示该dentry未被VFS使用, 但未来可能使用 - negative:
d_inode
为NULL, 因为文件被删除或路径不正确, 保留该dentry只是为了以后快速查找
除了dentry object, dentry cache还包含以下部分:
- LRU链表: 包含unused和negative状态dentry的双向链表, 该链表使用insertion sort(插入排序算法)插入dentry, 因此dentry越靠近头部, 说明该dentry最近被使用. 当kernel需要回收内存时, 根据局部性原理, 会从链表尾部移除dentry.
struct list_head dentry_hashtable[D_HASHSIZE];
: 数组坐标表示dentry的hash值, 每个数组元素是一个dentry组成的链表, 且链表内dentry的hash值相同. 该hashtable可让VFS根据pathname快速找到对应的dentry.
出于查询性能考虑, dentry只保存在内存中, 不会存入磁盘.
4.2 Inode
inode提供了kernel所需的所有关于file的信息. 对于block device filesystem, inode保存在磁盘中, 需要时加载到内存, 修改时写回磁盘; 对于pseudo filesystem, inode保存在内存中. 多个dentry可指向同一inode.
struct inode { |
4.2 Inode Operations
struct inode_operations { |
4.3 Superblock
Superblock表示一个挂载的file system, 其中包含file system所需的metadata. 若file system的superblock损坏, 则VFS无法挂载该file system, 因此file system会有多个superblock备份.
struct super_block { |
superblock中最重要的成员为s_op
, 其中包含file syste对inode的所有操作.
/* create and initialize a new inode object under |