前几章的文件系统是存在内存里的——重启数据就没了,文件名也是硬编码的。这一章做真实的:挂载一个 ext2 磁盘镜像,让 Shell 能读取里面的文件。

上一章已经有了 ATA 驱动,能按扇区号读磁盘。现在的问题是:磁盘上的数据是怎么组织的?


ext2 的结构

ext2 是 Linux 最经典的文件系统,ext3/ext4 都是在它基础上演化来的。理解 ext2,基本上就理解了现代文件系统的核心思路。

磁盘从偏移 1024 字节开始是 Superblock,存整个文件系统的基本参数:block 大小是多少、有多少 inode、magic number 是 0xEF53(用来确认这确实是 ext2)。

接下来是 Group Descriptor,告诉你 inode table 在哪个 block。

然后才是真正的数据区:inode table 和数据块。

Inode 是文件的"身份证"。每个文件有一个唯一的 inode 号,inode 里存着文件大小、权限,以及最重要的——i_block[0..11],12 个直接指向数据块的指针。想读文件内容,就顺着这些指针去读对应的数据块。

目录也是文件,它的数据块里存的是一条条 ext2_dir_entry:每条记录包含 inode 号、文件名长度、文件名。ls 就是读根目录的数据块,遍历这些记录。


读文件的完整路径

Superblock  → 拿到 block_size、inodes_per_group
GroupDesc   → 拿到 inode table 的起始 block 号
Inode[ino]  → 拿到文件大小和 i_block[]
DataBlock   → 真正的文件内容

读目录(ls)就在最后一步多做一件事:把数据块里的 ext2_dir_entry 链遍历一遍。

每一步都需要从磁盘读若干个扇区——这正是上一章 ATA 驱动的用武之地。


和 VFS 对接

实现好读 inode、读数据块、按名字查目录这几个函数后,把它们包成一套 FileOps 注册进 VFS:

static FileOps ext2_fops = {
    ext2_read,    // 按 ino+offset 读数据
    ext2_write,   // 只读实现,返回 -1
    ext2_lookup,  // 目录里按名字找 ino
    ext2_create,  // 只读实现,返回 0
};
if (ext2_init() == 0) {
    serial_print("ext2 mounted!\r\n");
    vfs_init(ext2_get_root_ino(), ext2_get_fops());
}

Shell 的 readls 命令一行不用改,透过 VFS 层直接访问 ext2。这就是抽象层的价值。


跑起来

make ext2.img   # 创建 1.4MB ext2 镜像,写入 hello.txt
make run
ext2 mounted!

> ls
hello.txt
readme.txt

> read hello.txt
Hello from ext2!

从 ATA 端口读原始扇区,解析 ext2 结构,通过 VFS,Shell 打印出文件内容——整条链路全通。


踩坑:选盘选错了

跑起来第一件事:General Protection Fault,rip=0xf000ff54f000ff53——这是 BIOS ROM 的地址段。

原因:ATA 驱动选的是 master(0xE0),但 ext2.img 挂的是 slave(第二块盘)。读到了 myos.img 的内容,superblock magic 对不上,后续解析出垃圾地址,CPU 跑进 BIOS 区域触发 GPF。

0x1F6 的 bit4 决定读哪块盘:0xE0 是 master,0xF0 是 slave。把这一位改对,一行修复。


下一章

有了真实文件系统,下一章做 ELF 加载器——从 ext2 读取可执行文件,解析 ELF 格式,加载到内存里运行。

源码:github.com/tongpengfei/learn-with-ai