Linux:文件系统

发布于:2025-07-09 ⋅ 阅读:(15) ⋅ 点赞:(0)

文件系统总结

操作系统访问磁盘时,不会逐个扇区读取,而是以块为单位进行访问。通常一个块包含多个扇区(例如8个扇区)。

磁盘的存储空间可以被划分为多个块,操作系统通过管理这些块来高效地存储和读取数据。

文件系统:

例如,800GB的磁盘可以划分为300GB、300GB和200GB三个块。在Windows系统中,这些块通常被表现为C盘、D盘等。

在Linux中要管理一个300GB的磁盘内存,就只需要再把300GB的内存进行再次分治为一个个块组,就转化成对一个个块组进行管理,块组里面就存放着许多数据块,这些数据块就是用来存放各种数据的,而一个块的大小就是4KB。那么只需要把一个块组管理好,那么其他快组如法炮制就能全部管理好。

上图就是Linux在管理磁盘文件的逻辑图,我们看到在一个有很多区域,这些区域分别管理着不同的类型的数据和属性,接下来就让我们来进行一一介绍。

Data Block:

Data Block区域是用来存放文件数据的。而文件数据就会被存放在一个个的数据块中,一个块的大小就为4kb。那么如果一个文件小于4KB的话也同样会单独存放在这个数据块中,并不会和别的文件共同存储。

Inode:

我们知道文件=内容+属性。那么一个文件的属性,会记录文件的 大小,时间等等。在磁盘系统中许多不同的文件,但是文件的属性都是一样的,只不过是属性里的数据不同。就比如我们是学生,我们都有学号,只是学号各不相同罢了。所以indoe结论就出来了。inode就是一个struct对象,struct_inode记录着文件的各种属性信息。但不包含文件名,这里我们留个悬念。而一个文件就对应一个inode,就像我们身份证一个,是一个唯一值。在这里还要说明。

Inode table:

当我们有了inode_struct一个结构体对象,那么就可以在inode_struct结构体对象里添加inode_struct*next,这样就能将所有的inode_struct连接起来。这样OS管理文件属性就转化成了对链表的管理,那么对文件的增删查改,就变成了对链表的增删查改。这样通过一个inode就能找到inode映射的文件。

Inode bitmap 与Bolck bitmap:

Inode bitmap 与Bolck bitmap都是位图,对于不理解的可以去了解一下数据结构的位图,这里就不在详细解释。

Inodebitmap:每个比特位都标着一个inode,如果这个bit位为0,表示此inode空闲,为1表示此inode正在被占用。

Bolckbitmap:记录着Data Block中哪一个数据块正在被使用,哪一个数据块空闲。

通过Inode bitmap 与Bolck bitmap,就能得出,在同一分区内,inode与数据块的编号都是唯一的,并且不能跨分区。为此还要说明inode和数据块是跨组编号,什么意思呢?

跨组编号意味着 inode 和数据块的编号在整个文件系统中是全局唯一的,并且它们可以位于不同的块组中。文件的inode可能存储在一个块组中,而它的数据块则可能分布在其他块组中。这样设计的目的是为了提高文件系统的性能和空间利用率,尤其在文件较大或磁盘容量较大时。

所以我们如果拿到一个文件的inode,就可用通过.。。

GDT:

GDT全程为:Group Descriptor Table 。块组描述符表,用于描述当前块组中的各种信息,如记录当前块组的inode table、Data block的起始位置与结束位置,空闲的inode和数据块数量,GDT在每个块组开头都有一份拷贝。 文件系统中有多少块组就有多少GDT。

SuperBlock:

SuperBlock又名超级块,SuperBlock记录当前整个文件系统的信息,比如文件系统的总块数、inode 数量、空闲块数、空闲 inode 数量等以及记录文件系统中每个块组(block group)的相关信息,包括 GDT 的位置、每个块组的bolck bitmapinode bitmap inode table的位置。

那可能小伙伴可能就有疑问,既然SuperBlock是记录整个文件系统的,那是否只需要在当前文件系统的开始位置去存放一个SuperBlock区域就可以了。其实SuperBlock会在当前文件系统的开始位置去存放一个,也会在块组里去存放,并不是所有块组都会存放SuperBlock,而是在其中几个块组里各存放一份SuperBlock。那么为什么要这样呢?这里我们要想,如果GDT,inode table这些区域里的数据出现了问题,其实也还好,可能只是损害了几个G的问题,但是如果SuperBlock出现问题,那么整个分区的将会崩,所以当SuperBlock出现问题,就会从其他存放SuperBlock组里进行拷贝恢复数据。

格式化:

理解了文件系统的分区,那么我们也可以理解什么是格式化。我们将一个磁盘内容进行格式化,不就是将两个bitmap全部置为0,然后重新写入SuperBlock。所以格式化的本质就是写入文件系统的管理信息。

目录与文件的关系:

在讲述目录与文件至西安的关系前,我们先抛出两个问题:

1.我们知道访问一个文件需要知道文件的inode号,可是我们从来没使用过inode号

2.目录是文件吗,应该怎么看待?

先来解决问题2:

先说结论,目录就是文件,目录同样也有inode号,我们通过ls -li即可查看。

我们又回到之前保留的问题,我们说文件名不存在inode里,那么其实是存在 目录的数据中。目录存放文件名以及该文件映射的inode。

首先访问一个文件必须需要带路径,但我们在平常访问文件时,并没有带路径,是因为进程PCB里保存了我们当前路径

其实当我们访问code.c的时候OS会自动帮我们拼接补全路径/home/lwh/cat/lession21/code.c。所以文件名=路径+文件名。 OS会对拿到的路径进行路径解析,打开文件所在的路径,接着读取对应目录中的数据,获取到对应的文件名和映射的inode 进行文件查找。

那我们每次访问文件都要进行OS都要进行路径解析吗?这样效率是不是会非常慢。所以天才的计算机学家就想到了,我们将历史访问的所有路径载入内存,形成一个目录缓存。用tree命令就能形象的观察到。

就形成一个多叉树。在Linux中这颗多叉树名称为struct_dentry;

一个 dentry 对象记录了一个文件的 inode、文件名(name),以及用于高效查找和管理的算法结构(如哈希链表、LRU链表等)。当我们打开多个文件时,相应的 dentry 对象会被保存在路径缓存中(即目录项缓存)。这样,在后续访问相同路径时,不必重新进行路径解析,而是直接通过查找 dentry 缓存(如哈希表)来获得文件的相关信息。如果有新的文件或目录被访问,则会创建并缓存新的 dentry 对象。

所以当我们在fopen时候,操作系统都干了什么?

如果我们在fopen时没有指定路径,那么OS添加当前的cwd。所以当我们在fopen的时候,OS在内核当中根据当前路径找到这个文件,如果需要就要查找目录树,帮我们进行路径搜索,把所以的文件上面路径节点全部打开,找到对应的文件名与inode映射关系。

根据路径依次搜索,找到文件的inode,再根据inode找到文件对应的属性与内容。在内核当中创建struct_file对象,创建struct_inode,创建文件缓冲区。

然后把inode属性进行填充,磁盘属性加载到内存。文件内容 全部/部分 加载到文件缓冲区里。然后把struct_file对象的地址在文件描述符标中进行分配,在返回给用户文件描述符。

用户就可以通过返回的fd,找到该文件在内存中的struct_file对象,找到该文件所对应的路径,inode以及缓冲区了。

所以一个文件想要找到它的inode,先通过fd找到该文件的_struct_file,_struct_file里记录着当前_struct_file在dentry树里的节点。在通过dentry树找到文件名所对应的inode。

分区挂载

现在我们得到一个文件的inode信息,就可以特定分区内找到指定文件,也可以通过目录数据找到指定文件和inode,但是inode是不可以跨分区的。那又回到之前的问题,我们怎么知道这个inode是在哪个分区呢?

其实这个问题也很好解决,我们将一个目录与分区进行绑定,当我们进入这个目录就相当于进入这个分区,这个操作称之为分区挂载。

软连接:

当我们给a.out执行程序建立软连接后,直接运行abc就可以运行a.out里的内容,那么可能有小伙伴有疑惑,这样有什么用途呢?

当我们把a.out放到更深层次的目录时,在当前目录对a.out进行软连接后运行abc也就运行了a.out。其实这与Windows下的快捷是一样的。快捷方式会指向可执行文件,运行快捷方式其实就是运行可执行文件,并且当我们查看inode时会看到abc也有自己唯一的inode。所以软连接=快捷方式。

硬链接:

当我们硬链接后,可以看到两个文件的inode都是一样的,并且将abc移动到上级目录,删除a.out后再次运行abc会发现同样能运行。所以我们可以得出一个结论,硬链接等于源文件的一份拷贝。

当我们硬链接了时候我们就会圈起来的数字变成了2,这个数字叫做硬链接数, 表示有多少个文件名指向同一个 inode每创建一个硬链接,硬连接数就+1.

问题思考:

cd命令是在做什么?

首先,shell 脚本接收到 cd 命令后,会通过系统调用 `chdir()` 传递给操作系统,路径字符串作为 `chdir()` 的参数。操作系统会根据这个路径进行解析,使用 `dentry` 树查找相应的目录项。如果路径存在并且可访问,内核会通过该路径找到对应的 `inode` 号,然后更新进程的当前工作目录(cwd),cwd 会指向目标目录的 `inode`,而不是目录名本身。如果路径不存在或无法访问,操作系统会报错

当我们thouch一个文件的时候,系统在做什么?

路径解析:首先,操作系统会检查用户输入的路径是否有效,逐级解析路径中的每个目录。对于每一级目录,操作系统会检查它是否存在,如果某个目录不存在,则返回错误。如果路径正确,操作系统会继续向下查找。

检查文件是否存在:接着,操作系统会检查文件名是否已经存在。如果文件已存在,touch 命令会修改文件 的访问时间(Access Time,A)和修改时间(Modification Time,M),但不会更改文件内容。如果文件不存在,则继续执行以下步骤。

分配 inode:操作系统会为新文件分配一个 inode以下是具体步骤。inode 是文件的元数据结构,其中包含文件的权限、所有者、时间戳、文件大小等信息。操作系统首先检查当前块组的 inode bitmap,查看是否有空闲的 inode。如果有空闲 inode,操作系统会从该块组分配一个 inode 号。

更新 inode table:一旦 inode 被分配,操作系统会初始化 inode 节点,并将其插入到 inode table 中。此时 inode 中的元数据会被更新,包括文件的权限、所有者、时间戳等信息,但文件内容的数据块尚未分配。

更新目录信息:最后,操作系统会在文件所在的目录中创建一个目录项,将文件名和相应的 inode 号进行关联。

创建 struct_file:在文件第一次被打开时,操作系统会为该文件创建一个 struct_file 结构,用于管理文件的读写操作和其他文件描述符的相关信息。

当用户调用 fopen 打开文件时,操作系统执行以下步骤:

路径解析:如果 fopen 没有指定文件的绝对路径,操作系统会使用当前进程的工作目录(CWD)与指定的相对路径拼接成完整路径,并开始解析路径。

查找 struct_dentry:操作系统通过 dentry(目录项)缓存结构,查找路径中的每个目录。如果目录项不存在,则从磁盘读取目录数据,并将其载入到 dentry 树中。

映射关系与 inode 查找:在解析路径时,操作系统会根据 dentry 节点查找文件名与 inode 号的映射关系。找到对应的 inode 后,操作系统能够通过 inode 获取文件的属性和文件内容的存储位置(数据块)。

创建 struct_file 和文件描述符:操作系统为打开的文件创建 struct_file 结构(该结构与文件描述符相关联),并将文件的元数据(如权限、所有者等)填充到 inode 中。然后,操作系统根据文件的访问模式创建或更新文件描述符,并将 struct_file 对象关联到一个新的文件描述符。

加载文件内容:操作系统通过页缓存将文件内容从磁盘加载到内存中,可能部分或全部加载到内核缓存中。而用户级缓冲区(如标准 I/O 库的缓冲区)则在后续文件操作时进行管理。

返回文件描述符:操作系统通过文件描述符将 struct_file 与用户程序关联,最终返回一个文件描述符,供应用程序后续的文件操作使用。

通过这个文件描述符,用户可以访问文件的元数据、内容和内存中的相关结构(如 struct_fileinode 和缓冲区)。


网站公告

今日签到

点亮在社区的每一天
去签到