初探 FAT12:引导扇区

字数: 1363

从保护模式走来,现在开始一步步深入操作系统了。从最简单的 512 字节的引导扇区到现在引入文件系统,从最简单的文件系统 fat12 入手,一步步揭开文件系统的面纱。

创建一个 FAT12 的软盘

使用 dd 创建一个空软盘:

1
dd if=/dev/zero of=floppy.img bs=512 count=2880

格式化成 FAT12:

1
mkfs.fat -F12 floppy.img

可选,写入引导扇区:

1
dd if=boot.bin of=floppy.img bs=512 count=1 conv=notrunc

这时候我们就得到了 fat12 的软盘:floppy.img。

可以挂载到目录里面进行操作:

1
sudo mount -o loop -t msdos floppy.img floppy

这里挂载到当前目录下的 floppy 目录里。

真实的二进制数据

获得了 floppy.img 后,可以使用 bless 来查看里面的数据:

可以看到一些字符串段的信息,非常明显。

读取 BPB (BIOS Parameter Block)

引导扇区是整个软盘的第 0 个扇区,这里有一个很重要的数据结构叫做 BPB。

引导扇区的格式:

偏移量 (十六进制) 长度 (字节) 字段名称 说明
0x00 3 BS_jmpBoot 跳转指令
0x03 8 BS_OEMName OEM 名称
0x0B 2 BPB_BytsPerSec 每扇区字节数
0x0D 1 BPB_SecPerClus 每簇扇区数
0x0E 2 BPB_RsvdSecCnt 保留扇区数
0x10 1 BPB_NumFATs FAT 表数量
0x11 2 BPB_RootEntCnt 根目录最大文件数
0x13 2 BPB_TotSec16 总扇区数(小于65536时有效)
0x15 1 BPB_Media 媒体描述符
0x16 2 BPB_FATSz16 每FAT表占用扇区数
0x18 2 BPB_SecPerTrk 每磁道扇区数
0x1A 2 BPB_NumHeads 磁头数
0x1C 4 BPB_HiddSec 隐藏扇区数
0x20 4 BPB_TotSec32 总扇区数(大于65535时有效)
0x24 1 BS_DrvNum 驱动器号
0x25 1 BS_Reserved1 保留
0x26 1 BS_BootSig 扩展引导标志
0x27 4 BS_VolID 卷序列号
0x2B 11 BS_VolLab 卷标
0x36 8 BS_FilSysType 文件系统类型(如 “FAT12 “)
0x3E 448 BootCode 引导代码
0x1FE 2 Signature_word 引导扇区结束标志(0x55AA)

由此,我们可以创建一个结构体存储这些信息,并读取该扇区的信息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
typedef struct
{
    uint8_t BS_jmpBoot[3];   // 跳转指令
    uint8_t BS_OEMName[8];   // OEM 名称
    uint16_t BPB_BytsPerSec; // 每扇区字节数 (固定512)
    uint8_t BPB_SecPerClus;  // 每簇扇区数
    uint16_t BPB_RsvdSecCnt; // 保留扇区数 (引导扇区数)
    uint8_t BPB_NumFATs;     // FAT表个数 (固定2)
    uint16_t BPB_RootEntCnt; // 根目录最大文件数
    uint16_t BPB_TotSec16;   // 总扇区数
    uint8_t BPB_Media;       // 介质类型 (0xF0 软盘)
    uint16_t BPB_FATSz16;    // 一个FAT表占的扇区数
    uint16_t BPB_SecPerTrk;  // 每磁道扇区数
    uint16_t BPB_NumHeads;   // 磁头数
    uint32_t BPB_HiddSec;    // 隐藏扇区数
    uint32_t BPB_TotSec32;   // 总扇区数
    uint8_t BS_DrvNum;       // 驱动器号
    uint8_t BS_Reserved1;    // 保留位
    uint8_t BS_BootSig;      // 扩展引导标志
    uint32_t BS_VolID;       // 卷序列号
    uint8_t BS_VolLab[11];   // 卷标
    uint8_t BS_FileSysType[8];// 文件系统类型
    uint8_t BootCode[448];    // 引导代码
    uint16_t Signature_word;  // 引导扇区结束标志

} __attribute__((packed)) BPB;

BPB 结构体清晰的写出了 fat12 引导扇区的数据,并使用 __attribute__((packed)) 取消结构在编译过程的优化对齐。

在 main 函数中读取软盘:

1
2
3
4
5
6
7
    FILE *fp = fopen("floppy.img", "rb");
    BPB bpb;
    unsigned char sector[512];

    // 读取引导扇区
    fread(sector, 512, 1, fp);
    memcpy(&bpb, sector, sizeof(BPB));

先读取到 sector 中,再使用 memcpy 复制 BPB 大小的数据防止栈溢出。
这样 bpb 这个结构体就真实保存了软盘第一个扇区的数据。

1
2
3
4
5
6
7
8
9
    printf("每扇区字节数: %d\n", bpb.BPB_BytsPerSec);
    printf("厂商名:%.8s\n", bpb.BS_OEMName);
    printf("FAT表个数: %d\n", bpb.BPB_NumFATs);
    printf("根目录最大文件数:%d\n", bpb.BPB_RootEntCnt);
    printf("一个FAT表扇区数: %d\n", bpb.BPB_FATSz16);
    printf("卷标:%.11s\n", bpb.BS_VolLab);
    printf("文件系统类型:%.8s\n", bpb.BS_FileSysType);
    printf("引导代码:%s\n", bpb.BootCode + 29);
    printf("结束标志:0x%x\n", bpb.Signature_word);

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
每扇区字节数: 512
厂商名:mkfs.fat
FAT表个数: 2
根目录最大文件数:224
一个FAT表扇区数: 9
卷标:FLOPPY     
文件系统类型:FAT12   
引导代码:This is not a bootable disk.  Please insert a bootable floppy and
press any key to try again ... 

结束标志:0xaa55

和我们在 bless 看到的数据是一样的,到这我们就成功读取了 fat12 的引导扇区数据。

参考资料

  1. Youtube Building an OS - 3 - The FAT file system
  2. 自己动手写操作系统 作者:于渊
  3. OSDev.org FAT