本篇文章自顶向下,从文件系统的上层出发讲到磁盘,帮助理解程序是如何打开文件并进行后序的读写操作的,读到后面,前面的一些疑惑就得到解决
介绍相关概念
注意,目录也是文件
文件描述符
每个进程都有一个指针*files
,指向一张表files_struct
,该表最重要的部分就是包涵一个指针数组的struct_file*fd_array
每个元素都是一个指向打开文件的指针(fd)!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
当我们使用系统调用open
来打开文件时,我们会发现打开open函数会返回一个int类型的数据,在man手册中我们可以看到,这个int类型的数据叫做文件描述符,简称 fd。那么open
函数是怎么做的呢?
OS会拿到open
函数传入的实参pathname,然后根据路径名在文件系统中的目录项中查找该文件,如果找到了就返回 fd,没有找到就返回-1,并设置错误码。
在进程PCB中,会维护一个文件描述符表,该表可以想象为1个数组,fd是索引,该表存放的就是进程所打开的文件的文件描述符 fd,文件描述符一般会由OS选择表中最小的没有被使用的描述符作为当前要打开的文件的 fd。
fd 与 文件的 iNode 相关联,通过fd 就可以找到文件的iNode。
文件表
- 文件表处于文件系统中间的位置,属于OS
- 在Linux系统中,文件表通常是由一个链表来维护的,每个文件表项都有一个指向下一个文件表项的指针。
- 其上下层关系如下:
-
下层 - iNode :
- inode和文件数据块
- 每个文件在磁盘上都有一个对应的inode,存储文件元信息
- 文件数据存储在数据块中
- inode包含了指向数据块的指针
-
中层 - 文件表
- 文件表跟踪每个进程打开的文件信息
- 每打开一个文件,在文件表创建一个文件表项
- 文件表项包含了文件描述符、打开模式、当前偏移等信息
- 通过文件描述符可以找到对应的inode
-
上层 - 虚拟文件系统(VFS)
- VFS提供统一的文件系统操作接口给上层应用,如
open
- 接收应用层的操作请求,转换为对具体文件系统的调用
- 调用文件表接口实现对打开文件状态的跟踪
- VFS提供统一的文件系统操作接口给上层应用,如
-
iNode(index-node)
- iNode在linux内核中是一个结构体,每一个文件对应一个iNode号,包含文件的各种属性信息,比如文件占用的逻辑块数量,文件实际数据块的指针数组(指向了文件数据实际在磁盘中存储的位置),文件的权限,文件的修改时间,文件大小等信息。我们要通过 fd 找到 iNode,再通过iNode中设置的文件数据存储的数据块的地址来找到文件数据实际存储在磁盘的哪个扇区(物理块)。
- 要注意的是,iNode中所指向的数据块地址的字段不一定是直接指向数据实际存储的地址,iNode中存储的可能是间接地址。
- 具体地说,inode中存储了12个指向数据块的直接指针(如果文件大小<=12个数据块,这些指针就足够了),还包括一个一级间接块指针、一个二级间接块指针和一个三级间接块指针。这些指针分别指向一级、二级、三级块指针数组,这些数组中存储了更多的数据块地址。如果inode中的数据块数组直接索引到数据,那么数据块数组的大小开多大就是一个问题。所以通过上面存储指针的方式,文件系统可以支持非常大的文件,同时减少了inode的空间占用,解决了inode的存储空间问题。
- 同时,指针块不需要完全填满,可以按需分配,避免过多无用空间。
- 通过直接指针和多级间接指针的组合,iNode可以支持对大文件的数据寻址,最大可以支持GB级的数据量。
磁盘抽象之文件系统
文件系统是OS的一个子集,用于管理文件。这里介绍一下文件系统的简单布局:
各个字段介绍
- 磁盘被划分为一个或多个分区,每个分区中有一个独立的文件系统。磁盘的0号扇区称为主引导记录(Master Boot Record,MBR),用来引导计算机。该表给出了每个分区的起始和结束地址。表中的一个分区被标记为活动分区。在计算机被引导时,BIOS读入并执行MBR。MBR做的第一件事是确定活动分区,读入它的第一个块,称为引导块(boot lock),并执行之。引导块中的程序将装载该分区中的操作系统。为统一起见,每个分区都从一个引导块开始。
- 说人话就是:
- 想象一下你的电脑是一辆车,启动电脑就像启动车辆一样需要一个启动过程。
- MBR就像车钥匙,当你转动钥匙(加电启动)时,BIOS读入并执行MBR,它(MBR)包含了启动计算机最基本的程序(引导代码)。这些基本程序知道下一步应该去运行哪些程序,比如启动Windows或Linux系统的核心程序。
- 类似钥匙会转动点火、启动发动机,MBR中的引导代码启动了计算机,将控制权转交给操作系统,使计算机能正常工作。
- 另外在MBR的结尾是分区表,该表给出了硬盘的每个分区的起始和结束地址。记载了硬盘里面数据都存放在哪些区域,就像车里各个存放物品的地方一样。但现在新车型(新电脑)已经用更先进的分区方式了。这里不做介绍。
- 超级块(Superblock)
我们可以把文件系统想象成一个图书馆。
超级块就像图书馆的目录,它记录了这个图书馆的全局信息: - 图书馆有多大,可以存放多少本书(文件系统的大小)
- 书籍资源如何组织,按哪些规则排序(文件系统的结构和布局)
- 书籍的总数和可用数(文件系统的空间使用情况)
- 书籍增加或删除的基本规则(文件系统如何分配空间) 等等。
- 所以:
当你进入图书馆,首先需要查阅目录,了解这个图书馆的整体情况。
同样,当操作系统访问文件系统时,首先需要读取超级块,以了解文件系统的全局信息。
通过超级块,操作系统知道如何组织和管理文件系统,像添加删除文件,分配空间等。
可以看出超级块非常重要,它为操作系统提供了打开并使用这个“文件系统图书馆”所需要的全部信息。
所以简单来说,超级块就像图书馆的目录,记录了文件系统的全局信息,帮助操作系统理解和管理文件系统。 - 空闲空间管理
- 可以把文件系统比作一个储物柜,里面有许多不同大小的抽屉。
- 这些抽屉可以用来存放文件。当需要存储新文件时,就需要找到大小合适的抽屉来存放。
那么空闲空间管理就好比是一个记录,跟踪哪些抽屉是空的,大小是多少。
当你要把新的文件放入柜子时,可以查看记录,很快找到合适大小的空抽屉来使用。 - 如果没有空闲空间管理的记录,每次要放新文件时,你就需要一个一个抽屉打开查看能不能放得下,十分低效。
- 同样,文件系统中也需要空闲空间管理来跟踪空闲的存储空间,以方便快速找到合适的空间存放新文件。
- 一般来说,文件系统会以一定的粒度来划分空闲空间,并用位图等数据结构跟踪空闲块。
- 所以简单来说,空闲空间管理就像储物柜的空位记录,它帮助文件系统快速高效地找到空闲空间以存储新文件。
- i结点
就是iNode结点,包含了各种文件相关属性信息(也称元数据)。 - 根目录,文件和目录
- 在Windows里,C盘可以视为一个分区,C盘下的文件夹就相当于这个分区的目录。
例如C盘下有一个Windows文件夹,这个Windows文件夹就是C盘根目录下的一个子目录。
再比如在Windows文件夹里面,有个System32文件夹,那么这个System32就是Windows目录下的一个子目录。 - 又如在System32文件夹里面,有个drivers文件夹,那么drivers文件夹就是System32的子目录。
也就是说,C盘本身可以视为根目录,里面的Windows文件夹是C盘的一个子目录,System32是Windows目录的子目录,drivers是System32的子目录。 - 通过这种目录树的逻辑组织,文件系统可以层层包含子目录和文件,就像文件夹套娃一样。
我们通过从根目录开始,一级一级地打开子目录,就可以找到需要的文件,非常方便管理。 - 所以在Windows的文件系统中,根目录代表一个磁盘分区,子目录代表当前目录下的子文件夹,它们一起构成了文件资源的目录树结构。
- 另外,文件,emmm,就是文件,没什么解释的。
- 在Windows里,C盘可以视为一个分区,C盘下的文件夹就相当于这个分区的目录。
磁盘的寻址方式
磁盘是典型的块设备,硬盘分区被划分为一个个的block(块,也叫扇区)。一个block的大小是由格式化的时候确定的,并且不可以更改。
我们将磁盘分成一个个的扇区,如图中蓝色线条组成的三角形一样,又称物理块,我们磁盘的寻址一般以一个扇区(一个块)为单位进行寻址。
串起来
通过上面的基础知识铺垫,我们现在可以串一串程序究竟是怎么打开一个文件并在后序对文件进行操作的了。
- 程序运行起来,调用语言相关文件打开函数,或直接使用系统调用。
- 在打开文件时,VFS层(虚拟文件系统)根据文件路径在磁盘的目录树中找到iNode,如果找到该文件,那么为此文件分配一个文件描述符,维护在进程的文件描述符表中,在当前进程的文件表中创建文件表项,建立起 fd 与 iNode 的映射关系,并记录打开状态。
- 如果使用的是系统调用则返回文件描述符,如果是语言封装的文件相关函数,那么返回语言规定的返回值,该返回值封装了 fd。比如c语言中的FILE*,该结构体指针就封装了 fd。
- 打开文件后对文件读写时,通过文件描述符在文件表中查找记录,进行偏移定位,找到对应的iNode,通过提取iNode中具体的文件所在逻辑块位置,通过块位映射表(维护了逻辑块与物理块的映射关系,类似与虚拟内存的页表)最终转换为对具体物理块的操作。
- 关闭文件时,文件表项被删除,资源释放。
用通俗的语言解释上下层关系:
- 把文件系统比作一个组织机构,不同层次负责不同的工作:
- 底层员工(inode)负责文件存储和元信息。
- 中间管理(文件表)负责跟踪当前正在处理的文件项目。
- 高层主管(VFS)与外界交流,接受任务请求。
- 当一个新任务来时,主管交给管理层处理,管理层创建一个项目跟踪记录。
- 员工根据记录去实际操作和处理项目,完成时汇报管理层。
- 管理层最后将项目记录删除,汇报主管任务完成。
- 这样不同层级最终协同完成对文件操作的管理。
几个关于文件系统的疑问
我们删除文件,是直接对磁盘上的文件清理了吗?
不,我们只是对文件的iNode进行了处理。因为有文件系统这一层抽象,我们的计算机是通过文件系统来管理文件的,那么我们也就只需要在文件系统中要删除的文件所对应的iNode删掉就好,没有了索引,也就相当于不存在,下一次别的文件就可以直接覆盖磁盘上的这一块区域,也是效率上的提示。
我们常说的格式化,比如格式化硬盘,什么意思
实际上就是对硬盘进行初始化,加载文件系统,向该磁盘分区写入文件系统的管理属性信息。这里我们也可以看到,其实每个分区都会有一个单独的文件系统进行管理。