欢迎光临
我们一直在努力

【C++与Linux基础】文件篇(8)磁盘文件系统:从块、分区到inode与ext2

本系列主要旨在帮助初学者学习和巩固Linux系统。也是笔者自己学习Linux的心得体会。

在这里插入图片描述

个人主页:
爱装代码的小瓶子

文章系列:
Linux

2.
C++


文章目录

  • 1.前言:
  • 2 文件系统的概念:
    • 2-1 引入“块”概念,更好的理解文件:
    • 2-2 引入“区”的概念:
    • 2-3 引入“组”的概念:
  • 3. ext2系统的正式介绍
    • 3-1 Data Blocks和Block Bitmap
    • 3-2 inode Table 和inode bitmap
    • 3-3 GDT和Super Block
    • 3-4 实验 + 总结:
  • 4. 从磁盘到内存,文件的生与死
    • **4-1 诞生与静态存在:磁盘上的“户籍”与“房产”**
    • **4-2 打开:从磁盘加载到内存的“唤醒”仪式**
    • **4-3 读写:数据的流动与缓冲**
    • **4-4 关闭与消亡:资源的释放**
    • 4-5 总结与描述:
  • 总结

1.前言:

在我们之前的文章中主要讲的是:内存中被打开的视角,今天这篇文章我们着重讲述: 磁盘的文件系统:

  • 为了便于对文件进行管理:引入块的概念。
  • 引入分区的概念。
  • 引入inode的概念。
  • 我将一步一步带你讲解这些概念,让你更好的理解磁盘上的文件。随后我们需要引入ext2的文件系统的概念。 其实这里也是比较难的。但是也是我们理解Linux系统的基石。

    2 文件系统的概念:

    在我们的磁盘上面,有这么多的文件,我们应该怎么管理呢?接下来我们讲慢慢深入探讨Linux下的文件是如何被系统给管理的。

    2-1 引入“块”概念,更好的理解文件:

    其实硬盘就是一个块设备。操作系统在读取硬盘的时候,是不会一个一个扇区来来读取的,而是八个扇区完成读写的,这八个扇区(512B * 8 = 4096B = 4KB)即:

    1

     Block (4KB)

    =

    8

    ×

    Sectors (512B)

    1 \\text{ Block (4KB)} = 8 \\times \\text{Sectors (512B)}

    1 Block (4KB)=8×Sectors (512B) 如果说扇区是磁盘的“物理刻度”,那么块就是文件系统的“逻辑容器”。无论你是电影还是音频还是txt文本,在操作系统中,一切都是以块为调度的。

    1.什么是CHS?

    • 磁盘由柱面(Cylinder)、磁头(Head)、扇区(Sector) 构成。
    • CHS 就是通过这三个坐标来定位一个具体的物理扇区 2.什么是LBA?
    • 为了解决上述问题,我们将磁盘复杂的物理结构进行“拉直”。
    • 你可以想象把磁盘所有盘片上的所有磁道(按柱面顺序)首尾相连,拼接成一根很长的“磁带”。
    • 这根“磁带”上的每一个扇区,就获得了唯一的、连续的线性地址,就像一维数组的下标。这个线性地址就是 LBA。
    • 从此,在操作系统和用户看来,磁盘就是一个“一维数组”,数组下标是LBA,每个元素是一个扇区。

    磁盘就是⼀个三维数组,我们把它看待成为⼀个"⼀维数组",数组下标就是LBA,每个元素都是扇区。

    在硬件物理视角是如何看待磁盘是一个三维数组,我们一层一层的来看:

  • 磁盘看作一个“一维数组”
    • 磁盘的物理结构是三维的(柱面、磁头、扇区),但操作系统和上层应用通过 LBA 将其抽象为连续的一维地址空间
    • 每个 LBA 对应一个唯一的扇区,就像数组的下标
  • 扇区(Sector) – 传统机械硬盘和许多固态硬盘的基本读写单位,通常为 512字节 或 4KB(高级格式化)。 – 一个扇区在逻辑上对应一个 LBA。
  • 我们通过对CHS和LBA的相互转化就可以了,相连的八个扇区就是一个块。 image.png|642

    2-2 引入“区”的概念:

    这里我们先谈了分区的概念:像我们的windows平台,提供了不同的区比如:C盘和D盘,但是实际上,你的硬盘并没有被一分为二。这里区是为了更好的管理整个硬盘。 分区的本质是设置每个区域的起始和结束柱面号。“柱面是分区的最小单位”。

    如果把一整块物理硬盘想象成一大块“原始土地”。 那么 分区 就是在这块土地上,用篱笆划出几块独立的、逻辑上的“大区域”。例如:C区、D区、E区。 分区之后,每个区域就需要建立自己的“管理规则”(即文件系统),这个过程就是 格式化。所以文档说:“分区从实质上说就是对硬盘的一种格式化”,但格式化后还不能直接使用,需要 挂载(这里挂载后面再讲)

    我们这里说一个分区就是一个完整的文件系统了。

    2-3 引入“组”的概念:

    什么是分组:分组是文件系统内部的管理单元,是一个分区被文件系统格式化后,内部进一步的细粒度划分。

    为什么这样划分:一个区还是太大了,为了便于管理每一个数据和组织起每一块区域。在前面我们不能详细的讲太多,但是这里还是可以点一些东西出来:

    • 便于管理:将大分区的元数据(如inode表、块位图)分散存储,避免寻找空闲块或inode时需要遍历整个庞大的分区,提升效率。
    • 提高可靠性:超级块 (Super Block) 在每个块组都有备份。如果一个块组的超级块损坏,可以从其他块组的备份中恢复,增强了文件系统的健壮性。
    • 支撑大容量:这种结构能更好地支持和管理非常大的硬盘分区。

    这些我们后面都会讲出来。

    3. ext2系统的正式介绍

    我们借ext2正式来介绍文件系统时怎么管理,我们先看图片: image.png

    这里我们就详细的画了一个硬盘是如何划分成为不同的区,再通过不同的区划分成为不同的组,一个组里面有什么,大致分成什么部分。这里都是我们要讲清楚的:

  • Data Blocks :存放数据的区域。
  • inode Table inode 表
  • inode bitmap:inode 的位图
  • Block Bitmap: 数据块的位图。
  • GDT :一个组信息。
  • Super Block:超级块(备份思想) 接下来,我们就详细说说这个六个吧:
  • 3-1 Data Blocks和Block Bitmap

    其中Data Blocks用于存储实际文件数据、目录条目等。这是块组的最大部分。比如我们从网上下一个电影或者文章,就是存放在这个位置。 我们在之前的文章中我们经常说:文件 = 文件内容 + 属性

  • 在普通的文件中:这里存放的是就是文件里面的内容。我们文件里面的内容,也是我们写的代码,就是放在这里的
  • 对于目录(创建的目录,mkdir)在Data Blocks中存放的就是文件名和inode的映射(现有个印象,后面会讲)
  • 那么这个Data Blocks 就清楚了,那么这个相关的位图有什么用呢? 没错,他就是用于看那个db里面那个区域存放了数据,哪里没有存放,这个巧妙的使用另一个01就控制了db里面的数据到底是存放还是没有存放。其中:1表示已用,0表示空闲。通常的大小是1块。

    题外话:我们在删除文件或者电影或者某个程序,就是讲这里的块位图置为0.下次再下载的时候,直接进行覆写就可以了。我们后面还会讲到inode bitmap怎么变化。 “由此可见,Block Bitmap 是文件系统管理存储空间的核心数据结构之一。它通过简单的 0/1 标记,高效地记录了数百个数据块的空闲状态,使得文件的创建(分配块)和删除(释放块)变得非常迅速。”

    3-2 inode Table 和inode bitmap

    我们刚刚说到了文件 = 文件内容 + 文件属性。文件内容我们已经知道在DB里面了,但是属性呢? 你想到没错,就是放在inode table 里面了。inode表是每个块组中一个连续的块序列,用于存储文件系统的inode(Index Node)结构。 那么inode是什么?inode是ext2文件系统的核心元数据单元,每个文件、目录、符号链接或设备文件都对应一个唯一的inode(inode号从1开始,0保留)。inode编号的唯一性是再一个分区里面的,也就是只给1,进程是不知道文件再哪里的。 inode表是一个连续的磁盘块区域(由组描述符中的bg_inode_table字段指向起始块号)。表内按inode号顺序排列:inode号1的结构体在表的最开头,inode号2紧接着,依此类推。偏移计算公式:(inode_number – 1) * inode_size(inode_size通常128字节)。

    这里面的结构体,到底记录什么吗?

    偏移字段名称大小(字节)描述
    0-1 i_mode 2 文件类型和权限
    2-3 i_uid 2 低16位用户ID
    4-7 i_size 4 文件大小(低32位)
    8-11 i_atime 4 访问时间
    12-15 i_ctime 4 创建时间
    16-19 i_mtime 4 修改时间
    20-23 i_dtime 4 删除时间(0表示未删除)
    24-25 i_gid 2 低16位组ID
    26-27 i_links_count 2 硬链接数
    28-31 i_blocks 4 占用的512字节扇区数
    32-35 i_flags 4 文件标志
    36-39 i_osd1 4 OS特定字段
    40-87 i_block[15] 48 块指针数组:12个直接 + 1间接 + 1双间接 + 1三间接
    88-91 i_generation 4 文件版本(NFS用)
    92-95 i_file_acl 4 文件ACL块号
    96-99 i_size_high / i_dir_acl 4 大小高32位或目录ACL
    100-103 i_faddr 4 碎片地址
    其余填充/扩展字段 OSD2等,视系统而定

    大致就这样,这里已经很详细,可以看一下。不是本文的重点。

    image.png

    那么 inode bitmap是做什么的呢? 类似块位图,用于标记块组内哪些inode已被分配。大小为1块。也是和上面一样的。

    所以真正的删除,只要改变两个位图,其实就好了,这又是为什么删除比下载快多了的原因。

    3-3 GDT和Super Block

    先说GDT是什么?他是每一个组都必备的。ext2把整个文件系统划分为多个块组(Block Groups),而GDT就是用来描述“每个块组长什么样、里面有哪些东西”的表格。 位置:GDT紧跟在超级块(Superblock)之后。块组0里有主拷贝,其他某些块组(如1、3、5、7的幂次方组)里有备份拷贝,用于损坏恢复。 Super Block(超级块)就是一个区域的描述。超级块(Superblock)是ext2文件系统的“总控制台”或“全局配置文件”,它存储了整个文件系统的核心元数据,让内核一读就能知道这个分区是ext2、有多大、怎么划分等信息。

    由于它特别重要,所以我们能看到他在很多分组中能看到这个超级块。当有一个被破坏了,进程可以读取其它的来恢复超级块。

    简单说:超级块告诉你文件系统整体有多大、有多少块组;GDT则告诉你“第N个块组的位图和inode表在哪儿、还剩多少空间”。 image.png 这张图形象地解释了“操作系统是如何知道磁盘哪里是空的,哪里是满的”**。

    它没有去扫描每一个具体的硬盘扇区(那样太慢了),而是查看一张“地图”(位图)。如果地图上标记为 0,系统就知道那里是个空车位,可以直接停进去。

    3-4 实验 + 总结:

    我们先来看看,如何看待每个文件,是怎么知道知道每一个文件的inode 呢? 我们可以使用

    ls -a -li

    来查看该目录文件的inode: image.png 我们说进程是不认识这些文件,而是通过inode 来寻找。但是反直觉的是,我们再寻找的时候,一般都是通过find 加路径和名字来寻找的 image.png

    这里我们就要回顾我们之前在Data Blocks里面说的目录存储的是文件名 和inode 的对应关系。 目录(Directory)本身也是一种特殊的文件。 虽然文件的 Inode 里没有存文件名,但是**目录的数据块(Data Block)**里存了一张“对照表”。 当我们说 lesson8 是一个目录时,你可以把它想象成一个“电话本”。这个电话本的内容(Data Block)长这样:

    文件名 (Filename)Inode 编号 (索引节点号)
    . (当前目录) 415984
    .. (上级目录) 392468
    test.c 415986
    main.c 415990
    可以试着运行一下 stat test.c 命令。它会把 inode、权限、修改时间等信息一次性全部列出来

    这样我们就知道了进程是如何把这些文件转换成为了inode来搜索这个文件的位置了。

    读到这里,我们已经知道了知道文件时怎么存放在磁盘上面的,接下来我们讲讲什么文件时怎么到内存里面的,open函数时怎么精确定位的。

    4. 从磁盘到内存,文件的生与死

    我们已经剖析了文件在磁盘上的“静态家园”(ext2结构)。现在,让我们跟随一个文件的旅程,看它如何从冰冷的磁盘被唤醒,进入活跃的内存,为进程服务,最终走向消亡。

    4-1 诞生与静态存在:磁盘上的“户籍”与“房产”

    一个文件在磁盘上并非一个连续的整体,而是被拆解为:

  • 一份户籍(inode):存储在 inode Table 中。拥有唯一编号(inode number),记录了文件的元数据(大小、权限、所有者、时间戳等)以及最关键的信息——其数据块存放在哪些 Data Blocks 中(通过直接、间接指针)。
  • 多处房产(Data Blocks):实际存储文件内容的地方。由 inode 中的指针寻址。
  • 位图登记:它的存在被记录在 inode bitmap 和 block bitmap 中,状态为“已占用“。
  • 核心观点**:此时,文件是静止的。操作系统通过文件系统(如ext2)的这套“账簿”(超级块、位图、inode表)来管理它,但文件本身并未参与任何运

    4-2 打开:从磁盘加载到内存的“唤醒”仪式

    当进程执行 open() 系统调用时,魔法开始了。这是磁盘与内存的第一次握手。

  • 路径解析:内核根据文件路径,从根目录 / 或当前目录开始,逐级查找目录的 Data Blocks(里面存着 文件名->inode编号 的映射),最终找到目标文件的 inode number
  • 加载元数据:
    • 内核根据 inode number,去对应的块组中读取文件的 inode 信息。
    • 在内存中创建一个 struct inode 的内核对象(VFS层),复制或映射磁盘 inode 的关键信息。这是该文件在内存中的核心代表。
  • 创建访问句柄:
    • 内核为本次“打开”创建一个 struct file 对象。它包含了:
      • 指向内存 inode 的指针。
      • 文件读写偏移量(f_pos)。
      • 访问模式(读、写、追加等)。
      • 操作函数集指针(指向具体文件系统如ext2的读写函数)。
  • 与进程关联:
    • 在进程的文件描述符表(File Descriptor Table) 中,分配一个最小的空闲编号(如 3)。
    • 将这个 struct file 对象的地址填入其中。
    • open() 系统调用将这个文件描述符(fd,例如 3)返回给用户程序。
  • 此时的状态:文件的“魂”(inode信息)已被调入内存,并准备好了“操作界面”(struct file)。但文件的实际数据(内容)还懒洋洋地躺在磁盘的 Data Blocks 里,并未全部加载。

    4-3 读写:数据的流动与缓冲

    当进程通过 read(fd, buffer, size) 或 write(…) 操作文件时,数据开始流动。

  • 通过 fd 找到一切:内核用 fd(如 3)从进程的文件描述符表找到 struct file,再找到 struct inode。
  • 页面缓存(Page Cache)—— 内存中的“数据驿站”:
    • 这是Linux内核一个至关重要的机制。内核会在内存中开辟一片区域,作为磁盘数据的缓存。
    • 第一次读:内核根据 inode 中的指针,从磁盘 Data Blocks 读取所需数据到页面缓存,然后再复制到用户提供的 buffer 中。
    • 后续读:如果数据已在页面缓存中,则直接从内存提供,速度极快。
    • 写操作:数据通常先写入页面缓存,并被标记为“脏页”。内核会在合适的时机(或调用 fsync() 时)再将“脏页”写回磁盘对应的 Data Blocks。
  • 修改回写:写入操作可能会导致磁盘上文件的 inode 元数据变更(如文件大小、修改时间),这些变更也需要同步回磁盘的 inode Table。
  • 核心思想:内存中的 页面缓存 解耦了进程快速的读写请求与相对缓慢的磁盘IO,是文件系统性能的关键。

    image.png

    4-4 关闭与消亡:资源的释放

  • 关闭 (close(fd))

    • 进程调用 close(),内核释放对应的 struct file 对象。
    • 递减引用计数:该文件的 struct inode 的引用计数减1。如果减到0(没有进程再打开它),内核可能会尝试释放内存中的 inode 对象。
    • 重要:关闭文件并不立即刷新“脏页”到磁盘。数据可能还在页面缓存中,等待内核线程写回。
  • 删除 (unlink()) – 真正的“死亡”

  • unlink() 系统调用操作的是目录的 Data Blocks,它删除了 文件名->inode编号 这条映射记录。
  • 关键步骤:
    • 将该文件 inode 的硬链接数减1。
    • 如果硬链接数变为 0,且没有进程打开此文件(inode引用计数为0),则文件系统开始真正的清理:
    • 将其 inode 在 inode bitmap 中标记为空闲(0)。
    • 将其占用的数据块在 block bitmap 中标记为空闲(0)。
    • 注意:如果文件正在被进程使用(已打开),即使执行 unlink,磁盘空间也不会立即释放,会延迟到最后一个打开它的进程关闭文件后。这解释了为什么有时删除了一个大文件,磁盘空间却没马上回来。
  • 4-5 总结与描述:

    我们open的时候,按理会从更目录一直读到目标位置,为了不每次都读磁盘,Linux 有一个dentry cache (目录项缓存),如果刚才访问过这些目录,它会直接从内存缓存里拿到 Inode 号,速度极快。找到 Inode 号之后,系统把磁盘上的 Inode 信息(权限、大小、Block位置)拷贝一份到内存里。创建struct_inode。那么struct_task里面会有:一个结构体指针files,指向struct_file,里面有一个数组fd_array,里面0,1,2都已经存储了三个标准文件。我们刚刚打开的文件就会在4号。我们通过这个找到struc file。里面有他的inode指针,指向struct inode。这里就描述了他在哪里。但是仅仅通过这个还是不行,我们不知道他在哪个区域!我们还需要挂载,进程(struct_task)会一个指针打开struct_path,里面有两个变量:他的地址,我们通过地址就知道他是哪个区的。这样才能顺利完成读写。

    这上面还是有些错误,我来重写修改: 我们 open 的时候,按理会从 根目录 (更目录) 一直读到目标位置。为了不每次都读磁盘,Linux 有一个 dentry cache (目录项缓存)。如果刚才访问过这些目录,它会直接从内存缓存里拿到 Inode 号,速度极快。 找到 Inode 号之后,系统把磁盘上的 Inode 信息(权限、大小、Block位置)拷贝一份到内存里,创建 struct inode。 那么 task_struct (struct_task) 里面会有:一个结构体指针 files,指向 struct files_struct (struct_file,注意:这是文件描述符表,不是文件对象),里面有一个数组 fd_array。里面 0,1,2 都已经存储了三个标准文件,我们刚刚打开的文件通常就会在 3 号 (4号)。 我们通过这个 fd 索引 找到 struct file。关键点在这里: 里面有他的inode指针,指向struct inode struct file 里面包含了一个 struct path。 进程(struct_task)会一个指针打开struct_path 这个 struct path 里面有两个变量:

  • mnt 指针 (他的地址):我们通过它就知道他是 哪个文件系统/哪个分区 (哪个区的);
  • dentry 指针:它指向 struct inode,这里描述了物理数据在哪里。
  • 总结

    这篇文章完整揭示了Linux文件系统(以ext2为例)的核心机制:在磁盘上,文件被拆解为存储属性的inode和存储内容的Data Blocks,并通过位图高效管理空间;当文件被打开时,内核将其inode加载到内存并创建访问句柄,进程通过文件描述符经由页面缓存进行快速读写;而删除文件本质上只是清除位图中的占用标记,因此速度极快,这体现了文件系统通过精巧的元数据设计来桥接物理磁盘与逻辑访问的精髓。

    这篇文章稍微有点长,有点难,希望大家能有所收获!

    感谢各位对本篇文章的支持。谢谢各位点个三连吧!


    请添加图片描述 在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » 【C++与Linux基础】文件篇(8)磁盘文件系统:从块、分区到inode与ext2
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址