文件管理

← 操作系统知识地图


核心概念:FCB 与 inode

FCB(File Control Block) 是 OS 用来描述一个文件的控制块。Unix 里叫 inode,Windows 里嵌在目录项或 MFT 中。每个文件都有唯一的 FCB/inode。

磁盘上的 FCB/inode 至少包含:

字段含义
文件类型与权限普通文件/目录/设备,rwx
文件大小字节数
时间戳创建/最后访问/最后修改时间
硬链接计数指向本 inode 的文件名数量
数据块指针文件的物理块号(直接块 / 一级间接 / 二级间接)

关键:FCB/inode 里不存文件名——文件名存在目录里。

打开文件:两条路径

每级路径分量都先查 dentry 缓存,命中则跳过读磁盘目录,未命中则从磁盘加载目录内容并建 dentry。

open("/docs/note.txt") — 假设全部 dentry 未命中(最慢路径):

① "/"
   dentry 缓存未命中 → 读 "/" 的目录磁盘块
   → 找到 ("docs", inode=512) → 建 dentry("/", docs) 写入缓存

② "docs"
   dentry 缓存未命中 → 读 docs 的目录磁盘块(inode 512 先载入内存)
   → 找到 ("note.txt", inode=1024) → 建 dentry("docs", note.txt) 写入缓存

③ "note.txt"
   dentry 缓存未命中 → inode 1024 从磁盘载入内存 inode 缓存
   → 是普通文件 → 建 dentry("note.txt", inode 1024)

④ 建立进程侧访问结构
   系统打开文件表: 偏移=0, O_RDONLY → inode 1024
   进程 fd 表:     fd=3 → 系统表条目
   返回 3


再次 open("/docs/note.txt") — dentry 全部命中(最快路径):

① "/"       → dentry 命中 → 直接拿到 inode,跳过读目录磁盘
② "docs"    → dentry 命中 → 直接拿到 inode 512
③ "note.txt"→ dentry 命中 → 直接拿到 inode 1024
④ 同上,返回 fd
结构位置存什么何时创建
目录磁盘数据区(文件名, inode号)创建文件/目录时写入
inode磁盘 inode 区 → 内存 inode 缓存属性 + 数据块指针文件创建时写磁盘,访问时载入内存
dentry仅内存文件名 + 内存 inode 指针 + 父子关系路径解析时创建,持续缓存
打开文件表仅内存偏移量 + 打开模式 + 指向 inodeopen 创建,close 销毁
fd 表仅内存(每进程一张)fd → 打开文件表条目open 创建,close 销毁

文件共享

类型原理删除源文件后
硬链接多个文件名指向同一个 inode,inode 里记录链接计数文件内容仍在(计数 > 0 就还在)
软链接单独新建一个文件,内容是目标路径字符串软链接失效(断链)

一句话:硬链接连 inode,软链接连路径。

访问控制

基本模型:rwx × 所有者-组-其他人

实际检查顺序:进程访问文件时,OS 先拿进程的 uid/gid 和文件的 inode 权限位对比,通过才放行。

文件系统布局

磁盘上的区域

区域内容
引导块启动引导代码,通常放卷开头
超级块文件系统全局信息:块大小、总块数、空闲块数、inode 总数
inode 区所有 inode(FCB),每个文件一个
数据区文件内容 + 目录内容(目录的”数据”就是文件名→inode表)

文件的物理分配方式

数据区的文件块如何分布:

方式原理优点缺点
连续分配文件占一串连续磁盘块顺序读写快外部碎片,文件拓展困难
链接分配无外部碎片,一个文件可以拆开链接存储开销大
└ 隐式链接每块末尾存下一块指针,链在数据块里无外部碎片,易拓展随机访问差:找第 N 块得把前 N-1 块都读一遍
└ 显式链接(FAT)链接指针集中存一张文件分配表(FAT),链在内存里随机访问快(FAT 常驻内存,跟链条不读数据块)FAT 表本身占内存
索引分配一个索引块集中存所有数据块号随机访问好索引块本身也占空间

现代文件系统普遍用索引分配(ext4 多级索引、NTFS MFT 运行列表)。

内存中

结构内容
挂载表已挂载文件系统的超级块副本
inode 缓存正在使用的 inode 副本
dentry 缓存路径分量 → inode 的映射,加速路径解析
打开文件表当前系统内所有打开的文件及其读写位置

空闲空间管理

方法原理适合场景
空闲表法记录 (起始块号, 长度),类似动态分区连续分配
空闲链表法把所有空闲块串成链表简单但效率低
位示图法每个块对应 1 bit:0 空闲 / 1 已用现代主流,易找连续空闲块
成组链接法把连续空闲块号分组,一组存下一组指针,批量出入Unix 传统做法

虚拟文件系统(VFS)

为什么需要 VFS

不同文件系统的物理存储方式完全不同。以三种常见文件系统为例:

文件系统文件定位方式怎么找到数据块
ext4inode + 多级索引inode 里存 12 个直接块 + 1 个一级间接块 + 1 个二级间接块 + 1 个三级间接块
FAT32文件分配表(FAT)链目录项存首簇号,FAT 表中每个簇指向下一个簇,跟着链条找到所有簇
NTFSMFT + 运行列表MFT 记录里存 (起始簇号, 连续簇数) 的运行列表,直接描述一段段连续区域

如果每个文件系统都要应用程序自己适配,open 一个 ext4 文件和 open 一个 FAT32 U 盘就得写两套代码。

VFS 的解决方案:在系统调用和具体文件系统之间加一个抽象层:

应用程序
  ↓ open/read/write/close(统一接口,只认 fd)
VFS 层(把调用分发到对应文件系统)
  ↓          ↓           ↓
ext4 驱动  FAT32 驱动  NTFS 驱动
  ↓          ↓           ↓
[ext4 盘]  [FAT32 U盘]  [NTFS 盘]

应用程序只管调 read(fd, buf, n),VFS 根据 fd 指向的文件所属文件系统,把调用翻译成 ext4_read()fat32_read()ntfs_read()

VFS 四个核心对象

对象含义存在位置
super_block代表一个已挂载的文件系统实例磁盘 → 内存挂载表
inode (Vnode)代表一个文件的属性 + 数据位置磁盘 inode 区 → 内存 inode 缓存
dentry目录项,关联一个路径分量到 inode仅内存,解析时创建
file代表文件在一次打开中的状态(偏移量、打开模式)仅内存,进程打开文件表