未安装文件系统的磁盘称之为生磁盘,生磁盘也可以作为文件读写,linux中一切皆文件。
生磁盘可以被分区,分区中可以安装文件系统,常见的文件系统有fat32、ext2、ext4等。分区后的磁盘结构布局如下图,其中主引导扇区记录了分区信息,并且包含引导代码可用于引导操作系统。
分区内可以安装指定文件系统,同一磁盘多个分区文件系统不要求相同。MINIX文件系统布局如下:
用于存放磁盘设备上文件系统结构的信息,说明各部分的大小。
图中列出的数据包含两部分:出现在盘上和内存中的字段和仅在内存中使用的字段,仅在内存中使用的字段是内核读取超级块后所附加的一些用于管理使用的字段信息。
inode节点保存对应文件的属性信息,其中i_zone数组使用类似多级页表的方式维护文件数据块,即记录文件逻辑块到物理块之间的映射关系。
i_zone数组包含直接盘块号、一次间接盘块号和二次间接盘块号。一次盘块号可视为单级页表,一次间接盘块号可视为二级页表、二次间接盘块号可视为三级页表。
这种处理方式的好处在于,对于小文件,通过直接块号可快速定位数据块;对于中等类型的文件,一次间接块可以维护较多数据块的同时,具有较快的访问速度;对于大型文件,二次间接盘块号可以维护大量磁盘块,但访问速度较慢。
内存多级页表与i_zone直接区别:不同进程具有固定大小的虚地址空间,并且对其整个虚地址空间的内存,都有可能访问到,因此使用多级页表。文件系统内存在很多大小不一的文件,综合考虑对不同大小文件的特点,使用1-3级磁盘块表可以分别处理小、中、大文件。
文件系统目录结构为树形,根节点为根目录,目录可以指向若干个子目录或子文件。
通过文件inode节点,可以定位文件数据块,那如何通过文件路径定位到具体文件?
文件系统主要包含文件和目录两种文件,目录是一种特殊的文件,其文件内容存储其目录下文件名->inode节点号的映射信息。文件查找开始于根目录,根目录号固定为0,不需要查找即可直接打开。
举例说明文件查找过程,给定存在路径/name1/name2/name3
查找具体文件过程:
1)通过根节点inode号,打开根目录,读取其文件内容,即目录下文件名->inode节点号映射表,找到name1目录inode节点号n1
2)通过name1的inode号n1,打开name1目录,读取其文件内容,即目录下文件名->inode节点号映射表,找到name1目录inode节点号n2
3)通过name2的inode号n2,打开name2目录,读取其文件内容,即目录下文件名->inode节点号映射表,找到name3目录inode节点号n3
4)通过name3的inode号n3,打开name3文件
1)打开文件:先通过文件查找找到文件inode节点号,然后打开文件,即读取inode至内存。
2)定位数据块:通过文件inode节点,访问其i_zone数组,进一步可以定位具体的数据所在磁盘块号。
硬链接,在指定目录文件中,生成一个文件,即建立文件名->inode节点号的映射。不同之处在于,硬链接文件的inode号是其它文件的inode号,即多个文件共享一个inode。多个文件共享inode节点时,会引起inode节点引用计数增加。
硬链接示意图如下。
软链接,在指定目录文件中,生成一个文件,建立文件名->文件路径的映射。软链接文件项不直接指向被指向文件的inode号,而是记录其文件路径。符号链接不直接共享inode,不会引起inode节点引用计数增加。
打开软链接文件时,先获取其指向的文件路径,再通过指向的文件路径打开文件。
软链接文件示意图如下,其中蓝色线表示软链接。
硬链接:多个文件指向同一个inode节点,引用计数等于文件数量,删除其中一个文件,只会导致引用计数减小1,引用计数为零时,文件才会被删除。
软链接:删除软链接,不会对被指向文件产生任何影响;删除被指向文件,软链接可能会失效。
高速缓存是内核访问磁盘文件系统数据的必经之道,高速缓存用于解决CPU速度与磁盘IO速度不匹配的问题。因为CPU速度与磁盘IO速度差距较大,CPU同步读写磁盘数据,会导致CPU性能的浪费。
内核维护一个高速缓冲区池,按块使用和管理,可用于缓存磁盘数据,提高访问磁盘数据的性能。
buffer.c文件包含实现高速缓冲区的程序。内核代码通过指定设备号和逻辑块号来调用块读写函数。这些接口函数有,bread()、breada()、bread_page()。
该部分函数用于处理文件系统的元数据,包括超级块、位图、inode节点等。该部分函数被内核其它部分代码调用,不是提供给用户代码调用的系统调用接口。
文件系统底层处理函数包含在以下5个文件中:
bitmap.c:包含对i节点位图和逻辑块位图进行释放和占用处理函数,free_inode()、new_inode()、free_block()、new_block()
truncate.c:对数据文件长度截断为0的函数truncate(),可释放文件所有数据块。
inode.c:包含分配释放inode节点函数iget()和iput(),以及根据inode节点,获取文件逻辑块号函数bmap()。
namei.c:包含函数namei(),获取指定路径的inode节点
super.c:包的程序专门用于处理系统超级块,包括函数get_super()、free_super()等。还包括几个文件系统加载/卸载处理函数和系统调用,如sys_mount()等。
下图展示了文件系统不同部分对应的底层处理函数:
该部分函数用于处理文件的读、写操作。
用户程序调用read()、write()函数读写文件,read()、write()函数根据文件类型与其所在设备,进一步调用设备对应的读写函数如read_pipe()、write_pipe()等。
内核使用文件结构file、文件表file_table[]和内存中的i节点表inode_table[]来管理对文件的操作访问。
思考:文件描述符指向file结构、file结构指向内存inode节点,允许存在多对一的情况。什么场景会存在多个文件描述符指向同一个file结构;什么场景存在多个file结构指向同一个内存inode节点。
open同一个文件:多个file数据指向同一inode节点
dul同一文件描述符:多个文件描述符指向同一文件表项—file数据。
fork进程:仅复制文件描述符表
内核数据结构
file数据结构