目录
1、详解FAT12文件系统
2、实验
1、向虚拟软盘写入文件
2、读取软盘文件系统信息
3、在根目录中查找目标文件
4、读取目标文件到内存
3、小结
文件系统
- 存储介质上组织文件数据的方法(数据组织的方式)
文件系统示例
- FAT12是DOS时代的早期文件系统
- FAT12结构非常简单,一直沿用于软盘
- FAT12的基本组织单位
字节(Byte):基本数据单位
扇区(Sector):磁盘中的最小数据单元
簇(Cluster):一个或多个扇区
深入FAT12文件系统
FAT12文件系统由引导区,FAT表,根目录项表和文件数据区组成。
FAT12的主引导区
主引导区存储的比较重要的信息是文件系统的类型,文件系统逻辑扇区总数,
每簇包含的扇区数等。主引导区最后以0x55AA两个字节作为结束,共占用一个扇区。
二进制分析:(data.img由后文生成)
根目录区的大小和位置
可以看到根目录区起始于第19扇区
根目录区的目录项
根目录区由目录项构成,每一个目录项代表根目录中的一个文件索引。
//创建 RootEntry 结构体类型
struct RootEntry
{
char DIR_Name[11];
uchar DIR_Attr;
uchar reserve[10];
ushort DIR_WrtTime;
ushort DIR_WrtDate;
ushort DIR_FstClus;
uint DIR_FileSize;
};
FAT表 - FAT12的数据组织核心
- FAT1和FAT2是相互备份的关系 ,数据内容完全一致
- FAT表是一个关系图,记录了文件数据的先后关系
- 每一个FAT表项占用12比特
- FAT表的前2个表项规定不使用
FAT表中的先后关系
- 以簇(扇区)为单位存储文件数据(这里一簇等于一扇区大小)
- 每个表项(vec[i])表示文件数据的实际位置(簇)
(1)DIR_FstClus表示文件第0簇(扇区)的位置
(2)vec[DIR_FstClus]表示文件第1簇(扇区)的位置
(3)vec[vec[DIR_FstClus]]表示文件第2簇(扇区)的位置...
FAT12数据物理组织示意
FAT12数据逻辑组织示意
模拟Boot 查找目标文件(Loader),并读取文件的内容!- 突破Boot512字节限制的解决方案
解决方案(★)
- 使用FAT12对软盘(data.img)进行格式化
- 编写可执行程序(Loader),并将其拷贝到软盘中
- 主引导程序(Boot)在文件系统中查找Loader
- 将Loader复制到内存中,并跳转到入口处执行
实验:往虚拟软盘中写入文件
- 原材料:FreeDos,Bochs,bximage
- 步骤:
① 创建虚拟软盘data.img
② 在FreeDos中进行格式化(FAT12)
③ 将data.img挂载到Linux中,并写入文件
1、先创建一个虚拟软盘 data.img
2、修改bochsrc 配置文件,freedos.img 作为启动软盘(A盘),将软盘data.img作为B盘插入
###############################################################
# Configuration file for Bochs
###############################################################
# how much memory the emulated machine will have
megs: 32
# filename of ROM images
romimage: file=/usr/local/share/bochs/BIOS-bochs-latest
vgaromimage: file=/usr/share/vgabios/vgabios.bin
# what disk images will be used
floppya: 1_44=freedos.img, status=inserted
floppyb: 1_44=data.img, status=inserted
# choose the boot disk.
boot: floppy
# where do we send log messages?
# log: bochsout.txt
# disable the mouse
mouse: enabled=0
# enable key mapping, using US layout as default.
keyboard_mapping: enabled=1, map=/usr/local/share/bochs/keymaps/x11-pc-us.map
启动bochs
3、将data.img挂载到Linux中,并写入loader文件
成功向FAT12格式的软盘写入文件
实验:读取data.img中的文件系统信息
步骤:
创建Fat12Header结构体类型
使用文件流读取前512字节的内容
解析并打印相关的信息
#include
#include
#include
#include
#pragma pack(push)
#pragma pack(1)
struct Fat12Header
{
char BS_OEMName[8]; // OEM字符串,必须为8个字符,不足以空格填空
ushort BPB_BytsPerSec; // 每扇区字节数
uchar BPB_SecPerClus; // 每簇占用的扇区数
ushort BPB_RsvdSecCnt; // Boot占用的扇区数
uchar BPB_NumFATs; // FAT表的记录数
ushort BPB_RootEntCnt; // 最大根目录文件数
ushort BPB_TotSec16; // 每个FAT占用扇区数
uchar BPB_Media; // 媒体描述符
ushort BPB_FATSz16; // 每个FAT占用扇区数
ushort BPB_SecPerTrk; // 每个磁道扇区数
ushort BPB_NumHeads; // 磁头数
uint BPB_HiddSec; // 隐藏扇区数
uint BPB_TotSec32; // 如果BPB_TotSec16是0,则在这里记录
uchar BS_DrvNum; // 中断13的驱动器号
uchar BS_Reserved1; // 未使用
uchar BS_BootSig; // 扩展引导标志
uint BS_VolID; // 卷序列号
char BS_VolLab[11]; // 卷标,必须是11个字符,不足以空格填充
char BS_FileSysType[8];// 文件系统类型,必须是8个字符,不足填充空格
};
#pragma pack(pop)
void PrintHeader(Fat12Header& rf, QString p)
{
QFile file(p);
if( file.open(QIODevice::ReadOnly) )
{
QDataStream in(&file);
file.seek(3);
in.readRawData(reinterpret_cast(&rf), sizeof(rf));
rf.BS_OEMName[7] = 0;
rf.BS_VolLab[10] = 0;
rf.BS_FileSysType[7] = 0;
qDebug() << "BS_OEMName: " << rf.BS_OEMName;
qDebug() << "BPB_BytsPerSec: " << hex << rf.BPB_BytsPerSec;
qDebug() << "BPB_SecPerClus: " << hex << rf.BPB_SecPerClus;
qDebug() << "BPB_RsvdSecCnt: " << hex << rf.BPB_RsvdSecCnt;
qDebug() << "BPB_NumFATs: " << hex << rf.BPB_NumFATs;
qDebug() << "BPB_RootEntCnt: " << hex << rf.BPB_RootEntCnt;
qDebug() << "BPB_TotSec16: " << hex << rf.BPB_TotSec16;
qDebug() << "BPB_Media: " << hex << rf.BPB_Media;
qDebug() << "BPB_FATSz16: " << hex << rf.BPB_FATSz16;
qDebug() << "BPB_SecPerTrk: " << hex << rf.BPB_SecPerTrk;
qDebug() << "BPB_NumHeads: " << hex << rf.BPB_NumHeads;
qDebug() << "BPB_HiddSec: " << hex << rf.BPB_HiddSec;
qDebug() << "BPB_TotSec32: " << hex << rf.BPB_TotSec32;
qDebug() << "BS_DrvNum: " << hex << rf.BS_DrvNum;
qDebug() << "BS_Reserved1: " << hex << rf.BS_Reserved1;
qDebug() << "BS_BootSig: " << hex << rf.BS_BootSig;
qDebug() << "BS_VolID: " << hex << rf.BS_VolID;
qDebug() << "BS_VolLab: " << rf.BS_VolLab;
qDebug() << "BS_FileSysType: " << rf.BS_FileSysType;
file.seek(510);
uchar b510 = 0;
uchar b511 = 0;
in.readRawData(reinterpret_cast(&b510), sizeof(b510));
in.readRawData(reinterpret_cast(&b511), sizeof(b511));
qDebug() << "Byte 510: " << hex << b510;
qDebug() << "Byte 511: " << hex << b511;
}
file.close();
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Fat12Header f12;
PrintHeader(f12, "E:\\data.img");
return a.exec();
}
200H = 512
实验结论
1.FreeDos中的format程序在格式化软盘的时候自动在第0扇区生成了一个主引导程序,这个主引导程序只打印一个字符串
2.文件格式和文件系统都是用于定义数据如何存放的规则,只要遵循这个规则就能够成功读写目标数据(参考PE文件结构)
如何在FAT12根目录中查找是否存在目标文件?
实验:读取FAT12文件系统的根目录信息
#include
#include
#include
#include
#include
#include
#include
#pragma pack(push)
#pragma pack(1)
struct Fat12Header
{
char BS_OEMName[8];
ushort BPB_BytsPerSec;
uchar BPB_SecPerClus;
ushort BPB_RsvdSecCnt;
uchar BPB_NumFATs;
ushort BPB_RootEntCnt;
ushort BPB_TotSec16;
uchar BPB_Media;
ushort BPB_FATSz16;
ushort BPB_SecPerTrk;
ushort BPB_NumHeads;
uint BPB_HiddSec;
uint BPB_TotSec32;
uchar BS_DrvNum;
uchar BS_Reserved1;
uchar BS_BootSig;
uint BS_VolID;
char BS_VolLab[11];
char BS_FileSysType[8];
};
struct RootEntry
{
char DIR_Name[11];
uchar DIR_Attr;
uchar reserve[10];
ushort DIR_WrtTime;
ushort DIR_WrtDate;
ushort DIR_FstClus;
uint DIR_FileSize;
};
#pragma pack(pop)
void PrintHeader(Fat12Header& rf, QString p)
{
QFile file(p);
if( file.open(QIODevice::ReadOnly) )
{
QDataStream in(&file);
file.seek(3);
in.readRawData(reinterpret_cast(&rf), sizeof(rf));
rf.BS_OEMName[7] = 0;
rf.BS_VolLab[10] = 0;
rf.BS_FileSysType[7] = 0;
qDebug() << "BS_OEMName: " << rf.BS_OEMName;
qDebug() << "BPB_BytsPerSec: " << hex << rf.BPB_BytsPerSec;
qDebug() << "BPB_SecPerClus: " << hex << rf.BPB_SecPerClus;
qDebug() << "BPB_RsvdSecCnt: " << hex << rf.BPB_RsvdSecCnt;
qDebug() << "BPB_NumFATs: " << hex << rf.BPB_NumFATs;
qDebug() << "BPB_RootEntCnt: " << hex << rf.BPB_RootEntCnt;
qDebug() << "BPB_TotSec16: " << hex << rf.BPB_TotSec16;
qDebug() << "BPB_Media: " << hex << rf.BPB_Media;
qDebug() << "BPB_FATSz16: " << hex << rf.BPB_FATSz16;
qDebug() << "BPB_SecPerTrk: " << hex << rf.BPB_SecPerTrk;
qDebug() << "BPB_NumHeads: " << hex << rf.BPB_NumHeads;
qDebug() << "BPB_HiddSec: " << hex << rf.BPB_HiddSec;
qDebug() << "BPB_TotSec32: " << hex << rf.BPB_TotSec32;
qDebug() << "BS_DrvNum: " << hex << rf.BS_DrvNum;
qDebug() << "BS_Reserved1: " << hex << rf.BS_Reserved1;
qDebug() << "BS_BootSig: " << hex << rf.BS_BootSig;
qDebug() << "BS_VolID: " << hex << rf.BS_VolID;
qDebug() << "BS_VolLab: " << rf.BS_VolLab;
qDebug() << "BS_FileSysType: " << rf.BS_FileSysType;
file.seek(510);
uchar b510 = 0;
uchar b511 = 0;
in.readRawData(reinterpret_cast(&b510), sizeof(b510));
in.readRawData(reinterpret_cast(&b511), sizeof(b511));
qDebug() << "Byte 510: " << hex << b510;
qDebug() << "Byte 511: " << hex << b511;
}
file.close();
}
// 返回第i个目录项
RootEntry FindRootEntry(Fat12Header& rf, QString p, int i)
{
RootEntry ret = {{0}};
QFile file(p);
// BPB_RootEntCnt:目录项个数
if( file.open(QIODevice::ReadOnly) && (0 <= i) && (i < rf.BPB_RootEntCnt) )
{
QDataStream in(&file);
// 定位到第i个目录项
file.seek(19 * rf.BPB_BytsPerSec + i * sizeof(RootEntry));
in.readRawData(reinterpret_cast(&ret), sizeof(ret));
}
file.close();
return ret;
}
// 根据文件名查找目录项
RootEntry FindRootEntry(Fat12Header& rf, QString p, QString fn)
{
RootEntry ret = {{0}};
for(int i=0; i= 0 )
{
QString n = fn.mid(0, d);
QString p = fn.mid(d + 1);
if( name.startsWith(n) && name.endsWith(p) )
{
ret = re;
break;
}
}
else
{
if( fn == name )
{
ret = re;
break;
}
}
}
}
return ret;
}
void PrintRootEntry(Fat12Header& rf, QString p)
{
for(int i=0; i
对于多出来的目录项不必理会
文件以簇为单位存储,Fat12文件系统1簇等于1扇区,那么一个文件大于512字节如何存储?
实验:加载FAT12中的文件数据
- 步骤:
(1)在根目录区查找目标文件对应的项
(2)获取目标文件的起始簇号和文件大小
(3)根据FAT表中记录的逻辑先后关系读取数据
小贴士
- FAT表中的每个表项只占用12比特(1.5字节)
- FAT表一共记录了BPB_BytsPerSec * 9 * 2 / 3个表项 (9个扇区 * 512 /1.5)
- 可以使用一个short表示一个表项的值
- 如果表项值大于等于0xFF8,则说明已经到达最后一个簇
- 如果表项值等于0xFF7,则说明当前簇已经损坏
- 数据区起始簇(扇区)号为33,地址为0x4200(33 * 512)
- 数据区起始地址所对应的编号为2(不为0)(有了起始地址,这里可以用偏移地址)
- 因此,DIR_FstClus对应的地址为:0×4200+(DIR_FstClus-2)*512
编程实验
读取指定文件内容
#include
#include
#include
#include
#include
#include
#pragma pack(push)
#pragma pack(1)
struct Fat12Header
{
char BS_OEMName[8];
ushort BPB_BytsPerSec;
uchar BPB_SecPerClus;
ushort BPB_RsvdSecCnt;
uchar BPB_NumFATs;
ushort BPB_RootEntCnt;
ushort BPB_TotSec16;
uchar BPB_Media;
ushort BPB_FATSz16;
ushort BPB_SecPerTrk;
ushort BPB_NumHeads;
uint BPB_HiddSec;
uint BPB_TotSec32;
uchar BS_DrvNum;
uchar BS_Reserved1;
uchar BS_BootSig;
uint BS_VolID;
char BS_VolLab[11];
char BS_FileSysType[8];
};
struct RootEntry
{
char DIR_Name[11];
uchar DIR_Attr;
uchar reserve[10];
ushort DIR_WrtTime;
ushort DIR_WrtDate;
ushort DIR_FstClus;
uint DIR_FileSize;
};
#pragma pack(pop)
void PrintHeader(Fat12Header& rf, QString p)
{
QFile file(p);
if( file.open(QIODevice::ReadOnly) )
{
QDataStream in(&file);
file.seek(3);
in.readRawData(reinterpret_cast(&rf), sizeof(rf));
rf.BS_OEMName[7] = 0;
rf.BS_VolLab[10] = 0;
rf.BS_FileSysType[7] = 0;
qDebug() << "BS_OEMName: " << rf.BS_OEMName;
qDebug() << "BPB_BytsPerSec: " << hex << rf.BPB_BytsPerSec;
qDebug() << "BPB_SecPerClus: " << hex << rf.BPB_SecPerClus;
qDebug() << "BPB_RsvdSecCnt: " << hex << rf.BPB_RsvdSecCnt;
qDebug() << "BPB_NumFATs: " << hex << rf.BPB_NumFATs;
qDebug() << "BPB_RootEntCnt: " << hex << rf.BPB_RootEntCnt;
qDebug() << "BPB_TotSec16: " << hex << rf.BPB_TotSec16;
qDebug() << "BPB_Media: " << hex << rf.BPB_Media;
qDebug() << "BPB_FATSz16: " << hex << rf.BPB_FATSz16;
qDebug() << "BPB_SecPerTrk: " << hex << rf.BPB_SecPerTrk;
qDebug() << "BPB_NumHeads: " << hex << rf.BPB_NumHeads;
qDebug() << "BPB_HiddSec: " << hex << rf.BPB_HiddSec;
qDebug() << "BPB_TotSec32: " << hex << rf.BPB_TotSec32;
qDebug() << "BS_DrvNum: " << hex << rf.BS_DrvNum;
qDebug() << "BS_Reserved1: " << hex << rf.BS_Reserved1;
qDebug() << "BS_BootSig: " << hex << rf.BS_BootSig;
qDebug() << "BS_VolID: " << hex << rf.BS_VolID;
qDebug() << "BS_VolLab: " << rf.BS_VolLab;
qDebug() << "BS_FileSysType: " << rf.BS_FileSysType;
file.seek(510);
uchar b510 = 0;
uchar b511 = 0;
in.readRawData(reinterpret_cast(&b510), sizeof(b510));
in.readRawData(reinterpret_cast(&b511), sizeof(b511));
qDebug() << "Byte 510: " << hex << b510;
qDebug() << "Byte 511: " << hex << b511;
}
file.close();
}
RootEntry FindRootEntry(Fat12Header& rf, QString p, int i)
{
RootEntry ret = {{0}};
QFile file(p);
if( file.open(QIODevice::ReadOnly) && (0 <= i) && (i < rf.BPB_RootEntCnt) )
{
QDataStream in(&file);
file.seek(19 * rf.BPB_BytsPerSec + i * sizeof(RootEntry));
in.readRawData(reinterpret_cast(&ret), sizeof(ret));
}
file.close();
return ret;
}
// 根据文件名找到对应目录项
RootEntry FindRootEntry(Fat12Header& rf, QString p, QString fn)
{
RootEntry ret = {{0}};
for(int i=0; i= 0 )
{
QString n = fn.mid(0, d);
QString p = fn.mid(d + 1);
if( name.startsWith(n) && name.endsWith(p) )
{
ret = re;
break;
}
}
else
{
if( fn == name )
{
ret = re;
break;
}
}
}
}
return ret;
}
// 打印存在的目录项各属性
void PrintRootEntry(Fat12Header& rf, QString p)
{
for(int i=0; i ReadFat(Fat12Header& rf, QString p)
{
QFile file(p);
// FAT表大小
int size = rf.BPB_BytsPerSec * 9;
uchar* fat = new uchar[size];
QVector ret(size * 2 / 3, 0xFFFF); // FAT表项个数和默认值
if( file.open(QIODevice::ReadOnly) )
{
QDataStream in(&file);
// 定位到FAT表的起始位置(主引导区后面的扇区)
file.seek(rf.BPB_BytsPerSec * 1);
in.readRawData(reinterpret_cast(fat), size);
for(int i=0, j=0; i((fat[i+1] & 0x0F) << 8) | fat[i];
ret[j+1] = static_cast(fat[i+2] << 4) | ((fat[i+1] >> 4) & 0x0F);
}
}
file.close();
delete[] fat;
return ret;
}
// 读取指定文件名的内容
QByteArray ReadFileContent(Fat12Header& rf, QString p, QString fn)
{
QByteArray ret;
RootEntry re = FindRootEntry(rf, p, fn);
if( re.DIR_Name[0] != '\0' )
{
QVector vec = ReadFat(rf, p);
QFile file(p);
if( file.open(QIODevice::ReadOnly) )
{
char buf[512] = {0};
QDataStream in(&file);
int count = 0;
ret.resize(re.DIR_FileSize);
for(int i=0, j=re.DIR_FstClus; j<0xFF7; i+=512, j=vec[j])
{
file.seek(rf.BPB_BytsPerSec * (33 + j - 2));
in.readRawData(buf, sizeof(buf));
for(uint k=0; k
加载新程序需要依赖于文件系统
FAT12是一种早期用于软盘的简单文件系统
FAT12文件系统的重要信息存储于0扇区
FAT12根目录区记录了文件的起始簇号和长度
通过查找根目录区能够确定是否存在目标文件
FAT12文件数据的组织使用了单链表的思想
- 文件数据离散的分布于存储介质中
- 文件数据通过FAT项进行关联