📁 文件系统
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
💾 磁盘分配方式
连续分配 · 链接分配 · 索引分配 · 对比优缺点
连续分配: 文件占用连续的磁盘块。
优点: 支持顺序访问和随机访问 (直接算出块位置)
缺点: 外部碎片 、文件大小难以增长
适用: CD-ROM、DVD等只读介质
隐式链接分配: 每个块存下一个块的指针,形成链表。
优点: 无外部碎片、易于文件增长
缺点: 不支持随机访问 (必须遍历)、指针损坏则文件丢失
适用: 顺序访问的文件(如音频、视频流)
显式链接(FAT表): 把所有指针集中存放在FAT表 (File Allocation Table),每个磁盘块在FAT中有一个表项。
优点: 随机访问快(查FAT即可)、FAT可缓存
缺点: FAT占用额外空间(磁盘越大FAT越大)
经典应用: FAT32(Windows 95/98)、FAT16(DOS)
块号 0 1 2 3 4 5 6 7
FAT项 2 5 3 6 -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),访问会报错
✅ 优点: 可以跨文件系统、可以链接目录
❌ 缺点: 访问需要额外一次读软链接文件的操作(性能稍差)
📝 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()后,子进程会复制打开文件表吗?" → 子进程继承父进程的用户打开文件表 (即共享同一个系统打开文件表项),所以父子进程共享同一个文件的读写位置 !