零——什么是文件系统?
文件这个词,相信大家从小就不陌生。实体的文件,可能会是打印的A4纸;虚拟的文件,可能是*.doc、*.dll以及*.exe等等格式。文件,就是用来记录信息的逻辑单元。
文件系统这个概念,我们大部分人可能也接触过。重装系统的时候,会遇到给磁盘分区,以及格式化的问题;当然,格式化U盘也是常用的操作。装机的时候,如果你细心,你会发现分区软件会显示每个分区的格式。格式化这个概念,许多人都接触过但是不太清楚到底做了什么。
其实,格式化就是把磁盘这样的存储设备,设置成我们需要的文件系统。FAT、NTFS这两个词相信大家都是见过的,他们都是文件系统,也是我们在格式化的时候需要选择的目标文件系统。
总体来看,文件系统就是操作系统处理文件的部分。
一、文件
文件是一种抽象机制,它提供了一种在磁盘上保留信息并且方便以后读取的方法。在Linux操作系统中,文件名并没有规定任何格式,比如一个aha.txt,只是提示用户这可能是一个文本文件,然而并不保证任何内容。对应的,windows是需要后缀名这个信息的。
文件可能有许多种结构,如:
流式文件,无结构的字节序列,操作系统根本不知道也不关心文件的内容是什么,见到的就是字节,含义在用户程序中解释,Linux和windows都是这样的;
树,通过键值查询文件,操作系统维护记录树。
文件分为许多种,在Linux中,文件分为普通文件、目录文件和特殊文件。普通文件包括:二进制文件和ASCII文件。前者是二进制,需要用特定的规则来读,后者是我们常见的字符文件。
目录文件除了自身属性外,记录了该目录下的文件名称。
特殊文件:包括块设备文件和字符设备文件。块设备文件,如硬盘,支持随机访问操作;字符设备文件通常是串行设备,如鼠标键盘等,自然不支持随机访问。
文件的存取,对于主流的Linux和windows操作系统,都不是顺序的,因为这样会造成很大的空穴浪费。一般都是通过索引式文件存取。对于Linux的文件系统,详情可见后面的Ext2/3文件系统介绍。
我们在windows操作系统下,右键就可以显示文件的属性。文件的属性规定了不同的用户可以对它进行的操作,以及文件自身的大小、位置、更新时间等信息。Linux操作系统中可以用ls -l来查看完整的文件属性,包括文件类型,用户、用户组、其他用户的操作权限,以及大小、位置和修改时间等信息。当然,这并不全。
二、目录
现代操作系统,通常采用了层次目录系统,把目录组成一棵树。通常会有一个根目录,然后直到文件,形成一个绝对路径;也可以预先进入一个目录,然后使用相对目录访问。
在Linux操作系统中,目录其实也是一种文件,在类型标识中为d。
目录同样有文件的各种属性,不过在对属性的解释上可能与文件有些许区别。
三、文件系统的实现
1、文件系统的布局
毫无疑问,文件系统存在于磁盘上。一开始我们提到了分区的概念。磁盘被划分了多个分区,一般来说可以有4个分区,其中一个分区为扩展分区,可以进一步分出许多逻辑分区。通常,一个分区中只存在一种文件系统。磁盘的头上是启动扇区,存放着主引导记录(MBR),然后是分区表,记录着磁盘的分区情况。一个分区被标记为活动分区。每个分区的开始会有一个引导块。
下图是Linux操作系统的Ext2文件系统的布局。
可以看到,Linux操作系统把磁盘块分成了组,每个组中都可能有超级块的备份。
超级块存储了文件系统的关键参数,包括inode和block的使用情况等。
接下来的两组bitmap记录了本组块和inode的使用情况。之后是inode表,因为Linux采用了多级块索引,所以一部分块需要用作索引块。最后才是实际的存储区域。
2、文件系统的实现
最简单的实现自然是顺序存储,这样显然会造成管理上的不方便,以及内存的利用率很低。改进使用链表,简单串联起文件。这种方法速度会相当慢,也无法随机存取。
可靠的实现方法:
(1)内存中采用表的链表分配。每个块都需要一个表项,记录文件中下一个块的位置。这个表格称为文件分配表(FAT)。
(2)inode。这是Linux的Ext文件系统采用的方法,采用预先分配好的inode,每个inode分配给一个文件,记录文件的属性信息,并指向实际存储文件的块。
3、目录的实现
windows采取的方法,是有一个文件表,每个表项包含了文件名称与对应的属性;Linux系统的Ext文件系统同样采取inode的方式,目录的inode记录该目录的属性,在数据块中存储该目录下的文件名以及对应的inode。
4、共享文件
在不同的目录上创建连接文件,就可以实现文件的共享。Linux操作系统采取了两种方式:
(1)硬连接。硬连接其实没有占用新的inode节点,而是在需要创建文件的目录数据块中,新增了一个项:文件名-inode。访问时,会顺着访问路径查找原来的文件。硬链接存在一个问题,就是这个inode必须是在同一个分区中,不然无法找到这个inode。
(2)软连接,也叫符号连接。这种方式类似windows的快捷方式,真正创建了一个文件,分配了inode。文件的内容比较简单,就是原文件的路径。
5、日志文件系统
日志文件系统的思想,是保存一个用于记录系统下一步要做什么的日志。这样,系统如果崩溃了,可以通过查看日志来获取崩溃前的任务,并完成它们。Ext3文件系统就是支持日志的系统,它是Ext2的扩展,也很容易升级。
6、虚拟文件系统
我们说到不同分区可能有不同的文件系统,所以系统必须能组织起不同的文件系统,并提供统一的操作接口。这就是虚拟文件系统(VFS),Linux操作系统采用了它。即使不同的分区采用了不同的文件系统,也可以采用POSIX接口对所有的文件系统进行操作。
四、文件系统的管理和性能
1、磁盘空间的管理
首先要确定的是,也要如内存分页一样,把磁盘分为块进行管理。块的大小是一个影响因素。
2、空闲空间的管理
如内存一样,位图的方式是合理的,Ext文件系统使用了位图。另外,也可以通过空闲块链表的方式进行管理。
3、文件系统的备份
文件系统可能由于各种原因损坏,比如意外的灾难或者操作错误。备份到磁带上,可分为物理转储和逻辑转储。前者前部按需输出,后者从某一目录开始,递归地转储自给定基准日期后更改的全部文件和目录。
4、文件系统的一致性
因为文件的直接存储块和记录空闲空间的位图等结构是分离的,因此可能会出现不一致的情况。
5、提高性能的方法
(1)高速缓存。缓存的概念,遍布计算机的各个领域。磁盘的缓存与其他缓存也类似,内存页面置换算法也在这里适用。
(2)块提前读。根据局部性的原理,提前读取部分可能用到的块。
五、Linux文件系统实例分析
1、LInux虚拟文件系统
虚拟文件系统隐对用户隐藏了Linux支持的文件系统之间的区别,以及文件是一个本地设备,还是挂载的设备,或者是网络访问的远程设备等。
VFS定义了一个基本的文件系统抽象以及这些抽象上允许的操作。VFS支持四个主要结构:
(1)Superblock。该结构记录了文件系统布局的重要信息。
(2)Inode。每个inode表示一个确切的文件,目录和设备也是文件,也有自己的inode。
(3)Dentry。表示一个目录项,由文件系统在运行过程中创建。目录项被缓存在dentry_cache中。对于前面提到的硬链接,多个进程通过一个硬链接会访问同一个文件,文件对象都会指向这个cache中的同一个目录项。
(4)File。这是一个打开文件在内存中表示的数据结构,在调用open时被创建,可以支持read/write/close/lseek等系统调用。
2、Ext2文件系统
Ext2文件系统的布局,其实我们前面已经提到了,参见三、1节的文件系统布局。
具体地讲,块0不被Linux使用,而通常用来存放启动计算机的代码。块0后被分为多个块组,每个块组包括:
(1)superblock。包含了文件系统的个数,磁盘块数以及空闲块的起始位置。
(2)组描述符。存放了位图的位置、空闲块数、组中的inode数,以及组中目录数的信息。这个信息很重要,ext2文件系统会试图把目录均匀地分散存储到磁盘上。
(3)inode和block的位图。
(4)inode的存储区域。在ext2中inode大小为128字节,最新的ext4中已经增到到了256字节。
(5)实际的存储块。ext2会试图把普通文件组织到与父目录相同的块组上,而把同一个块上的数据文件组织成初始文件i节点。
下面的图是在我的虚拟机用dumpe2fs查看的文件系统:
这是头部的信息,从超级块中读取。我们可以看到,包含了文件系统的特性、魔数(magic number)、inode的总数、块的总数、保留块数、空闲的inode和块数,以及块的大小、每组的块数量和inode数量、文件系统的修改时间、inode的大小等等信息。
因为文件系统是ext4文件系统了,所以inode256字节,并且包含了128M的日志。
这张图是两个实际的块组,从组描述符以及位图读出了许多信息。块组的起止位置,是否备份了superblock,组描述符的位置和位图的位置,inode表的位置,以及inode和块的使用情况等等。
当打开一个文件时,我们前面提到,会为目录dentry进行缓存。操作系统将文件调入内存,并把i节点存放在i节点表中,i节点表是一个内核数据结构,用于保存当前所有打开的文件和目录的i节点。
我们知道,执行open时会返回一个文件描述符(通常是整数),因此会有一个文件描述符表,用来记录fd以及对应的表项。实际上,每个进程维护自己的文件描述符表,并且在它和i节点表间维护一个打开文件描述符表,并将文件读写位置放入其中。