news 2026/10/21 2:30:25

【Linux】文件系统(一)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Linux】文件系统(一)

目录

磁盘的理解

存储结构

相关概念

CHS和LAB

CHS

LAB

公式转换

文件系统

初步

”块“的概念

“分区”

iNode

块组内部

Super Block(超级块)

GDT(Group Descriptor Table)

Block Bitmap(块位图)

Inode Bitmap(inode 位图)

Inode Table(索引节点表)

Data Blocks(数据块)

疑问


磁盘的理解

存储结构

相关概念

拆开一个机械硬盘,你会看到里面有几个关键部件:

  1. 盘片(Platter):像光盘一样闪闪发光的圆盘。数据就刻在上面。

  2. 磁头(Head):悬在盘片上方极近的地方,负责读取和写入数据。重点:每个盘面都有一个磁头,但所有的磁头都连在同一个机械臂上,同进同退!

  3. 主轴(Spindle):带着盘片飞速旋转的马达。

为了在这么大的盘片上定位数据,工程师发明了三个概念:

  • 磁道(Track):把盘片想象成操场跑道,一圈一圈的同心圆就是磁道。

  • 扇区(Sector):把跑道切成一段一段的,每一小段就是一个扇区。这是磁盘存储的最小物理单位,通常是 512 字节。

  • 柱面(Cylinder):因为所有盘片的磁头是同进同退的,所以当你把磁头停在某个位置时,所有盘面上对应位置的磁道在空间上叠在一起,形成了一个圆柱体,这就是柱面。

CHS和LAB

CHS

如果让你去拿数据,你需要告诉机械臂:

  1. 去第几个柱面(C)?

  2. 用第几个磁头(H)?

  3. 读第几个扇区(S)?
    这就叫CHS 寻址。但这种方法很反人类,因为你要同时管理三个数字,而且外圈磁道和内圈磁道能切的扇区数量还不一样(早期)。

⼀个细节:传动臂上的磁头是共进退的

LAB

操作系统是个“大忙人”,它不想管你里面有几个盘片、磁头怎么动。它希望用最简单的方式管理存储。

所以,操作系统把整个磁盘“拍扁”,看成一根长长的“录音磁带”。

  • 磁带上密密麻麻排满了等大的格子。

  • 每个格子就是一个扇区。

  • 每个格子都有一个唯一的编号(从 0 开始),这个编号就叫 LBA(Logical Block Address,逻辑块地址)。

操作系统只需要说:“我要读第 1000 号扇区(LBA=1000)”,然后把它交给磁盘控制器。
磁盘内部的固件(伺服系统)会自己用数学公式,瞬间把 LBA 1000 转换成对应的 CHS(柱面、磁头、扇区),然后机械臂精准地去拿数据。

公式转换

假设磁盘有 100 个柱面,4 个磁头,每磁道 8 个扇区。
想象你在操场上找自己的位置(LBA),你要先算前面有几个完整的小队。

  1. C 算前面有几排:每个柱面(排)有4 × 8 = 32个扇区。
    LBA = C × 32 + H × 8 + (S - 1)
    (注:S 减 1 是因为物理扇区从 1 开始数,而 LBA 从 0 开始数)

  2. 反推:
    C = LBA ÷ 32(算排在前面有几排)
    H = (LBA 剩下的余数) ÷ 8(算这一排里,前面有几层)
    S = (LBA 剩下的余数) % 8 + 1(算这一层里,第几个座)

所以:OS 只认 LBA,磁盘硬件自己转换成 CHS。

文件系统

初步

”块“的概念

物理磁盘存取的最小单位是扇区(Sector),通常只有 512 字节。如果你有一本 10MB 文件,需要读 2 万次扇区。磁盘机械臂疯狂寻道,效率低。

  • 块是操作系统读写磁盘的最小单位。

  • 就像图书馆的“书箱”,借书还书都以“箱”为单位。

  • 这样一来,读取一个 10MB 的文件,只需搬运 2500 次(10MB / 4KB),效率直接提升 8 倍!

“分区”

假设你有一块 1TB 的硬盘,如果把它当成一个大整体来管理,所有的文件、系统备份、用户数据全混在一起。一旦系统崩溃,重装系统可能会把数据全毁掉;而且寻找文件时,面对 1TB 的空间,头都要炸了。

【引入分区】
就像一个大图书馆,如果只有一个大厅,找书太累了。
管理员决定用承重墙把大厅隔开,分成:科技区(C盘)、历史区(D盘)。

  • 分区,就是把一个大硬盘切成几个逻辑上独立的区域。

  • 每个分区可以单独格式化、单独装系统、单独管理。

  • 操作系统只需要分别管理这几个独立的小区域,复杂度大幅降低。

iNode

在分区里,数据已经被打散成一个个“块”了。假如你写了一个 100KB 的 C 语言代码文件main.c,它会被切成 25 个 4KB 的块,散落在硬盘的各个角落。
操作系统怎么知道这 25 个块属于main.c呢?

引入 inode】
在图书馆里,为了找到一本书,你必须查“图书检索卡”。

  • 这张卡片上写着:书名、作者、出版时间(文件的属性)。

  • 最重要的是,卡片上写着:这本书分别放在了第几排、第几个书架的哪几个箱子里(指向文件数据块的指针)。

这张“图书检索卡”,在 Linux 中就叫inode(索引节点)。

  • 一个文件对应唯一的一个 inode。

  • inode 存储了文件的所有属性(大小、权限、创建时间等),但唯独没有文件名。

  • inode 内部有一个指针数组,指向了存储该文件内容的那些数据块(Block)。

块组内部

Super Block(超级块)

struct ext2_super_block { __le32 s_inodes_count; // 整个分区有多少个 inode __le32 s_blocks_count; // 整个分区有多少个 4KB 块 __le32 s_free_inodes_count; // 还剩多少空闲 inode __le32 s_free_blocks_count; // 还剩多少空闲数据块 __le32 s_log_block_size; // 块大小(比如 4KB) // ... };

详细解释:
如果你格式化了一个 300GB 的分区,Super Block 里就记着:“本分区总共有 7864 万个数据块,总共有 196 万个 inode,目前全都是空的。”
协作情况:它只管全局宏观数据。如果有进程想创建一个文件,内核会先看一眼 Super Block,确认“嗯,还有空闲的 inode 和块”,才允许创建。

格式化是在磁盘上写入管理信息。

格式化步骤

1. 划分 Block Group 把 300GB 切成一块块的区域(比如每个区 128MB),划分为 Group 0, Group 1, Group 2... 2. 建立“总账本”(写入 Super Block) 在 Group 0 的开头,写入 Super Block。记录:“本分区总共有 7864 万个 4KB 块,有 196 万个 inode。块大小是 4KB。”等信息。 3. 建立“索引目录”(写入 GDT) 写入 Group Descriptor Table,告诉系统:Group 0 的位图在哪个块,Group 1 的位图在哪个块…… 4. 清空“状态位图”(初始化 Bitmap) 把 Block Bitmap 和 Inode Bitmap 全部用 0 填满。 全是 0 代表什么?代表所有数据块都是空的,所有 inode 也都是空闲的。 5. 创建“根目录” 在 Inode Table 里分配 1 号 inode 给根目录 /,并在某个数据块里写入根目录的初始内容(通常是 . 和 ..)。

GDT(Group Descriptor Table)

struct ext2_group_desc { __le32 bg_block_bitmap; // 块位图在这个块组的哪个块? __le32 bg_inode_bitmap; // inode位图在这个块组的哪个块? __le32 bg_inode_table; // inode表在这个块组的哪个块? __le16 bg_free_blocks_count; // 这个块组还剩多少空闲块? __le16 bg_free_inodes_count; // 这个块组还剩多少空闲inode? // ... };

详细解释:
因为一个大分区被切成了很多个 Block Group,GDT 就是用来管理这些 Block Group 的。它相当于一个“目录”,告诉你 Block Group 0 的位图在哪、Block Group 1 的位图在哪。
协作情况:当系统要在某个组找 inode 时,先查 GDT,瞬间定位到那个组的 Inode Table 起始位置。

Block Bitmap(块位图)

内部结构:
它根本不是一个复杂的结构体,它就是一段连续的字节(byte)。每一个比特位(bit)代表一个数据块(Block)。

  • 0代表该数据块是空闲的。

  • 1代表该数据块已经被占用了。

详细解释:
假设本块组的 Block Bitmap 是0000 0001(从右往左看),说明 0号到6号块是空的,7号块被占了。
协作情况:当你要往文件里写数据(比如追加 4KB),文件系统扫描这串 0101 的位图,找到第一个为0的 bit,把它改成1,就说明这个块被分配给你了。不仅分配快,还省空间(1个字节就能管8个块)。

Inode Bitmap(inode 位图)

内部结构:
和块位图一模一样,也是一串连续的字节。每一个比特位(bit)代表一个 inode。

  • 0代表这个 inode 号码还没人用。

  • 1代表这个 inode 已经被分配出去了。

详细解释:
Inode Table 是一个大数组(比如能容纳 1024 个 inode),Inode Bitmap 就是用来标记这 1024 个 inode 哪些被用掉了。
协作情况:当你touch abc新建文件时,系统查 Inode Bitmap,找到第一个为0的 bit,把它标记为1,并给你分配这个 inode 号(比如 263466)。

Inode Table(索引节点表)

struct ext2_inode { __le16 i_mode; // 文件类型和权限 (比如 0644) __le16 i_uid; // 所有者ID __le32 i_size; // 文件大小(字节) __le32 i_atime; // 访问时间 __le32 i_mtime; // 修改时间 __le32 i_links_count; // 硬链接数 __le32 i_block[15]; // 核心!记录文件数据存放在哪些 Block 里 // ... };

详细解释:
这就是我们前面反复提的inode。在 4KB 的数据块里,一个 128 字节的 inode 可以塞下 32 个。所以一个块组里的 Inode Table 可能占据了成百上千个数据块,里面密密麻麻全是 inode。
协作情况:一旦分配了 inode 号,系统就会在 Inode Table 里找到这个结构体,把文件大小、权限、以及真正的数据存在哪几个块(i_block数组),全部记进去。

Data Blocks(数据块)

内部结构:
没有特定结构,就是一块 4KB 的空白内存。存什么完全取决于你的文件。

  • 如果你存的是代码,这里面就是代码字符。

  • 如果你存的是目录,这里面存的就是{文件名, inode号}的列表(这就是前面说的“目录也是文件”)。

协作情况:Inode 里的i_block[15]记录着它们的编号。OS 拿着编号,直接去 Data Blocks 里读数据。

疑问

1、为什么super block单独放在Block group内部,而不是单独拿出来?

机械硬盘是极其脆弱的物理设备。如果一块磁道坏了,或者磁盘表面刮花了,那块区域的数据就彻底没了。 如果只放一份:假设你把 Super Block 单独放在分区的开头。一旦硬盘开头那几个扇区出现了物理坏道(这在早期硬盘中很常见),整个分区就直接瘫痪了,操作系统连这个分区有多大、有多少空闲空间都不知道,根本无法挂载。 冗余备份:Ext2 的设计者规定,每一个 Block Group 的开头都要包含一份 Super Block 的拷贝。而且,这些拷贝是保持同步的。 如果 Group 0 的 Super Block 所在的物理磁道坏了,操作系统会尝试去 Group 1 读取备份的 Super Block。 只要有一份备份存活,你的文件系统就还能被抢救回来。 (注:现代文件系统如 ext4 做了优化,虽然每个组都有备份空间,但为了节省空间,通常只在少数几个组里真正存储 Super Block 的完整拷贝,这叫稀疏备份。)

2、inode的编号在文件系统里面是唯一的吗

是的,inode 的编号在整个文件系统(也就是一个独立的分区)内是唯一的。

“inode和数据块,跨组编号的。所以,在同一个分区内部,inode编号和块号都是唯一的!”

  • inode 表是分开的:Block Group 0有自己的一段Inode Table,Block Group 1也有自己的一段Inode Table。

  • 编号是全局递增的:虽然它们分散在不同的组里,但文件系统给它们统一编了号。

    • 比如:Group 0的 inode 编号可能是1 ~ 1024。

    • Group 1的 inode 编号紧接其后,可能是1025 ~ 2048。

  • 计算公式:如果已知一个 inode 号,通过(inode号 - 1) / 每组inode数,就能算出它在哪个块组;通过(inode号 - 1) % 每组inode数,就能算出它是这个组里的第几个 inode。

怎么知道 inode 在哪一个分组里?

假设你在 Super Block 里查到:每个块组(Block Group)固定包含 8192 个 inode。
现在你手里拿着一个文件的 inode 编号:12345。

操作系统用脚趾头算一下就知道了:

分组号 = (inode 编号 - 1) ÷ 每组 inode 数
分组号 = (12345 - 1) ÷ 8192 = 12344 ÷ 8192 =1(取整)

结论:这个 inode 12345 存在Block Group 1里面。
(注:减 1 是因为 inode 编号是从 1 开始的,而数组下标是从 0 开始的)

问题二:怎么知道在分组中位图的哪一个位置?

既然知道了它在 Group 1,那它在 Group 1 的 Inode Bitmap 里,具体是第几个比特位(bit)呢?

接着用刚才的余数算:

组内偏移量 = (inode 编号 - 1) % 每组 inode 数
组内偏移量 = 12344 % 8192 =4152

结论:它的确切位置是 Group 1 的 Inode Bitmap 里的第4152个 bit。

进一步定位到具体结构体:

  • Inode Bitmap 里:第 4152 个 bit 肯定被标记为了1(表示已分配)。

  • Inode Table 里:系统直接在 Group 1 的 Inode Table 中,往后跳过 4151 个 inode 结构体(每个 128 字节),找到第 4152 个。这个结构体,就是你要找的文件的属性档案!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 2:30:10

冒泡排序从入门到精通:优化、递归与实战避坑指南

排序算法是每个学编程的人绕不开的第一道坎,而冒泡排序几乎毫无例外地成为这门课的起点。很多人对它的印象停留在"两个for循环、交换相邻元素、考试要背"这个层面,写完交作业就再也没碰过。但真正把冒泡排序吃透的人会发现,它其实是…

作者头像 李华
网站建设 2026/10/21 2:28:22

CF1870B Friendly Arrays

题目链接&#xff1a; Problem - 1870B - Codeforces 最佳思路&#xff1a; #include <iostream> #include <vector> using namespace std; int main() {int t;cin >> t;while (t--) {int n, m;cin >> n >> m;vector<int> a(n);unsigned…

作者头像 李华
网站建设 2026/10/21 2:27:47

空中机器人竞赛备赛利器:从仿真平台到真机迁移的完整指南

你们的队伍正在备战大学生机器人竞赛的空中机器人赛项&#xff1f;那大概率已经在真机上经历过几次“心碎时刻”&#xff1a;新调好的四旋翼第二次试飞就炸机&#xff0c;桨叶碎片弹到天花板上&#xff0c;剩下半架飞机只能躺在维修桌上。某高校的A同学跟我讲过&#xff0c;他们…

作者头像 李华
网站建设 2026/10/21 2:26:43

AGENTS.md 实战指南:如何让 AI 编程助手真正遵循项目规范

AGENTS.md 实战指南&#xff1a;如何让 AI 编程助手真正遵循项目规范 在使用 Cursor、Codex、Claude Code 等 AI 编程工具时&#xff0c;经常会遇到一个问题&#xff1a; AI 能写出功能正确的代码&#xff0c;却不一定能写出符合项目规范的代码。 例如&#xff0c;你的项目明明…

作者头像 李华
网站建设 2026/10/21 2:25:50

PHP安全漏洞修复指南:防SQL注入与XSS

一、为什么 PHP 项目总被盯上做 PHP 开发这些年&#xff0c;我见过太多项目上线后被黑的情况。说句实话&#xff0c;大部分漏洞并不是什么高深的技术&#xff0c;翻来覆去就是 SQL 注入和 XSS 这两类。很多新手写代码的时候图省事&#xff0c;直接把用户输入拼进 SQL 语句&…

作者头像 李华