本文的主要内容是介绍文件系统基础概念。
1 基于inode的文件系统
1.1 inode与文件
inode,即index node,用于记录一个文件的数据所在的存储块号、文件大小以及其他更多的文件属性(包括权限、修改时间、访问时间等),这些属性称为文件的元数据。
inode文件系统的存储布局可以分成几个组成部分:
- 超级块:指示各个区域的起始块号和大小等信息,文件系统开始的第一个块就是超级块
- 存储块分配信息:记录哪些磁盘块正在被使用,哪些块处于空闲状态
- inode分配信息表:记录哪些inode正在被使用,哪些inode是空闲的
- inode表:以数组的方式保存的inode的数组,inode和文件是一一对应的
- 文件数据区:用来存储实际文件的内容
1.2 多级inode
每个文件的大小是不一样的,可以从1KB到几GB,如果一个文件所有的存储块号顺序地保存在inode中,就得为所有的inode分配相同的大小,造成空间极大的浪费。
为了解决这个问题,借鉴虚拟内存页表管理的思想,可以采用多级inode的方式进行管理。
假设一个数据块的大小是4KB。一个索引块也是4KB,其中保存512个直接指针,那么一个间接指针能够管理512个4KB的数据块,共2MB数据。
相应的,一个二级索引就可以间接管理1GB的数据。
inode的结构就决定了文件系统能够支持的最大文件的大小。
1.3 文件名与目录
一个文件对应一个inode,文件需要有文件名,那么文件名放在哪里保存呢?
在inode文件系统中,文件名并不作为inode的一部分,文件名由目录文件进行管理。
目录也是一个文件,只是它是一种特殊的由文件系统定义的文件。目录文件里面的数据,就是一条条从文件名到inode号的映射,每条映射成为一个目录项(Directory Entry,dentry)。
以文件路径确定一个文件的过程,就是从根目录开始,一级一级找到每一个目录的dentry,最后找到文件的inode的过程。
1.4 硬链接
用dentry管理inode和文件名的方式,带来一个新的功能:链接。
如果已经存在一个文件和目录项,又有另外一个目录项的文件名和该文件的inode建立了映射,那么我们称这种新的文件名映射为链接。
为了管理这种链接,我们需要给inode增加一个引用计数器,也称链接数。
限制:只能用于同一个文件系统。
1.5 软链接
如果是不同文件系统,我们要怎么链接呢?答案是使用符号链接,也叫软链接。
软链接可以理解成:它就是一个独立的文件,只是这个文件里面存的是目标文件的路径。
通常符号链接文件里面的内容就是路径字符串,因此我们可以不为它分配数据区,而是直接保存在inode中。
2 基于表的文件系统
FAT 文件系统,是基于文件分配表(File Allocation Table)的文件系统。
2.1 存储布局
FAT文件系统的基本存储布局如下图所示。
- 引导记录:作用与超级块类似,其中记录了文件系统的元数据以及后续各个区域的位置。若此分区是可启动分区,还包含了引导整个操作系统启动所需要的代码。
- FAT表:通常由两份,互为备份。
FAT文件系统以簇(Cluster)为逻辑存储单元,每个簇对应物理存储上的一个或多个连续的存储扇区(Sector)。
2.2 目录项格式
与基于inode文件系统不同,FAT中每个目录项的大小固定是32字节。
8.3格式的文件名(11字节)
属性(1字节)
创建时间(3字节)
创建日期(2字节)
最后访问日期(2字节)
最后修改时间(2字节)
最后修改日期(2字节)
数据的起始簇号(2字节)
文件大小(4字节)
保留字节(3字节)
属性。用来表示该目录项的种类和状态。如果是长文件名,则属性字段固定是0x0F。
短文件名。采用8.3格式,前8字节是文件名,3字节为扩展名。固定用大写存储。
长文件名。在短文件名签名添加一个长文件名。
- 文件系统会自动为”OSBook File System.tex”这个文件生成短文件名。
- 长文件名构建目录项保存在短文件名目录项之前。
- 使用2字节的Unicode编码。
2.3 FAT和文件格式
FAT实际上是一个由簇号组成的数组。
2.4 FAT文件系统小结
FAT是FAT文件系统的核心。本质上,一个文件的多个数据簇以链表的形式进行索引。
相比与基于inode的文件系统,这种方式更加简单直接。
但是使用链表的结构在查找时并不高效。如果要访问一个文件的中间部分,FAT文件系统不得不逐个簇进行查找,使得大文件的访存变得尤为缓慢。
3 FatFS代码导读
本节通过阅读FatFS的核心API,以加深对FAT文件系统的理解。
f_mkdir
1 | FRESULT f_mkdir ( |
创建目录总共可以分为四步。
mount_volume
确认该路径是已经mount过了。
follow_path
假设我们新建的目录是 "/a/b/c"
,那么我们需要从根目录开始,逐级找到/a
和"/a/b"
,然后返回"/a/b"
这个父级目录项。这就是follow_path
要做的事情。
1 | static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ |
follow_path
这个函数稍微有点复杂,不过理解了这个函数,我们就能知道FAT是如何进行目录管理的了。
首先一开始,dp->obj.sclust 默认为0,也就是被理解为 root 目录(起始root目录的起始簇并不是0,但可以理解成它指示从根目录开始查找)。
我们的path是/a/b/c
,首先我们要从根目录里面找到/a
这个目录项。具体是怎么做的呢:
(a) create_name(dp, &path)
,把 "a"
保存在 dp->fn
中,然后path 变成/b/c
(b) dir_find(dp)
,从根目录里面查找 a
这个目录项
(c) 更新目录项的起始簇(有也可以理解成进入子目录)
这里我们对 dir_find()
这个函数做进一步的分析。
1 | static FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */ |
这里面涉及到dir_sdi()
和move_window()
以及dir_next()
这几个函数。我们不妨继续展开说说。
dir_sdi
这个函数是根据offset
更新dp->clust
和dp->sect
。这个函数里面体现了FAT表、数据簇和扇区的关系,代码分析贴在注释中:
1 | static FRESULT dir_sdi ( /* FR_OK(0):succeeded, !=0:error */ |
move_window()
这个函数体现了,FAT是怎么缓存扇区内容的——它在fs->win
中缓存了一个扇区的内容。move_window()
就是把一个扇区的数据更新到fs->win
中。如果之前fs->win
的数据是dirty了,那么先提前sync到物理存储中。(这几乎就是FAT文件系统最主要的缓存机制了 = =!)
1 | static FRESULT move_window ( /* Returns FR_OK or FR_DISK_ERR */ |
了解了dir_sdi()
的机制,也就很容易理解dir_next()
所作的事情了。它要做的就是要到下一个目录项。这个函数的要点:
- 当前目录的指针是
dp->dptr
,每一个目录项的长度是SZDIRE,也就是32个字节 - 如果到达sector边界,那么就更新sector
- 如果到达clust边界,那么就通过
get_fat()
获取下一个簇
1 | static FRESULT dir_next ( /* FR_OK(0):succeeded, FR_NO_FILE:End of table, FR_DENIED:Could not stretch */ |
理解了这三个子函数,我们也就能知道 dir_find()
的原理了。它不过是要逐个找到目录项,然后比较目录项的名字,和当前要找的目录项是否匹配。
目录项的存储结构示意图如下,请仔细理解这个图,因为它是理解FAT文件系统目录和文件管理机制的关键。
再回到 follow_path(&dj, path)
,我们要新建的是/a/b/c
这个目录,假设/a/b
已经存在,那么最终,/a/b/c
这个目录肯定是找不到的,因为还没有创建。而/a/b
可以找到,它保存在 dj
中。
create_chain
创建一个新的簇,用来存放即将要添加的新的目录。
这个函数主要是FAT表的管理。这里列出一些该函数的要点:
fs->last_clst
中保存了上次分配的簇,也即当前建议开始查找的簇fs->free_clst
中保存了剩余的空闲簇get_fat()
就是根据簇号找到FAT表对应的FAT entry,它是4字节长度的数据- 为0表示空闲的簇
- 分配完成之后,这个簇就是簇链最后的簇,因此给它的FAT entry赋值为0xFFFFFFFF
1 | static DWORD create_chain ( /* 0:No free cluster, 1:Internal error, 0xFFFFFFFF:Disk error, >=2:New cluster# */ |
目录项初始化
1 | if (res == FR_OK) { |
- 清空目录
- 创建
.
和..
这两个目录项 dir_register
注册到父级目录中- 更新起始簇号和其他属性信息
1 | static FRESULT dir_register ( /* FR_OK:succeeded, FR_DENIED:no free entry or too many SFN collision, FR_DISK_ERR:disk error */ |
这样,就完成了一个目录项的创建。
通过分析这样的一个函数,我们可以一窥FAT文件系统对目录、文件的管理。
目录是一种特殊类型的文件,区别在于,目录文件的数据区存储的是一个个的目录项,而普通的文件的数据区中存储的是文件数据的内容。
参考资料
- https://en.wikipedia.org/wiki/File_Allocation_Table#External_links
- http://elm-chan.org/fsw/ff/00index_e.html
- http://elm-chan.org/docs/fat_e.html
- http://www.c-jump.com/CIS24/Slides/FAT/lecture.html
4 VFS
计算机系统中可能同时存在多个文件系统。在操作系统中,虚拟文件系统(Virtual File System)对多种文件系统进行管理和协调,允许它们在同一个操作系统上共同工作。