Ext4 文件系统详解

Ext4 是 Linux 最常用的文件系统,提供了高性能、可靠性和可扩展性。

Ext4 架构

磁盘布局

┌─────────────────────────────────────────────────┐
│  Block 0: Boot Block                            │
├─────────────────────────────────────────────────┤
│  Block Group 0                                  │
│  ┌─────────────────────────────────────────┐  │
│  │ Super Block                              │  │
│  ├─────────────────────────────────────────┤  │
│  │ Group Descriptors                        │  │
│  ├─────────────────────────────────────────┤  │
│  │ Data Block Bitmap                        │  │
│  ├─────────────────────────────────────────┤  │
│  │ Inode Bitmap                             │  │
│  ├─────────────────────────────────────────┤  │
│  │ Inode Table                              │  │
│  ├─────────────────────────────────────────┤  │
│  │ Data Blocks                              │  │
│  └─────────────────────────────────────────┘  │
├─────────────────────────────────────────────────┤
│  Block Group 1                                  │
│  ...                                            │
├─────────────────────────────────────────────────┤
│  Block Group N                                  │
└─────────────────────────────────────────────────┘

超级块信息

# 查看超级块信息
sudo dumpe2fs /dev/sda1 | head -50

# 输出示例:
# Filesystem volume name: root
# Filesystem features: has_journal ext_attr resize_inode dir_index
# Filesystem state: clean
# Block count: 26214400
# Inode count: 6553600
# Block size: 4096
# Inode size: 256

inode 结构

Ext4 inode

// fs/ext4/ext4.h
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;        // 删除时间
    __le16  i_gid;          // 组 GID
    __le16  i_links_count;  // 硬链接数
    __le32  i_blocks_lo;    // 块数
    __le32  i_flags;        // 文件标志
    
    union {
        struct {
            __le32  l_i_version;
        } linux1;
        struct {
            __u32  h_i_translator;
        } hurd1;
    } osd1;
    
    __le32  i_block[EXT4_N_BLOCKS]; // 块指针
    __le32  i_generation;           // 文件版本
    __le32  i_file_acl_lo;          // 文件 ACL
    __le32  i_size_high;            // 文件大小(高 32 位)
    
    // ... 更多字段
};

// EXT4_N_BLOCKS = 15
// i_block[0-11]: 直接块指针
// i_block[12]: 一级间接块
// i_block[13]: 二级间接块
// i_block[14]: 三级间接块

inode 寻址

文件大小 vs 块指针:

块大小 4KB 时:
- 直接块:12 × 4KB = 48KB
- 一级间接:1024 × 4KB = 4MB
- 二级间接:1024 × 1024 × 4KB = 4GB
- 三级间接:1024 × 1024 × 1024 × 4KB = 4TB

最大文件大小:48KB + 4MB + 4GB + 4TB ≈ 4TB

Extent 树(Ext4 新特性)

传统间接块:
文件偏移 → 块号 → 块号 → ... → 数据

Extent 方式:
文件偏移 → (起始块, 长度) → 连续的数据块

优势:
- 减少元数据
- 提高大文件性能
- 减少碎片

查看 extent 信息

# 查看文件的 extent
debugfs -R "extents <inode>" /dev/sda1

# 或使用 filefrag
filefrag -v /path/to/file

日志(Journal)

日志模式

# 三种日志模式:

1. journal(最安全,最慢)
   - 元数据和数据都写入日志
   
2. ordered(默认,平衡)
   - 元数据写入日志
   - 数据先于元数据写入
   
3. writeback(最快,最不安全)
   - 元数据写入日志
   - 数据写入顺序不保证

# 查看当前日志模式
sudo tune2fs -l /dev/sda1 | grep features
# 或
mount | grep sda1

# 修改日志模式(挂载选项)
mount -o remount,data=ordered /

# 永久修改
# /etc/fstab
/dev/sda1  /  ext4  defaults,data=ordered  0  1

日志信息

# 查看日志状态
sudo dumpe2fs /dev/sda1 | grep -i journal

# 输出:
# Journal inode: 8
# Journal backup: inode blocks
# Journal features: journal_incompat_revoke journal_64bit
# Journal size: 128M
# Journal sequence: 0x00012345

目录索引

HTree(Hash Tree)

# 目录索引特性(dir_index)
# 使用哈希树加速大目录查找

# 查看是否启用
sudo tune2fs -l /dev/sda1 | grep dir_index

# 查看目录的 HTree 信息
debugfs -R "htree /path/to/dir" /dev/sda1

Ext4 特性

延迟分配(Delayed Allocation)

传统方式:
  写入数据 → 立即分配块 → 写入磁盘

延迟分配:
  写入数据 → 暂存内存 → 批量分配块 → 写入磁盘

优势:
- 减少碎片
- 提高性能
- 更好的块分配

多块分配(Multi-block Allocation)

# 一次分配多个连续块
# 提高大文件写入性能

# 查看块分配情况
stat /path/to/file | grep Blocks
filefrag -v /path/to/file

快速 fsck

# uninit_bg 特性
# 未使用的块组不需要检查

# 启用特性
sudo tune2fs -O uninit_bg /dev/sda1

# fsck 时间对比
time fsck.ext4 -n /dev/sda1

在线碎片整理

# Ext4 支持在线碎片整理
# 无需卸载文件系统

# 检查碎片程度
filefrag -v /path/to/file

# 整理单个文件
e4defrag /path/to/file

# 整理整个文件系统
e4defrag /mount/point

# 查看整理效果
e4defrag -c /mount/point

文件系统操作

创建文件系统

# 基本创建
mkfs.ext4 /dev/sdb1

# 指定参数
mkfs.ext4 \
  -b 4096 \              # 块大小
  -i 16384 \             # inode 比例
  -J size=128 \          # 日志大小(MB)
  -L mydata \            # 卷标
  -m 1 \                 # 保留块百分比
  -O ^has_journal \      # 禁用日志
  /dev/sdb1

# 查看详细信息
mkfs.ext4 -n /dev/sdb1  # 模拟运行

调整文件系统

# 查看文件系统信息
tune2fs -l /dev/sda1

# 设置卷标
tune2fs -L newlabel /dev/sda1

# 设置挂载次数检查
tune2fs -c 30 /dev/sda1      # 30 次挂载后检查
tune2fs -c -1 /dev/sda1      # 禁用次数检查

# 设置时间间隔检查
tune2fs -i 6m /dev/sda1      # 6 个月后检查
tune2fs -i 0 /dev/sda1       # 禁用时间检查

# 设置保留块
tune2fs -m 1 /dev/sda1       # 保留 1%

# 启用/禁用特性
tune2fs -O has_journal /dev/sda1
tune2fs -O ^has_journal /dev/sda1

扩展文件系统

# 在线扩展(Ext4 支持)

# 1. 扩展分区(使用 parted/fdisk)
parted /dev/sdb resizepart 1 100%

# 2. 通知内核分区变化
partprobe /dev/sdb

# 3. 扩展文件系统
resize2fs /dev/sdb1

# 离线扩展
umount /mount/point
e2fsck -f /dev/sdb1
resize2fs /dev/sdb1
mount /dev/sdb1 /mount/point

缩小文件系统

# Ext4 只能离线缩小

# 1. 卸载文件系统
umount /mount/point

# 2. 检查文件系统
e2fsck -f /dev/sdb1

# 3. 缩小到指定大小
resize2fs /dev/sdb1 50G

# 4. 缩小分区(使用 parted)
parted /dev/sdb resizepart 1 50G

# 5. 重新挂载
mount /dev/sdb1 /mount/point

文件系统检查和修复

fsck 工具

# 检查文件系统(只读,不修复)
fsck.ext4 -n /dev/sda1

# 自动修复错误
fsck.ext4 -y /dev/sda1

# 交互式修复
fsck.ext4 /dev/sda1

# 强制检查(即使标记为干净)
fsck.ext4 -f /dev/sda1

# 详细输出
fsck.ext4 -v /dev/sda1

# 检查坏块
fsck.ext4 -c /dev/sda1        # 只读测试
fsck.ext4 -cc /dev/sda1       # 读写测试(危险)

debugfs 调试

# 进入 debugfs
debugfs /dev/sda1

# 常用命令:
# stat <inode>          - 显示 inode 信息
# stat <filename>       - 显示文件信息
# ls                    - 列出目录
# cd <dir>              - 切换目录
# pwd                   - 显示当前目录
# cat <file>            - 显示文件内容
# dump <file> <output>  - 导出文件
# blocks <file>         - 显示文件块
# extents <inode>       - 显示 extent 信息

# 示例:恢复删除的文件
debugfs -w /dev/sda1
debugfs: lsdel
# 找到要恢复的 inode
debugfs: dump <inode> /tmp/recovered_file
debugfs: quit

性能优化

挂载选项优化

# /etc/fstab 优化选项

# noatime - 不更新访问时间
/dev/sda1  /  ext4  defaults,noatime  0  1

# nodiratime - 不更新目录访问时间
/dev/sda1  /  ext4  defaults,nodiratime  0  1

# commit=60 - 延长提交间隔(秒)
/dev/sda1  /  ext4  defaults,commit=60  0  1

# data=writeback - 最快的日志模式
/dev/sda1  /  ext4  defaults,data=writeback  0  1

# barrier=0 - 禁用写屏障(风险)
/dev/sda1  /  ext4  defaults,barrier=0  0  1

I/O 调度器

# 查看当前调度器
cat /sys/block/sda/queue/scheduler

# 设置调度器
echo deadline > /sys/block/sda/queue/scheduler
echo noop > /sys/block/sda/queue/scheduler
echo cfq > /sys/block/sda/queue/scheduler

# 永久设置(Grub)
# /etc/default/grub
GRUB_CMDLINE_LINUX="elevator=deadline"
update-grub

预读优化

# 查看预读大小(扇区数)
blockdev --getra /dev/sda

# 设置预读大小
blockdev --setra 4096 /dev/sda  # 2MB

# 临时测试效果
hdparm -t /dev/sda

监控和统计

iostat 监控

# 监控文件系统 I/O
iostat -x 1

# 监控特定设备
iostat -x sda 1

文件系统统计

# 查看 inode 使用
df -i

# 查看块使用
df -h

# 详细统计
dumpe2fs /dev/sda1 | grep -E "Block count|Free blocks|Inode count|Free inodes"

下一步:学习 磁盘 I/O 子系统 章节。