北京交通大学实验报告
课程名称:操作系统
实验题目:FAT文件系统模拟与实现
学号:21281280
姓名:柯劲帆
班级:物联网2101班
指导老师:何永忠
报告日期:2023年12月31日
---
## 目录
[TOC]
---
# 1. 开发运行环境和工具
| 操作系统 | Linux内核版本 | 处理器 | GCC版本 |
| :---------: | :-----------------------: | :--------------------------------------: | :-------------------------------: |
| Deepin 20.9 | 5.18.17-amd64-desktop-hwe | Intel(R) Core(TM) i3-2330M CPU @ 2.20GHz | gcc (Uos 8.3.0.3-3+rebuild) 8.3.0 |
- **其他工具**:
- **编辑**:VSCode
- **编译和运行**:Terminal
# 2. FAT磁盘格式化操作和映像文件生成
FAT的引导扇区各个字段的定义和说明如下:
| 字节位移 | 字段长度(字节) | 字段名称 |
| -------- | -------------- | ---------------------------- |
| 0x00 | 3 | 跳转指令(Jump Instruction) |
| 0x03 | 8 | OEM ID |
| 0x0B | 25 | BPB |
| 0x24 | 26 | 扩展BPB |
| 0x3E | 448 | 引导程序代码(Bootstrap Code) |
| 0x01FE | 4 | 扇区结束标识符(0xAA55) |
FAT16分区的BPB字段为:
| 字节位移 | 字段长度(字节) | 例值 | 名称、定义和描述 |
| -------- | -------------- | ----------- | ------------------------------------------------------------ |
| 0x0B | 2 | 0x0200 | 扇区字节数(Bytes Per Sector) 硬件扇区的大小。本字段合法的十进制值有512、1024、2048和4096。对大多数磁盘来说,本字段的值为512 |
| 0x0D | 1 | 0x10 | 每簇扇区数(Sectors Per Cluster) 一个簇中的扇区数。由于FAT16文件系统只能跟踪有限个簇(最多为65536个)。因此,通过增加每簇的扇区数可以支持最大分区数。分区的缺省的簇的大小取决于该分区的大小。本字段合法的十进制值有 1、2、4、8、16、32、64和128。导致簇大于32KB(每扇区字节数*每簇扇区数)的值会引起磁盘错误和软件错误 |
| 0x0e | 2 | 0x0006 | 保留扇区数(Reserved Sector) 第一个FAT开始之前的扇区数,包括引导扇区。 |
| 0x10 | 1 | 0x02 | FAT数(Number of FAT)该分区上FAT的副本数。本字段的值一般为2 |
| 0x11 | 2 | 0x0200 | 根目录项数(Root Entries) 能够保存在该分区的根目录文件夹中的32个字节长的文件和文件夹名称项的总数。在一个典型的硬盘上,本字段的值为512。其中一个项常常被用作卷标号(Volume Label),长名称的文件和文件夹每个文件使用多个项。文件和文件夹项的最大数一般为511,但是如果使用的长文件名,往往都达不到这个数。 |
| 0x13 | 2 | 0x0000 | 小扇区数(Small Sector) 该分区上的扇区数,表示为16位(<65536)。对大于65536个扇区的分区来说,本字段的值为0,而使用大扇区数来取代它。 |
| 0x15 | 1 | 0xF8 | 媒体描述符( Media Descriptor)提供有关媒体被使用的信息。值0xF8表示硬盘,0xF0表示高密度的3.5寸软盘。媒体描述符要用于MS-DOS FAT16磁盘,在Windows 2000中未被使用 |
| 0x16 | 2 | 0x00F5 | 每FAT扇区数(Sectors Per FAT) 该分区上每个FAT所占用的扇区数。计算机利用这个数和FAT数以及隐藏扇区数来决定根目录在哪里开始。计算机还可以根据根目录中的项数(512)决定该分区的用户数据区从哪里开始 |
| 0x18 | 2 | 0x003F | 每道扇区数(Sectors Per Trark) |
| 0x1A | 2 | 0x00FF | 磁头数(Number of head) |
| 0x1C | 4 | 0x000000400 | 隐藏扇区数(Hidden Sector) 该分区上引导扇区之前的扇区数。在引导序列计算到根目录和数据区的绝对位移的过程中使用了该值 |
| 0x20 | 4 | 0x000F4C00 | 大扇区数(Large Sector) 如果小扇区数字段的值为0,本字段就包含该FAT16分区中的总扇区数。如果小扇区数字段的值不为0,那么本字段的值为0 |
FAT16分区的扩展BPB字段为:
| 字节位移 | 字段长度(字节) | 图8对应取值 | 名称、定义和描述 |
| -------- | -------------- | ----------- | ------------------------------------------------------------ |
| 0x24 | 1 | 0x80 | 物理驱动器号( Physical Drive Number) 与BIOS物理驱动器号有关。软盘驱动器被标识为0x00,物理硬盘被标识为0x80,而与物理磁盘驱动器无关。一般地,在发出一个INT13h BIOS调用之前设置该值,具体指定所访问的设备。只有当该设备是一个引导设备时,这个值才有意义 |
| 0x25 | 1 | 0x01 | 保留(Reserved) FAT16分区一般将本字段的值设置为1 |
| 0x26 | 1 | 0x29 | 扩展引导标签(Extended Boot Signature) 本字段必须要有能被Windows 2000所识别的值0x28或0x29 |
| 0x27 | 2 | 0xABA13358 | 卷序号(Volume Serial Number) 在格式化磁盘时所产生的一个随机序号,它有助于区分磁盘 |
| 0x2B | 11 | "NO NAME" | 卷标(Volume Label) 本字段只能使用一次,它被用来保存卷标号。现在,卷标被作为一个特殊文件保存在根目录中 |
| 0x36 | 8 | "FAT16" | 文件系统类型(File System Type) 根据该磁盘格式,该字段的值可以为FAT、FAT12或FAT16 |
因此,各参数初始化如下:
```c
#include
#include
#include
#include
#include
#define BS_jmpBoot_Offset 0x00 // 跳转指令偏移
#define BS_jmpBoot_Len 3 // 跳转指令长度
#define BS_OEMName_Offset 0x03 // 厂商名称偏移
#define BS_OEMName_Len 8 // 厂商名称长度
#define BPB_BytsPerSec_Offset 0x0B // 每扇区字节数偏移
#define BPB_BytsPerSec_Len 2 // 每扇区字节数长度
#define BPB_SecPerClus_Offset 0x0D // 每簇扇区数偏移
#define BPB_SecPerClus_Len 1 // 每簇扇区数长度
#define BPB_RsvdSecCnt_Offset 0x0E // 保留扇区数 (引导扇区的扇区数)偏移
#define BPB_RsvdSecCnt_Len 2 // 保留扇区数 (引导扇区的扇区数)长度
#define BPB_NumFATs_Offset 0x10 // FAT的份数偏移
#define BPB_NumFATs_Len 1 // FAT的份数长度
#define BPB_RootEntCnt_Offset 0x11 // 根目录可容纳的目录项数偏移
#define BPB_RootEntCnt_Len 2 // 根目录可容纳的目录项数长度
#define BPB_TotSec16_Offset 0x13 // 扇区总数偏移
#define BPB_TotSec16_Len 2 // 扇区总数长度
#define BPB_Media_Offset 0x15 // 介质描述符偏移
#define BPB_Media_Len 1 // 介质描述符长度
#define BPB_FATSz16_Offset 0x16 // 每个FAT表扇区数偏移
#define BPB_FATSz16_Len 2 // 每个FAT表扇区数长度
#define BPB_SecPerTrk_Offset 0x18 // 每磁道扇区数偏移
#define BPB_SecPerTrk_Len 2 // 每磁道扇区数长度
#define BPB_NumHeads_Offset 0x1A // 磁头数偏移
#define BPB_NumHeads_Len 2 // 磁头数长度
#define BPB_HiddSec_Offset 0x1C // 隐藏扇区数偏移
#define BPB_HiddSec_Len 4 // 隐藏扇区数长度
#define BPB_TotSec32_Offset 0x20 // 偏移 如果BPB_TotSec16是0,由这个值记录扇区数
#define BPB_TotSec32_Len 4 // 长度 如果BPB_TotSec16是0,由这个值记录扇区数
#define BS_DrvNum_Offset 0x24 // int 13h的驱动器号偏移
#define BS_DrvNum_Len 1 // int 13h的驱动器号长度
#define BS_Reservedl_Offset 0x25 // 偏移 未使用
#define BS_Reservedl_Len 1 // 长度 未使用
#define BS_BootSig_Offset 0x26 // 扩展引导标记偏移
#define BS_BootSig_Len 1 // 扩展引导标记长度
#define BS_VolID_Offset 0x27 // 卷序列号偏移
#define BS_VolID_Len 4 // 卷序列号长度
#define BS_VolLab_Offset 0x2B // 卷标偏移
#define BS_VolLab_Len 11 // 卷标长度
#define BS_FileSysType_Offset 0x36 // 文件系统类型偏移
#define BS_FileSysType_Len 8 // 文件系统类型长度
#define DBR_BootCode_Offset 0x3E // 引导代码及其他偏移
#define DBR_BootCode_Len 448 // 引导代码及其他长度
#define DBR_EndMark_Offset 0x1FE // 结束标志偏移
#define DBR_EndMark_Len 2 // 结束标志长度
#define DBR_EndMark 0xAA55 // 结束标志
typedef struct {
short BytsPerSec; // 每扇区字节数
char SecPerClus; // 每簇扇区数
short RsvdSecCnt; // 保留扇区数 (引导扇区的扇区数)
char NumFATs; // FAT的份数
short RootEntCnt; // 根目录可容纳的目录项数
short TotSec16; // 扇区总数
char Media; // 介质描述符
short FATSz16; // 每个FAT表扇区数
short SecPerTrk; // 每磁道扇区数
short NumHeads; // 磁头数
int HiddSec; // 隐藏扇区数
int TotSec32; // 替代扇区数
char DrvNum; // int 13h的驱动器号
char Reservedl; // 未使用
char BootSig; // 扩展引导标记
int VolID; // 卷序列号
int TotalSize; // 总磁盘大小
int current_dir; // 当前目录的首地址
} DISK;
DISK init_disk_attr() {
DISK disk;
disk.BytsPerSec = 0x0200;
disk.SecPerClus = 0x01;
disk.RsvdSecCnt = 0x0001;
disk.NumFATs = 0x01;
disk.RootEntCnt = 0x0200;
disk.TotSec16 = 0x2000;
disk.Media = 0xf8;
disk.FATSz16 = 0x0020;
disk.SecPerTrk = 0x0020;
disk.NumHeads = 0x0040;
disk.HiddSec = 0x00000000;
disk.TotSec32 = 0x00000000;
disk.DrvNum = 0x80;
disk.Reservedl = 0x00;
disk.BootSig = 0x29;
disk.VolID = 0x00000000;
disk.TotalSize = 0x400000;
disk.current_dir = 0x4200;
return disk;
}
```
初始化和格式化磁盘代码如下:
```c
void init_file_system(char disk_name[13], DISK disk_attr) {
FILE* fp = fopen(disk_name, "wb");
// 跳转指令
fseek(fp, BS_jmpBoot_Offset, 0);
char jmpBoot[BS_jmpBoot_Len] = { 0xEB, 0x3C, 0x90 };
fwrite(jmpBoot, 1, BS_jmpBoot_Len, fp);
// 厂商名称
fseek(fp, BS_OEMName_Offset, 0);
char OEMName[BS_OEMName_Len] = { 'K', 'e', 'J', 'F', ' ', ' ', ' ', ' ' };
fwrite(OEMName, 1, BS_OEMName_Len, fp);
// 每扇区字节数
fseek(fp, BPB_BytsPerSec_Offset, 0);
fwrite(&disk_attr.BytsPerSec, BPB_BytsPerSec_Len, 1, fp);
// 每簇扇区数
fseek(fp, BPB_SecPerClus_Offset, 0);
fwrite(&disk_attr.SecPerClus, BPB_SecPerClus_Len, 1, fp);
// 保留扇区数 (引导扇区的扇区数)
fseek(fp, BPB_RsvdSecCnt_Offset, 0);
fwrite(&disk_attr.RsvdSecCnt, BPB_RsvdSecCnt_Len, 1, fp);
// FAT的份数
fseek(fp, BPB_NumFATs_Offset, 0);
fwrite(&disk_attr.NumFATs, BPB_NumFATs_Len, 1, fp);
// 根目录可容纳的目录项数
fseek(fp, BPB_RootEntCnt_Offset, 0);
fwrite(&disk_attr.RootEntCnt, BPB_RootEntCnt_Len, 1, fp);
// 扇区总数
fseek(fp, BPB_TotSec16_Offset, 0);
fwrite(&disk_attr.TotSec16, BPB_TotSec16_Len, 1, fp);
// 介质描述符
fseek(fp, BPB_Media_Offset, 0);
fwrite(&disk_attr.Media, BPB_Media_Len, 1, fp);
// 每个FAT表扇区数
fseek(fp, BPB_FATSz16_Offset, 0);
fwrite(&disk_attr.FATSz16, BPB_FATSz16_Len, 1, fp);
// 每磁道扇区数
fseek(fp, BPB_SecPerTrk_Offset, 0);
fwrite(&disk_attr.SecPerTrk, BPB_SecPerTrk_Len, 1, fp);
// 磁头数
fseek(fp, BPB_NumHeads_Offset, 0);
fwrite(&disk_attr.NumHeads, BPB_NumHeads_Len, 1, fp);
// 隐藏扇区数
fseek(fp, BPB_HiddSec_Offset, 0);
fwrite(&disk_attr.HiddSec, BPB_HiddSec_Len, 1, fp);
// 替代扇区数
fseek(fp, BPB_TotSec32_Offset, 0);
fwrite(&disk_attr.TotSec32, BPB_TotSec32_Len, 1, fp);
// int 13h的驱动器号
fseek(fp, BS_DrvNum_Offset, 0);
fwrite(&disk_attr.DrvNum, BS_DrvNum_Len, 1, fp);
// 未使用
fseek(fp, BS_Reservedl_Offset, 0);
fwrite(&disk_attr.Reservedl, BS_Reservedl_Len, 1, fp);
// 扩展引导标记
fseek(fp, BS_BootSig_Offset, 0);
fwrite(&disk_attr.BootSig, BS_BootSig_Len, 1, fp);
// 卷序列号
fseek(fp, BS_VolID_Offset, 0);
fwrite(&disk_attr.VolID, BS_VolID_Len, 1, fp);
// 卷标
fseek(fp, BS_VolLab_Offset, 0);
char VolLab[BS_VolLab_Len] = {'K', 'e', 'J', 'i', 'n', 'g', 'f', 'a', 'n', ' ', ' '};
fwrite(VolLab, 1, BS_VolLab_Len, fp);
// 文件系统类型
fseek(fp, BS_FileSysType_Offset, 0);
char FileSysType[BS_FileSysType_Len] = {'F', 'A', 'T', '1', '6', ' ', ' ', ' '};
fwrite(FileSysType, 1, BS_FileSysType_Len, fp);
// 引导代码等
fseek(fp, DBR_BootCode_Offset, 0);
char BootCode[DBR_BootCode_Len] = { 0 };
fwrite(BootCode, 1, DBR_BootCode_Len, fp);
// 结束标志
fseek(fp, DBR_EndMark_Offset, 0);
short EndMark = DBR_EndMark;
fwrite(&EndMark, DBR_EndMark_Len, 1, fp);
// FAT分区表
fseek(fp, 0x0200, 0);
int FAT_Len = disk_attr.FATSz16 * disk_attr.BytsPerSec * disk_attr.NumFATs; // 每个FAT表扇区数 * 扇区字节数 * FAT的份数
char* FAT_data = (char*)malloc(sizeof(char) * FAT_Len);
fwrite(FAT_data, 1, FAT_Len, fp);
// DATA段
fseek(fp, 0x0200 + FAT_Len, 0);
int DATA_Len = disk_attr.TotalSize - 0x0200 - FAT_Len;
char* DATA_data = (char*)malloc(sizeof(char) * DATA_Len);
fwrite(DATA_data, 1, DATA_Len, fp);
fclose(fp);
}
```
调用这个函数生成.img虚拟磁盘镜像挂载到linux上:
```sh
> gcc ./main.c -o main
> ./main
> mkdir /media/cdrom
> sudo mount -o loop KJF_disk.img /media/cdrom
> cd /media/cdrom
```
发现可以正常认盘和使用。
查看其二进制:
```txt
> hexdump KJF_disk.img -C
00000000 eb 3c 90 4b 65 4a 46 20 20 20 20 00 02 01 01 00 |.<.KeJF .....|
00000010 01 00 02 00 20 f8 20 00 20 00 40 00 00 00 00 00 |.... . . .@.....|
00000020 00 00 00 00 80 00 29 00 00 00 00 4b 65 4a 69 6e |......)....KeJin|
00000030 67 66 61 6e 20 20 46 41 54 31 36 20 20 20 00 00 |gfan FAT16 ..|
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000200 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00400000
```
FAT文件格式正确。
# 3. 目录
目录项字段定义:
| 偏移地址 | 字节数 | 说明 | |
| --------- | ------ | ---------------- | ------------------------------------------------------------ |
| 0x00~0x07 | 8 | 文件名 | |
| 0x08~0x0A | 3 | 扩展名 | |
| 0x0B | 1 | 属 性 | 00000000(读写) 00000001(只读) 00000010(隐藏) 00000100(系统) 00001000(卷标)00010000(目录) 00100000(归档) |
| 0x0C~0x15 | 10 | 保留 | |
| 0x16~0x17 | 2 | 文件最近修改时间 | |
| 0x18~0x19 | 2 | 文件最近修改日期 | |
| 0x1A~0x1B | 2 | 文件的首簇号 | |
| 0x1C~0x1F | 4 | 文件长度大小 | |
因此代码如下:
```c
int mkdir(char dir_name[11], char disk_name[13], DISK disk_attr) {
FILE* fp = fopen(disk_name, "rb+");
unsigned short dir_firstBlockNum = find_free_blocks(disk_attr, fp);
if (dir_firstBlockNum == 0xFFFF) return -1;
fseek(fp, 0x200 + dir_firstBlockNum * 2, 0);
short new_index = 0xFFFF;
fwrite(&new_index, 2, 1, fp);
int start_byte = disk_attr.current_dir;
int byte_line_num = 0;
while (byte_line_num < disk_attr.BytsPerSec) {
fseek(fp, start_byte + byte_line_num, 0);
int byte_line[4] = { 0 };
fread(byte_line, 4, 3, fp);
if (!byte_line[0] && !byte_line[1] && !byte_line[2]) break;
byte_line_num += 0x20;
}
if (byte_line_num >= disk_attr.BytsPerSec) return -1;
fseek(fp, start_byte + byte_line_num, 0);
fwrite(dir_name, 1, 11, fp);
short dir_attr_byte = 0b00010000;
fwrite(&dir_attr_byte, 1, 1, fp);
short dir_reserved_byte[6] = { 0 };
fwrite(dir_reserved_byte, 2, 5, fp);
struct tm time = get_time();
short dir_time_byte = time.tm_hour * 2048 + time.tm_min * 32 + time.tm_sec / 2;
fwrite(&dir_time_byte, 2, 1, fp);
short dir_date_byte = (time.tm_year + 1900 - 1980) * 512 + time.tm_mon * 32 + time.tm_mday;
fwrite(&dir_date_byte, 2, 1, fp);
fwrite(&dir_firstBlockNum, 2, 1, fp);
short dir_size = disk_attr.BytsPerSec * disk_attr.SecPerClus;
fwrite(&dir_size, 2, 1, fp);
int new_block = 0x7800 + disk_attr.BytsPerSec * disk_attr.SecPerClus * dir_firstBlockNum;
fseek(fp, new_block, 0);
char dot[11] = { '.', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' };
fwrite(dot, 1, 11, fp);
fwrite(&dir_attr_byte, 1, 1, fp);
fwrite(dir_reserved_byte, 2, 5, fp);
fwrite(&dir_time_byte, 2, 1, fp);
fwrite(&dir_date_byte, 2, 1, fp);
short dir_oriBlock_byte = 0;
fwrite(&dir_oriBlock_byte, 2, 1, fp);
unsigned int dir_oriSize = 0;
fwrite(&dir_oriSize, 4, 1, fp);
fseek(fp, new_block + 0x20, 0);
char dotdot[11] = { '.', '.', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' };
fwrite(dotdot, 1, 11, fp);
fwrite(&dir_attr_byte, 1, 1, fp);
fwrite(dir_reserved_byte, 2, 5, fp);
short dir_oriTime_byte = 0;
fwrite(&dir_oriTime_byte, 2, 1, fp);
short dir_oriDate_byte = (1970 - 1980 + 1900) * 512 + 1 * 32 + 1;
fwrite(&dir_oriDate_byte, 2, 1, fp);
fwrite(&dir_oriBlock_byte, 2, 1, fp);
fwrite(&dir_oriSize, 4, 1, fp);
fclose(fp);
return 0;
}
struct tm get_time() {
struct timeval tv;
struct timezone tz;
struct tm *t;
gettimeofday(&tv, &tz);
t = localtime(&tv.tv_sec);
return *t;
}
```
# 4. 文件
```c
int touch(char file_name[11], char disk_name[13], DISK disk_attr) {
FILE* contentFile = fopen("./data.txt", "r");
char *content = (char*)malloc(2000 * sizeof(char));
fread(content, 1, 2000, contentFile);
fclose(contentFile);
FILE* fp = fopen(disk_name, "rb+");
int file_blockNum = strlen(content) / (disk_attr.BytsPerSec * disk_attr.SecPerClus) + 1;
unsigned short* index = (short*)malloc(sizeof(short) * file_blockNum);
for (int i = 0; i < file_blockNum; i++) {
index[i] = find_free_blocks(disk_attr, fp);
}
for (int i = 0; i < file_blockNum - 1; i++) {
fseek(fp, 0x200 + index[i] * 2, 0);
fwrite(&index[i + 1], 2, 1, fp);
}
fseek(fp, 0x200 + index[file_blockNum - 1] * 2, 0);
short last_index = 0xFFFF;
fwrite(&last_index, 2, 1, fp);
int start_byte = disk_attr.current_dir;
int byte_line_num = 0;
while (byte_line_num < disk_attr.BytsPerSec) {
fseek(fp, start_byte + byte_line_num, 0);
int byte_line[4] = { 0 };
fread(byte_line, 4, 3, fp);
if (!byte_line[0] && !byte_line[1] && !byte_line[2]) break;
byte_line_num += 0x20;
}
if (byte_line_num >= disk_attr.BytsPerSec) return -1;
fseek(fp, start_byte + byte_line_num, 0);
fwrite(file_name, 1, 11, fp);
short file_attr_byte = 0b00000000;
fwrite(&file_attr_byte, 1, 1, fp);
short file_reserved_byte[6] = { 0 };
fwrite(file_reserved_byte, 2, 5, fp);
struct tm time = get_time();
short file_time_byte = time.tm_hour * 2048 + time.tm_min * 32 + time.tm_sec / 2;
fwrite(&file_time_byte, 2, 1, fp);
short file_date_byte = (time.tm_year + 1900 - 1980) * 512 + time.tm_mon * 32 + time.tm_mday;
fwrite(&file_date_byte, 2, 1, fp);
fwrite(&index[0], 2, 1, fp);
unsigned int file_size = strlen(content);
fwrite(&file_size, 4, 1, fp);
for (int i = 0; i < file_blockNum; i++) {
int new_block = 0x7800 + disk_attr.BytsPerSec * disk_attr.SecPerClus * index[i];
fseek(fp, new_block, 0);
fwrite(content,
file_size > disk_attr.BytsPerSec * disk_attr.SecPerClus ? disk_attr.BytsPerSec * disk_attr.SecPerClus : file_size,
1,
fp
);
content += disk_attr.BytsPerSec * disk_attr.SecPerClus;
file_size -= disk_attr.BytsPerSec * disk_attr.SecPerClus;
}
fclose(fp);
return 0;
}
```