Skip to content

文件系统:FCB与索引节点

你有没有想过,当你保存一个文件时,这个文件到底存在了哪里? 操作系统是怎么知道这个文件有多长、叫什么、在磁盘的哪个位置?

答案就在两个核心概念里:FCB索引节点(inode)

从文件到磁盘:中间发生了什么?

┌──────────────────────────────────────────────────────────┐
│                    文件系统层次结构                        │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  用户程序                                               │
│     ↓                                                   │
│  系统调用(open, read, write, close)                     │
│     ↓                                                   │
│  虚拟文件系统(VFS)                                      │
│     ↓                                                   │
│  具体文件系统(Ext4, NTFS, FAT32)                        │
│     ↓                                                   │
│  块设备层(磁盘控制器)                                    │
│     ↓                                                   │
│  物理磁盘(扇区)                                        │
│                                                          │
└──────────────────────────────────────────────────────────┘

FCB:文件控制块

FCB(File Control Block)是文件系统中描述一个文件的数据结构。 每个文件都有一个FCB,包含文件的所有元数据。

┌──────────────────────────────────────────────────────────┐
│                    FCB结构(典型)                        │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  ┌──────────────────────────────────────────────────┐   │
│  │ 文件名        │ 文件类型       │ 访问权限           │   │
│  ├──────────────────────────────────────────────────┤   │
│  │ 文件大小      │ 创建时间       │ 修改时间           │   │
│  ├──────────────────────────────────────────────────┤   │
│  │ 所有者ID     │ 所属组ID      │ 链接数             │   │
│  ├──────────────────────────────────────────────────┤   │
│  │ 文件数据位置  │ (磁盘块号列表) │                   │   │
│  ├──────────────────────────────────────────────────┤   │
│  │  文件状态     │  引用计数      │                   │   │
│  └──────────────────────────────────────────────────┘   │
│                                                          │
└──────────────────────────────────────────────────────────┘

目录项(Directory Entry)

FCB太大了,不能直接放在目录里。所以目录里放的是目录项,是FCB的简化版。

┌──────────────────────────────────────────────────────────┐
│                    目录项结构                             │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  Linux (Ext2/3/4):                                     │
│  ┌──────────────────────────────┐                       │
│  │ inode编号 (4字节)             │ 文件名 (可变长)         │   │
│  └──────────────────────────────┘                       │
│                                                          │
│  FAT32:                                                │
│  ┌──────────────────────────────┐                       │
│  │ 文件名 (8+3字节)             │ FCB起始簇号            │   │
│  └──────────────────────────────┘                       │
│                                                          │
│  NTFS:                                                 │
│  ┌──────────────────────────────────────────────┐      │
│  │ 文件名 (Unicode)           │ MFT记录号             │   │
│  └──────────────────────────────────────────────┘      │
│                                                          │
└──────────────────────────────────────────────────────────┘

inode:Unix的革命性设计

inode(Index Node)是Unix/Linux文件系统的心脏。 它解决了FCB的直接访问问题——目录项只需要存储inode编号。

┌──────────────────────────────────────────────────────────┐
│                    inode结构(Ext4)                      │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  ┌──────────────────────────────────────────────────┐   │
│  │ mode (文件类型 + 权限)                           │   │
│  ├──────────────────────────────────────────────────┤   │
│  │ uid (所有者用户ID)     │ size (文件大小)           │   │
│  ├──────────────────────────────────────────────────┤   │
│  │ atime (访问时间)       │ mtime (修改时间)          │   │
│  ├──────────────────────────────────────────────────┤   │
│  │ ctime (属性改变时间)   │ dtime (删除时间)          │   │
│  ├──────────────────────────────────────────────────┤   │
│  │ gid (所属组ID)        │ links_count (链接数)       │   │
│  ├──────────────────────────────────────────────────┤   │
│  │ blocks (占用块数)      │ flags (Ext4特性)          │   │
│  ├──────────────────────────────────────────────────┤   │
│  │ 15个块指针:                                    │   │
│  │   12个直接块                                  │   │
│  │   1个一级间接块                               │   │
│  │   1个二级间接块                               │   │
│  │   1个三级间接块                               │   │
│  └──────────────────────────────────────────────────┘   │
│                                                          │
└──────────────────────────────────────────────────────────┘

inode如何定位文件数据?

inode中的15个块指针:

┌──────────────────────────────────────────────────────────┐
│                                                          │
│  直接块指针(12个):                                        │
│  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐               │
│  │D0│D1│D2│D3│D4│D5│D6│D7│D8│D9│D10│D11│              │
│  └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘               │
│  每个指向一个磁盘数据块                                     │
│                                                          │
│  一级间接块(1个):                                        │
│  ┌─────────────────────┐                                │
│  │  间接块指针表        │ ──→ [P0][P1][P2]...[P255]   │
│  └─────────────────────┘        ↓                         │
│                            数据块D                        │
│                                                          │
│  二级间接块(1个):                                        │
│  ┌─────────────────────┐                                │
│  │  二级间接指针表      │ ──→ [I1][I2]...[I255]        │
│  └─────────────────────┘      ↓                         │
│                         ┌─────────────────────┐          │
│                         │  一级间接指针表      │          │
│                         └─────────────────────┘          │
│                                  ↓                        │
│                            [P0][P1]...[P255]            │
│                                  ↓                        │
│                               数据块D                     │
│                                                          │
│  三级间接块(1个):                                        │
│  ┌─────────────────────┐                                │
│  │  三级间接指针表      │                                │
│  └─────────────────────┘                                │
│       ↓                                                   │
│  指向多个二级间接块                                        │
│       ↓                                                   │
│  指向多个一级间接块                                        │
│       ↓                                                   │
│  指向多个数据块                                            │
│                                                          │
└──────────────────────────────────────────────────────────┘

inode的最大文件大小

计算方式(假设块大小4KB):

直接块: 12 × 4KB = 48KB

一级间接: 256 × 4KB = 1MB
(256个指针,每个4字节)

二级间接: 256² × 4KB = 256MB
(256 × 256个指针)

三级间接: 256³ × 4KB = 64GB

总最大文件大小 ≈ 64GB + 256MB + 1MB + 48KB ≈ 64.26GB

硬链接 vs 软链接

java
// 硬链接:多个目录项指向同一个inode
// 特点:
// - 不能跨文件系统
// - 不能链接目录
// - 删除只是减少链接数
// - 所有链接地位相同

public class HardLink {
    public static void main(String[] args) throws IOException {
        // 创建硬链接
        // ln source.txt hardlink.txt
        // 两个文件名指向同一个inode
        // inode中的links_count = 2
    }
}
java
// 软链接(符号链接):一种特殊文件,内容是另一个文件的路径
// 特点:
// - 可以跨文件系统
// - 可以链接目录
// - 删除原文件,软链接失效
// - 类似Windows快捷方式

public class SoftLink {
    public static void main(String[] args) throws IOException {
        // 创建软链接
        // ln -s source.txt softlink.txt
        // softlink.txt的内容是 "source.txt"
    }
}

实际案例:Ext4文件系统

c
// Ext4的inode结构
struct ext4_inode {
    __le16  i_mode;         // 文件模式(类型+权限)
    __le16  i_uid;          // UID
    __le32  i_size_lo;      // 低32位文件大小
    __le32  i_atime;        // 访问时间
    __le32  i_ctime;        // 创建时间
    __le32  i_mtime;        // 修改时间
    __le32  i_dtime;        // 删除时间
    __le32  i_gid;          // GID
    __le16  i_links_count;  // 硬链接计数
    __le32  i_blocks_lo;    // 占用的块数(512字节单位)
    __le32  i_flags;        // 文件标志
    __le32  i_osd1;         // OS相关
    __le32  i_block[15];    // 15个块指针!
    __le32  i_generation;   // 文件版本(用于NFS)
    __le32  i_file_acl_lo;  // ACL
    __le32  i_size_high;    // 高32位文件大小
    __le32  i_obso_faddr;   // 废弃
    // ...
};

Ext4的改进:

  • Extent存储:用区间代替块指针,减少元数据开销
  • 延迟分配:优化写入性能
  • 日志(Journal):保证文件系统一致性

面试追问方向

  • FCB和inode的区别是什么? 提示:FCB在内存,inode在磁盘。
  • inode如何定位大文件的数据块? 提示:直接块、一级/二级/三级间接块。
  • 硬链接和软链接的区别是什么? 提示:是否共享inode、能否跨文件系统、能否链接目录。
  • 为什么目录也是一个文件? 提示:目录内容是目录项的列表。

基于 VitePress 构建