📁 文件系统

inode结构 · 磁盘分配方式 · 磁盘调度算法 · 目录结构
目录结构 inode与寻址 磁盘分配方式 磁盘调度
📂 目录结构
树形目录 · 目录项 · 路径表示
目录结构类型:

单级目录:所有文件在同一个目录(早期系统)
两级目录:系统目录 + 用户目录(隔离用户)
树形目录:多级嵌套,现代OS主流
无环图目录:允许共享文件(硬链接),需垃圾回收

路径表示:
· 绝对路径:从根目录开始 /home/user/file.txt
· 相对路径:从当前目录开始 ../doc/
📂 树形目录示例
/ 根目录
📂 bin 系统命令
📂 home
📂 alice
📄 readme.txt
📄 code.c
📂 bob
📂 usr
📂 var
📄 = 普通文件   📂 = 目录文件
🗃️ inode与文件寻址
inode存储文件元数据 · 多级索引支持大文件
inode(索引结点)内容:
· 文件类型(普通/目录/设备)
· 文件权限(rwx)
· 文件大小(字节)
· 文件创建/修改/访问时间
· 链接计数(硬链接数)
· 磁盘地址(寻址信息)← 关键!

寻址结构(UNIX):
· 12个直接地址块(每个块4KB)
· 1个一级间接块(指向一个索引块)
· 1个二级间接
· 1个三级间接
📊 最大文件大小计算(例):
块大小=4KB,地址=4B → 每块可存 4K/4 = 1024个地址

直接:12 × 4KB = 48KB
一级间接:1024 × 4KB = 4MB
二级间接:1024² × 4KB = 4GB
三级间接:1024³ × 4KB = 4TB
合计:4TB + 4GB + 4MB + 48KB
inode寻址结构图
inode
直接地址[0] → 块12
直接地址[1] → 块45
...
直接地址[11] → 块78
一级间接 → 块200
二级间接 → 块512
三级间接 → 块2048
一级间接块(块200)
→块100
→块101
...
→块1127
💾 磁盘分配方式
连续分配 · 链接分配 · 索引分配 · 对比优缺点
连续分配
隐式链接
显式链接(FAT)
索引分配
连续分配:文件占用连续的磁盘块。

优点:支持顺序访问和随机访问(直接算出块位置)
缺点:外部碎片、文件大小难以增长

适用:CD-ROM、DVD等只读介质
隐式链接分配:每个块存下一个块的指针,形成链表。

优点:无外部碎片、易于文件增长
缺点:不支持随机访问(必须遍历)、指针损坏则文件丢失

适用:顺序访问的文件(如音频、视频流)
显式链接(FAT表):把所有指针集中存放在FAT表(File Allocation Table),每个磁盘块在FAT中有一个表项。

优点:随机访问快(查FAT即可)、FAT可缓存
缺点:FAT占用额外空间(磁盘越大FAT越大)

经典应用:FAT32(Windows 95/98)、FAT16(DOS)
块号01234567
FAT项2536-1(结束)7-1-1空闲
含义文件A第1块文件A第2块文件A第3块文件A第4块文件A结束文件B第1块文件B结束文件C空闲
索引分配:为每个文件创建一个索引块,里面存放该文件所有块的地址。

优点:支持随机访问、无外部碎片
缺点:小文件也需一个索引块(开销);大文件需要多级索引

经典应用:UNIX inode(直接+间接索引混合)
索引块示例(文件A)
索引[0]
→块9
索引[1]
→块2
索引[2]
→块18
索引[3]
→块5
...
� disc 磁盘调度算法
寻道时间是磁盘I/O的主要开销 · 对比四种算法
FCFS
SSTF
SCAN(电梯)
C-SCAN
算法对比
📃 文件逻辑结构(文件在用户眼中的组织方式)
文件按逻辑结构分为三类:顺序文件、索引文件、索引顺序文件
📖 为什么需要逻辑结构?
用户关心的是"文件里的内容如何组织"(如:是顺序的一条条记录,还是带索引的快速查找?),而不是"文件在磁盘上如何存放"(那是物理结构/磁盘分配方式)。
① 顺序文件(Sequential File)
文件中的记录按顺序排列,一条接一条。

✅ 优点:简单、适合批量读写(磁带/流式文件)
❌ 缺点:查找慢(只能顺序查找)、增删记录麻烦
典型:日志文件、早期的磁带文件系统
② 索引文件(Indexed File)
为文件建立一张索引表(记录号→磁盘块号),通过索引表快速查找。

✅ 优点:查找快(O(1))、支持变长记录
❌ 缺点:需要额外空间存索引表
典型:数据库索引、现代文件系统
③ 索引顺序文件(Indexed Sequential File)
先按关键字排序(顺序文件),再为每组建立索引(索引文件)。

✅ 优点:兼顾顺序访问和随机访问
❌ 缺点:维护索引有开销
典型:大型数据库、ISAM索引
📝 408考点:
① "顺序文件的缺点?" → 查找慢、增删麻烦
② "索引文件的索引表存放在哪里?" → 存放在磁盘的专用块中(不是跟文件数据混在一起)
③ "索引顺序文件适用于什么场景?" → 既要顺序访问,又要随机访问的大型文件
🔗 文件共享(硬链接 vs 软链接/符号链接)
多个进程如何共享同一个文件?两种方式对比 + 动画演示
📖 文件共享的两种方式:
· 硬链接(Hard Link):多个目录项指向同一个inode(同一个文件)
· 软链接(Symbolic Link / 符号链接):创建一个新文件,内容是被链接文件的路径名
硬链接(Hard Link)
原理:多个文件名指向同一个inode
inode链接计数:每建一个硬链接,inode的链接计数+1
删除文件:只有链接计数=0时,才真正删除文件数据

✅ 优点:真正共享(修改同步)、删除原文件不影响硬链接
❌ 缺点:不能跨文件系统(inode号只在同一个文件系统内唯一)、不能链接目录(防止环)
软链接(Symbolic Link / 符号链接)
原理:创建一个新文件(类型为"链接文件"),内容是被链接文件的路径名
inode链接计数:不增加(软链接有自己的inode)
删除原文件:软链接变成悬空链接(dangling link),访问会报错

✅ 优点:可以跨文件系统、可以链接目录
❌ 缺点:访问需要额外一次读软链接文件的操作(性能稍差)
▲ 硬链接(左)vs 软链接(右)原理示意图
📝 408高频考点:
① "硬链接和软链接的区别?" → 硬链接共享inode;软链接有自己的inode,存路径名
② "能不能对目录创建硬链接?" → 不能(防止目录树出现环,只能用软链接)
③ "删除原文件后,硬链接/软链接还能访问吗?" → 硬链接(链接计数>0);软链接不能(悬空链接)
💾 存储空间管理(成组链接法 —— UNIX文件系统经典算法)
管理磁盘空闲块的方式:位图法、链接法、成组链接法(UNIX专用)
📖 为什么要管理存储空间?
文件系统需要记录磁盘上哪些块是空闲的(可以分配给新文件),哪些是已分配的。这就是空闲空间管理
① 位图法(Bitmap)
一位表示一个磁盘块的空闲/占用状态。

✅ 优点:简单、容易找到连续空闲块
❌ 缺点:位图本身需要占用存储空间(磁盘很大时,位图也很大)
典型:Windows NTFS的$Bitmap文件
② 链接法(Linked List)
把所有空闲块链接成一个链表。

✅ 优点:不需要额外空间(空闲块自己存下一个空闲块指针)
❌ 缺点:可靠性差(一个指针坏,后续都找不到)、随机访问慢
典型:FAT文件系统(文件分配表)
③ 成组链接法(Grouped Linkage)
UNIX文件系统的经典算法:把空闲块分组,每组n个,组之间用链接串联。

✅ 优点:不需要额外空间、分配/回收效率高
典型:UNIX文件系统(FFS)的空闲块管理
▲ 成组链接法(UNIX)原理:分组链接,第一组在内存专用栈中
📝 408综合题考点(成组链接法):
① "成组链接法的第一组空闲块号存放在哪里?" → 存放在内存专用栈中(加速分配)
② "分配一个空闲块的过程?" → 从专用栈弹出一个块号;如果弹出的是这一组的最后一个,则把该块内容(下一组的块号和块号列表)读入专用栈
③ "回收一个空闲块的过程?" → 如果专用栈没满,直接压栈;如果满了(这一组已经100个块),则把专用栈内容写入新回收的块,然后把这个块作为新组的第一个块压栈
🌐 VFS虚拟文件系统(Virtual File System)
屏蔽底层文件系统差异,让上层用同一套接口访问不同文件系统
📖 为什么需要VFS?
现代操作系统支持多种文件系统(如Linux支持ext4、XFS、Btrfs、NTFS、FAT32等)。如果让应用程序针对每种文件系统都写一套代码,那太麻烦了!VFS就是来解决这个问题——提供统一的抽象接口
▲ VFS层次结构:应用程序 → VFS → 具体文件系统 → 磁盘
VFS的四个主要抽象对象(408了解即可)
① super_block(超级块):存储文件系统的元信息(块大小、总块数、空闲块数等)
② inode(索引结点):存储文件元信息(文件大小、权限、磁盘地址等)—— 前面详细讲过
③ dentry(目录项):存储目录和文件的链接关系(父目录→子目录/文件的映射)
④ file(打开文件):存储进程打开文件的状态(当前读写位置、访问模式等)
📝 408考点:
① "VFS的作用?" → 屏蔽底层文件系统差异,提供统一接口
② "VFS和具体文件系统的关系?" → VFS是抽象层,具体文件系统实现VFS定义的接口
③ "VFS中的dentry是什么?" → 目录项缓存,加速路径名查找
📂 打开文件表(系统级 vs 用户级)
文件被打开后,操作系统如何管理?两级打开文件表结构
📖 为什么要打开文件表?
当进程调用 open() 打开一个文件时,操作系统需要记录这个文件的状态信息(当前读写位置、访问模式等)。这些信息的组织结构就是打开文件表
▲ 两级打开文件表:系统打开文件表(整个系统一份)vs 用户打开文件表(每个进程一份)
系统打开文件表(System Open File Table)
范围:整个系统一份(所有进程共享)
内容:
· 文件inode指针(指向文件的inode)
· 文件被打开次数(计数器,关闭时-1,到0才真正关闭)
· 文件访问模式(只读/只写/读写)
· 当前文件偏移量(但现代系统通常放在用户打开文件表里)

典型:Linux的 struct file
用户打开文件表(Per-Process Open File Table)
范围:每个进程一份(进程私有)
内容:
· 系统打开文件表的指针(指向系统表中的对应项)
· 当前文件偏移量(进程自己的读写位置,父子进程可以不同)

✅ 好处:多个进程可以共享同一个文件,但各自的读写位置独立
典型:Linux的 task_struct.files(文件描述符表)
📝 408高频考点:
① "打开文件表为什么分两级?" → 支持文件共享(多个进程打开同一个文件,共享系统级表项;但各自的读写位置独立,存在用户级表项中)
② "文件描述符是什么?" → 用户打开文件表的索引(0=标准输入,1=标准输出,2=标准错误)
③ "fork()后,子进程会复制打开文件表吗?" → 子进程继承父进程的用户打开文件表(即共享同一个系统打开文件表项),所以父子进程共享同一个文件的读写位置