FAT12文件系统
写在前面
本文首先罗列了FAT12文件系统的数据结构,然后写出自己在学习、编码过程中遇到的问题及思考,最后进行编码实现:
- 输出引导扇区结构数据。
- 输出根目录区结构数据。
- 在文件系统内存入一份文本,通过在FAT表中查询簇号输出数据区的文件内容。
FAT12文件系统结构
FAT12文件系统分为5个区,引导扇区中记录了整个文件系统的基础信息。FAT1表与FAT2表完全一致,属于相互备份的关系,里面存储了簇号,与数据区存在特殊的关系,后续会详细介绍。根目录区记录了文件系统内所有存储的文件信息,包括文件名、簇号、属性、时间等等。数据区不言而喻,是存储文件主体内容的地方。
序号 | 扇区位置 | 扇区个数 | 备注 |
---|---|---|---|
01 | 0 | 1 | 引导扇区 |
02 | 1 | 9 | FAT1 表 |
03 | 10 | 9 | FAT2 表 |
04 | 19 | 14 | 根目录区 |
05 | 33 | 2847 | 数据区 |
引导扇区结构
标识 | 偏移量 | 类型 | 大小 | 默认值 | 描述 |
---|---|---|---|---|---|
BS_JmpBoot | 0 | db | 3 | - | 跳转指令 |
BS_OEMName | 3 | db | 8 | FREEDOS | OEM字符串,必须为 8 个字符,不足会以空格填充 |
BPB_BytePerSec | 11 | dw | 2 | 0x200 | 每个扇区字节数 |
BPB_SecPerClus | 12 | db | 1 | 1 | 每簇占用的扇区数 |
BPB_RsvdSecCnt | 14 | dw | 2 | 1 | Boot占用的扇区数 |
BPB_NumFATs | 16 | db | 1 | 2 | FAT表的数量 |
BPB_RootEntCnt | 17 | dw | 2 | 0xE0 | 根目录可容纳的目录项数 |
BPB_TotSec16 | 19 | dw | 2 | 0xB40 | 逻辑扇区总数 |
BPB_Media | 21 | db | 1 | 0xF0 | 媒体描述符 |
BPB_FATSz16 | 22 | dw | 2 | 9 | 每个FAT占用扇区数 |
BPB_SecPerTrk | 24 | dw | 2 | 0x12 | 每个磁道扇区数 |
BPB_NumHeads | 26 | dw | 2 | 2 | 磁头数 |
BPB_HiddSec | 28 | dd | 4 | 0 | 隐藏扇区数 |
BPB_TotSec32 | 32 | dd | 4 | 0 | 若BPB_TotSec16是0,则在这里记录扇区总数 |
BS_DrvNum | 36 | db | 1 | 0 | 中断 13(int 13h)的驱动器号 |
BS_Reserved1 | 37 | db | 1 | 0 | 未使用 |
BS_Bootsig | 38 | db | 1 | 0x29 | 扩展引导标志 |
BS_VolID | 39 | dd | 4 | 0 | 卷序列号 |
BS_VolLab | 43 | db | 11 | - | 卷标,必须为11个字符,不足会以空格填充 |
BS_FileSysType | 54 | db | 8 | FAT12 | 文件系统类型,必须是8个字符,不足以空格填充 |
BOOT_Code | 62 | db | 448 | 0x00 | 引导代码,由偏移0字节(BS_JmpBoot)跳转过来 |
END | 510 | db | 2 | 0x55, 0xAA | 系统引导标识,引导扇区结束标识 |
FAT表结构
FAT 项 | 可取值 | 描述 |
---|---|---|
0 | BPB_Media | 磁盘标识字,低字节需与 BPB_Media 数值保持一致 |
1 | FFFh | 表示第一个簇已占用 |
2 ~ N | 000h | 可用簇 |
002h~FEFh | 已用簇 | |
FF0h~FF6h | 保留簇 | |
FF7h | 坏簇 | |
FF8h~FFFh | 文件的最后一个簇 |
根目录区
名称 | 偏移 | 长度 | 描述 |
---|---|---|---|
DIR_Name | 0x00 | 11 | 文件名 8B,扩展名 3B |
DIR_Attr | 0x0B | 1 | 文件属性 |
保留 | 0x0C | 10 | 保留位 |
DIR_WrtTime | 0x16 | 2 | 最后一次写入时间 |
DIR_WrtDate | 0x18 | 2 | 最后一次写入日期 |
DIR_FstCtus | 0x1A | 2 | 起始簇号 |
DIR_FileSize | 0x1C | 4 | 文件大小 |
编码须知
- 以1字节对齐
#pragma pack(push)
#pragma pack(1)
struct结构体
#pragma pack(pop)
- 输出11个字节的字符串,可以在输出时加入描述符,而不需要在字符串结尾加入\0
printf("DIR_Name: %11.11s\n", re.DIR_Name);
- 根据FAT表在数据区查找文件内容
重点:FAT表中的每个表项只占用12比特(1.5字节)
-
FAT表起始地址为第二个簇,MBR结构中BPB_FATSz16参数记录了每个FAT表所占簇的个数,占用9个,那么需要偏移一个簇开始读文件。大小为FAT簇个数*每个簇所占字节数。
-
此时获得的字符串即为FAT表的内存数据,那么需要以12bit为步长,获取其值,存入一个新的数据结构中【我malloc申请了一段内存,指针类型:unsigned short,指针变量名:cluster_list】,接下来的数据转化按照网上的讲解,我分析了好久,咨询了大神后,得到如下清晰易懂的步骤:
先取前两个字节,得到数据0x3412,然后左移4bit,得到0x412
再取后两个字节,得到数据0x5634,然后右移4bit,得到0x563
个人猜想,或许是设计此文件结构的人,是为了在固定的内存空间中,保留更多的数据段。并且让FAT表更好的与数据区进行对应,才想到如此方法压缩数据的吧
- FAT表与数据区对应关系
下图是我自身理解的过程,其中某些名字,并非官方命名。
编码:查看FAT12的MBR信息
#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define uint8 unsigned char
#define uint16 unsigned short
#define uint32 unsigned int
#pragma pack(push)
#pragma pack(1)
typedef struct _fat12header
{
uint8 BS_JmpBoot[3];
uint8 BS_OEMName[8];
uint16 BPB_BytePerSec;
uint8 BPB_SecPerClus;
uint16 BPB_RsvdSecCnt;
uint8 BPB_NumFATs;
uint16 BPB_RootEntCnt;
uint16 BPB_TotSec16;
uint8 BPB_Media;
uint16 BPB_FATSz16;
uint16 BPB_SecPerTrk;
uint16 BPB_NumHeads;
uint32 BPB_HiddSec;
uint32 BPB_TotSec32;
uint8 BS_DrvNum;
uint8 BS_Reserved1;
uint8 BS_Bootsig;
uint8 BS_VolID[4];
uint8 BS_VolLab[11];
uint8 BS_FileSysType[8];
uint8 BOOT_Code[448];
uint8 MBR_Flag[2];
}fat12header;
#pragma pack(pop)
void printheader(fat12header* fat12, const char* path)
{
FILE* fp = fopen(path, "r");
fread(fat12, sizeof(fat12header), 1, fp);
fat12->BS_OEMName[7] = 0;
fat12->BS_VolLab[10] = 0;
fat12->BS_FileSysType[7] = 0;
printf("BS_OEMName: %s\n", fat12->BS_OEMName);
printf("BPB_BytePerSec: %d\n", fat12->BPB_BytePerSec);
printf("BPB_SecPerClus: %d\n", fat12->BPB_SecPerClus);
printf("BPB_RsvdSecCnt: %d\n", fat12->BPB_RsvdSecCnt);
printf("BPB_NumFATs: %d\n", fat12->BPB_NumFATs);
printf("BPB_RootEntCnt: %d\n", fat12->BPB_RootEntCnt);
printf("BPB_TotSec16: %d\n", fat12->BPB_TotSec16);
printf("BPB_Media: %d\n", fat12->BPB_Media);
printf("BPB_FATSz16: %d\n", fat12->BPB_FATSz16);
printf("BPB_SecPerTrk: %d\n", fat12->BPB_SecPerTrk);
printf("BPB_NumHeads: %d\n", fat12->BPB_NumHeads);
printf("BPB_HiddSec: %d\n", fat12->BPB_HiddSec);
printf("BPB_TotSec32: %d\n", fat12->BPB_TotSec32);
printf("BS_DrvNum: %d\n", fat12->BS_DrvNum);
printf("BS_Reserved1: %d\n", fat12->BS_Reserved1);
printf("BS_Bootsig: 0x%x\n", fat12->BS_Bootsig);
printf("BS_VolID: %d %d %d %d\n", fat12->BS_VolID[0], fat12->BS_VolID[1], fat12->BS_VolID[2], fat12->BS_VolID[3]);
printf("BS_VolLab: %s\n", fat12->BS_VolLab);
printf("BS_FileSysType: %s\n", fat12->BS_FileSysType);
printf("Byte 510: 0x%x\n", fat12->MBR_Flag[0]);
printf("Byte 511: 0x%x\n", fat12->MBR_Flag[1]);
fclose(fp);
return;
}
int main()
{
fat12header fat12 = {0};
printheader(&fat12, "data.img");
return 0;
}
编码:查看根目录区结构
#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define uint8 unsigned char
#define uint16 unsigned short
#define uint32 unsigned int
#pragma pack(push)
#pragma pack(1)
typedef struct _fat12header
{
uint8 BS_JmpBoot[3];
uint8 BS_OEMName[8];
uint16 BPB_BytePerSec;
uint8 BPB_SecPerClus;
uint16 BPB_RsvdSecCnt;
uint8 BPB_NumFATs;
uint16 BPB_RootEntCnt;
uint16 BPB_TotSec16;
uint8 BPB_Media;
uint16 BPB_FATSz16;
uint16 BPB_SecPerTrk;
uint16 BPB_NumHeads;
uint32 BPB_HiddSec;
uint32 BPB_TotSec32;
uint8 BS_DrvNum;
uint8 BS_Reserved1;
uint8 BS_Bootsig;
uint8 BS_VolID[4];
uint8 BS_VolLab[11];
uint8 BS_FileSysType[8];
uint8 BOOT_Code[448];
uint8 MBR_Flag[2];
}fat12header;
typedef struct _rootentry
{
uint8 DIR_Name[11];
uint8 DIR_Attr;
uint8 reserve[10];
uint16 DIR_WrtTime;
uint16 DIR_WrtDate;
uint16 DIR_FstClus;
uint32 DIR_FileSize;
}rootentry;
#pragma pack(pop)
void printheader(fat12header* fat12, const char* path)
{
FILE* fp = fopen(path, "r");
fread(fat12, sizeof(fat12header), 1, fp);
fat12->BS_OEMName[7] = 0;
fat12->BS_VolLab[10] = 0;
fat12->BS_FileSysType[7] = 0;
fclose(fp);
return;
}
void findrootentry(fat12header* rf, const char* path, int i, rootentry* re)
{
FILE* fp = fopen(path, "rb");
if(fp && (0 <= i) &&(i < rf->BPB_RootEntCnt))
{
fseek(fp, 19*rf->BPB_BytePerSec+i*sizeof(rootentry), SEEK_SET);
fread((void*)re, 1, sizeof(rootentry), fp);
}
fclose(fp);
return;
}
void printrootentry(fat12header* rf, const char* path)
{
for(int i = 0; i < rf->BPB_RootEntCnt;i++)
{
rootentry re = {0};
findrootentry(rf, path, i, &re);
if(0 == re.DIR_FstClus || '\0' == re.DIR_Name[0])
continue;
printf("====%d==== : \n", i);
printf("DIR_Name: %11.11s\n", re.DIR_Name);
printf("DIR_Attr: 0x%x\n", re.DIR_Attr);
printf("DIR_WrtTime: %d\n", re.DIR_WrtTime);
printf("DIR_WrtDate: %d\n", re.DIR_WrtDate);
printf("DIR_FstClus: %d\n", re.DIR_FstClus);
printf("DIR_FileSize: %d\n", re.DIR_FileSize);
}
}
int main()
{
fat12header fat12 = {0};
printheader(&fat12, "data.img");
printrootentry(&fat12, "data.img");
return 0;
}
MacOS M1,结果为:
Ubuntu,结果为:
编码:根据根目录区文件名,加载文件数据
#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define uint8 unsigned char
#define uint16 unsigned short
#define uint32 unsigned int
#pragma pack(push)
#pragma pack(1)
typedef struct _fat12header
{
uint8 BS_JmpBoot[3];
uint8 BS_OEMName[8];
uint16 BPB_BytePerSec;
uint8 BPB_SecPerClus;
uint16 BPB_RsvdSecCnt;
uint8 BPB_NumFATs;
uint16 BPB_RootEntCnt;
uint16 BPB_TotSec16;
uint8 BPB_Media;
uint16 BPB_FATSz16;
uint16 BPB_SecPerTrk;
uint16 BPB_NumHeads;
uint32 BPB_HiddSec;
uint32 BPB_TotSec32;
uint8 BS_DrvNum;
uint8 BS_Reserved1;
uint8 BS_Bootsig;
uint8 BS_VolID[4];
uint8 BS_VolLab[11];
uint8 BS_FileSysType[8];
uint8 BOOT_Code[448];
uint8 MBR_Flag[2];
}fat12header;
typedef struct _rootentry
{
uint8 DIR_Name[11];
uint8 DIR_Attr;
uint8 reserve[10];
uint16 DIR_WrtTime;
uint16 DIR_WrtDate;
uint16 DIR_FstClus;
uint32 DIR_FileSize;
}rootentry;
#pragma pack(pop)
void printheader(fat12header* fat12, const char* path)
{
FILE* fp = fopen(path, "r");
fread(fat12, sizeof(fat12header), 1, fp);
fat12->BS_OEMName[7] = 0;
fat12->BS_VolLab[10] = 0;
fat12->BS_FileSysType[7] = 0;
fclose(fp);
return;
}
void findrootentry(fat12header* rf, const char* path, int i, rootentry* re)
{
FILE* fp = fopen(path, "rb");
if(fp && (0 <= i) &&(i < rf->BPB_RootEntCnt))
{
fseek(fp, 19*rf->BPB_BytePerSec+i*sizeof(rootentry), SEEK_SET);
fread((void*)re, 1, sizeof(rootentry), fp);
}
fclose(fp);
return;
}
int searchrootentry(fat12header* rf, rootentry* re, const char* path, const char* filename)
{
for(int i = 0; i < rf->BPB_RootEntCnt;i++)
{
findrootentry(rf, path, i, re);
if(0 == re->DIR_FstClus || '\0' == re->DIR_Name[0])
continue;
char dirname[11] = {0};
char *token = NULL;
token = strtok(re->DIR_Name, " ");
sprintf(dirname, "%s", token);
token = strtok(NULL, " ");
sprintf(dirname, "%s.%s", dirname, token);
if(0 != strcmp(dirname, filename))
continue;
printf("%s\n", dirname);
return 1;
}
return 0;
}
void readfat(fat12header* rf, const char* path, uint16* cluster_list)
{
int fat_size = rf->BPB_FATSz16 * rf->BPB_BytePerSec;
uint8* fat = (uint8*)malloc(fat_size);
FILE* fp = fopen(path, "rb");
if(fp)
{
fseek(fp, rf->BPB_BytePerSec * 1, SEEK_SET);
fread((uint8*)fat, 1, fat_size, fp);
for(int i = 0; i < fat_size; i+=3)
{
*cluster_list = ((uint16)(fat[i+1] & 0xF) << 8) + (uint16)fat[i];
cluster_list += 1;
*cluster_list = (fat[i+1] >> 4) + ((uint16)fat[i+2] << 4);
cluster_list += 1;
}
}
fclose(fp);
free(fat);
}
void printfilecontent(fat12header* rf, rootentry* re, const char* path, const char* filename)
{
// 根据目录项的名字,查找该文件的信息
int flag = searchrootentry(rf, re, path, filename);
if(0 == flag)
return;
// 将FAT项全部转化并存入 cluster_list 链表
int fat_size = rf->BPB_FATSz16 * rf->BPB_BytePerSec;
int cluster_list_size = fat_size*8/12;
uint16* cluster_list = (uint16*)malloc(cluster_list_size*2);
readfat(rf, path, cluster_list);
short DIR_FstClus = re->DIR_FstClus;
FILE* fp = fopen(path, "rb");
while(fp)
{
// 计算簇地址
short DIR_FstClus_addr = 33 * rf->BPB_SecPerClus * rf->BPB_BytePerSec + (DIR_FstClus - 2) * rf->BPB_SecPerClus * rf->BPB_BytePerSec;
//printf("DIR_FstClus:%d\tDIR_FstClus_addr:0x%x\n", DIR_FstClus, DIR_FstClus_addr);
char *filemessage = (char*)malloc(re->DIR_FileSize);
fseek(fp, DIR_FstClus_addr, SEEK_SET);
fread((void*)filemessage, 1, rf->BPB_SecPerClus * rf->BPB_BytePerSec, fp);
printf("%s", filemessage);
free(filemessage);
DIR_FstClus = cluster_list[DIR_FstClus];
if(DIR_FstClus >= 0xFF8)
break;
}
fclose(fp);
free(cluster_list);
}
int main()
{
fat12header fat12 = {0};
rootentry re = {0};
printheader(&fat12, "data.img");
printfilecontent(&fat12, &re, "data.img", "LOADER.BIN");
return 0;
}
写在后面(心得)
如果我们站在FAT12文件系统上面看它,无非就是设计者制定了一套数据结构和规则,使用者按照它的规则去使用。
上学的时候学数据结构,总觉得数组、队列、链表、树之类的,只需要多记多练甚至考前背一背,就没什么难的。
刚工作时,见到的数据结构比较大,为了让其内存空间足够小会用union。为了节约数据拷贝的内存,声明时会按字节数对齐。
如今经历了FAT12,感觉它更难了,更多的是设计能力。