在Linux文件系统之一:inode节点的提出一文中通过顺延FCB的管理思想,为加速文件系统的管理效率引出并介绍了inode节点的概念。考虑到当前主机的主要存储设备都是硬盘,故而谈及文件系统filesystem必须要结合硬盘分区partition的硬件概念。
对于主机而言,由于可能同时存在多种文件管理场景,诸如作为博客服务器的主机,可能既需要管理博文这一类长文章,也需要管理诸如评论这类简短信息,这种情况下,显然需要为不同的使用目的设置不同的文件管理系统。体现在硬件上,便是为不同的文件管理系统分配不同的硬件设备,显然为不同的文件管理系统配备专属的独立存储设备是一件奢侈的事。好在一块硬盘其实可以根据不同的使用场景划分出多个分区,每个分区可以采用不同设置的filesystem进行格式化,这样可以在充分满足自身需求的前提下,也可以减少对硬件的消耗。
硬盘划分出partition只需要记录每个分割区partition的起始和结束磁柱即可,这样,操作系统才知道如果对应分区A,需要到磁柱a和磁柱b之间去存取数据。这些分区的起始和结束磁柱号数据全部记录在MBR(Master Boot Recorder主要开机扇区),MBR就是一块硬盘的最外侧的第0号磁道上,除了记录硬盘的分区信息,还是开机的时候开机管理程序写入的地方。(也是因为外侧磁道的信息密度高,磁道转动相同角度比内侧磁道获取的信息更多,读写效率更快)
MBR最大的限制在于他要记录开机管理程序的信息,剩余的大小不够存储较多的分区分割信息,MBR最大只支持4个partition的记忆,这就是所谓的Primary(P)和Extended(E)的Partition最多只能有4个的原因。所以如果预计分割超过4个partition的话,需要使用3P+1E,并且将3P之后所有的剩余空间分配给Extended分区(Extended分区最多只能有1个),否则3P+1E之后剩下的空间将成为废物而被浪费掉。所以如果要分割硬盘并且预计将使用掉MBR提供的4个partition(3P+E或4P)那么磁盘的全部容量需要被使用掉,否则剩下的容量也不能再被使用。
在告知操作系统分区的起始和终止磁柱后,那么剩下的便是将partition格式化为当前OS可以识别的文件系统。每一个partition都可以有一个FileSystem。
扇区sector(512 B)是硬盘的物理最小存储单元,但是对于IO操作而言,sector显然太小了,效率太低,故而推出block磁盘块的概念。block是在进行文件系统格式化时,所指定的最小逻辑存储单元。block是sector的2的次方倍(1,2,3即分别对应1K, 2K, 4K)。以4K block为例子,那么对于10M文件,可以由扇区sector为基础读取单元的20480次降低到2560次,极大地提高读取效率。
但block也不是越大越好,因为一个block只能给一个文件使用,所以block越大对于小规模文件会产生内部碎片,即block的大小设计也是一种类“E-E平衡”的问题,要考虑:
1.文件IO读写的效率; 2.文件存储可能生成的内部碎皮导致空间浪费。
(社区BBS主机,因为主要存储较短的评论,故而应该block设置较小;博客主机因为主要存储较长的文章,故而应该block设置较大。)
在block中有一个特例, SuperBlock:前面说到对于每个分区partition可以有一个文件系统FileSystem,而对于一个分区的头部block则承担着记录当前分区partition的大小、可用空block和已用的block这类总览信息。所以如果要使用某分区Partition,则必须要经过SuperBlock。
Linux支持多人多任务,故而出于数据隐密性和分组共享性,Linux中每个文档除了数据内容,还有诸如所属群组、Owner、能否执行、文件创建时间、文件的特殊属性等其他管理标签。在 Linux文件系统之一中提出为了加速管理效率,在EXT2文件系统中,通过inode节点(一般都是128 bytes)来存储文档的管理信息(权限以及文档存储的Block映射表),通过Block来存放文档的实际数据内容。
inode节点包含的内容:
1.该文件的owner、所属群组
2.该文件的读写模式(只读、可读写、可执行)
3.该文件的类型(.doc, .cpp, .lib等)
4.该文件的时间信息(创建时间ctime、最近一次读取时间atime、最近修改的时间mtime)
5.该文件的容量
6.定义该文件的标志位flag,如SetUID…
7.该文件的block映射表即导向寻址信息
所以在我们将一个分区Partition格式化为EXT2,同时还需要指明该分区对应到inode和block大小,并划分inode area和Block area的空间大小。每个EXT2文件系统被建立时,它都将拥有superblock/group/description/block bitmap/inode bitmap/inode table/data blocks等不同功能区域。根据分区大小,可能一个分区有数个block group,每个block group都有自己的一套上述功能区域。
superBlock:记录当前分区的总览信息,诸如Block和inode的总量;未使用的inode和block数量,一个block大小,一个inode的大小,filesystem的挂载时间、最近一次写入数据的时间、最近一次检验磁盘fsck的时间等文件系统的相关时间,一个valid bit的数值,若此文件系统已经被挂载,则valid bit为0,若未被挂载,则valid bit为1;
Group Description: 记录该block由何处开始,到何处终止(起始和终止磁道)
Block bitmap:和block一一对应,用来快速查看哪些Block还是空闲状态;
Inode Bitmap: 和inode一一对应,用来快速查看各inode的使用状态
Inode table:为inode的数据聚集区
Data Block:为block的数据聚集区
根据这种总览信息metadata,系统可以快速简单的获取当前分区有哪些inode还没有被使用,哪些block还是空闲的,这样便可以快速和新建文件的size需求进行匹配分析。新建一个文件存在着两步操作:
1.首先根据inode bitmap和block bitmap找到分配出来的inode 和block chains,然后将文件的属性和内容相应填入。
2.填写完成后,将刚刚使用的Inode和block chains的信息告知superblock\inode bitmap\block bitmap等metadata。
既然是非原子性操作,那么必然要设置相应的容错处理机制,以应对可能出现的数据的不一致状态inconsistent情况
如果在文件写入硬盘的过程中,出现操作中断(比如系统停电、系统核心发生错误等),如果操作已经进行完整了第一步,而第二步尚未进行,则这时会出现metadata总览信息数据和实际数据区(inode area & block area)使用情况不匹配。
在早期的EXT2文件系统中,应对这种问题,是需要进行全分区遍历检查的,故而是极为耗时低效的,也因此出现了日志式文件系统。Log file通过类似于登记账本的形式来记录故障前的那一次操作的进行顺序,这样一旦出现问题,查看Log file可以有效缩小要搜索的目标区域。
A.在当前分区中划分一个区块专门用来记录写入或修改文件时已经完成的操作;
B.当前分区中药写入一个文件时,会先在log file区块中记录:有一个文件XXX准备写入到当前分区
C.开始在inode节点和block中写入文件的权限和数据;
D.更新当前分区的metadata中的总览信息;
E.数据写入和metadata更新完成后,在log file中记录该次写入操作成功完成。
这样一旦出现问题,查看log file可以将问题定位到具体的文件XXX上,而不需要对整个分区进行搜索。
带有日志式档案功能的便是EXT2的升级版EXT3。
对于目录
当我们在Linux下EXT2文件分区中新建一个目录,则会分配一个inode和至少一块Block给该目录,其中inode记录该目录的相关属性,并将在inode中设置指针指向初始分配的block,这个初始分配的block可以用来存放该目录下将要添加的新文件或子目录(随着目录下的文件或子目录增加,则会动态添加新的block给该目录)。
对于文件
当我们在Linux下的EXT2分区中新建一个一般文件,则ext2文件系统会分配一个inode和相对于该文件大小的Block数量给该文件使用。如假设当前EXT2文件系统的Block是4KB,要新建一个100 KB大小的文件,则系统会分配一个inode节点和25个Block来存储该文件。
要注意的是:文件的inode节点虽然记录很多文件的属性,但是并没有记录文件的文件名这一最为重要的属性(一方面是因为inode只有128 bytes大,文件名又是可变的,故而记录在固定size的inode会导致inode必然要出现空间冗余问题,另一方面是因为目录中已经记录文件名,重复记录浪费空间)。
Q: 所以问题来了,那么文件的文件名信息存在哪里呢?我们通过文件名搜索并加载文件的过程是怎样的?
A: 上面介绍目录时说道,新家目录时会给该目录分配一个inode和至少一块Block,该Block便是用来存放在此目录下将要新建的文件或子目录信息(这里便包括了文件名以及文件对应的inode节点指针,当该block填满了,则会再分配新的Block给其使用)。所以当要读取一个文件的具体内容时,会先找到该文件所在目录对应的inode,然后由该目录inode找到该目录的Block area,然后根据文件名搜索匹配到具体文件的索引信息,找到目标文件的inode节点指针,然后再由该文件inode找到该文件对应的Block数据内容存放块,从而取得最终的文档内容。
举个例子:操作系统要读取/home/fire这个文件,则读取过程如下
1. 操作系统根据/home这个目录所在的inode节点,找到该父目录所对应的block区,并根据fire文件名在block区中搜索目标文件的关联索引信息;
2. 找到目标文件的关联索引信息,由此知道fire文件对应的inode节点位置,前往读取该文件的inode节点;
3. 由上个步骤获取的inode节点,可以取得fire文件对应的管理属性,经过权限校验通过后,根据inode节点内部提供的多层block寻址信息,找到该文件的block区,读取该文件的内容。
虽然Linux的标准文件系统是EXT2和EXT3,但除此之外,Linux还支持很多别的档案格式
传统文件系统:ext2/ minix/ MS-DOS/ FAT/ ios9660
日志式文件系统:ext3/ ReiserFS/ Windows’ NTFS/ IBM’s JFS/SGI’s XFS
网络文件系统:NFS/ SMBFS
想要查看Linux系统支持哪些文件系统:ls -l /lib/modules/
uname -r/kernel/fs
而想要查看当前系统已经启用的文件系统则可以:cat /proc/filesystems
可以看到Linux系统不仅支持的文件系统众多,并且还可以同时挂载诸多不同文件系统的设备,但是在我们读写不同设备下的文件时却并没有要求我们手动输入指明该设备匹配的文件系统。这便是VFS抽象层的作用,其会根据系统/etc/mtab内的所有档案尝试主动挂载目标设备。Linux通过VFS抽象层来管理其支持的所有filesystem,并且这一形式的扩展性也是显而易见的。