外观
05.主引导程序的扩展(下)
约 1932 字大约 6 分钟
嵌入式主引导程序单片机x86
2022-06-04
1、问题
如何在FAT12根目录中查找是否存在目标文件?
2、根目录区的大小和位置

3、FAT12文件系统中的根目录区
根目录区由目录项构成,每一个目录项代表根目录中的一个文件索引。

对于fat12文件系统而言,1簇就是1扇区
4、实验:读取FAT12文件系统的根目录信息
- 步骤:
- 创建 RootEntry 结构体类型
- 使用文件流顺序读取每个项的内容
- 解析并打印相关的信息
5、编程实验:读取根目录信息




6、目录项的中的关键成员
DIR_Name
- 文件名(用于判断是否为目标文件)
DIR_FstClus
- 文件数据起始存储位置(用于确定读取位置)
DIR_FileSize
- 文件大小(用于确定读取的字节数)(在FAT12中,1簇就是1扇区就是512字节)
7、FAT表 - FAT12的数据组织核心
- FAT1和 FAT2是相互备份的关系,数据内容完全一致
- FAT表是一个关系图,记录了文件数据的先后关系
- 每一个FAT表项暂用12比特
- FAT表的前2个表项规定不使用
8、FAT表中的先后关系
- 以簇(扇区)为单位存储文件数据
- 每个表项(vec[i])表示文件数据的实际位置(簇)
- DIR_FstClus表示文件第0簇(扇区)的位置
- vec[DIR_FstClus]表示文件第1簇(扇区)的位置
- vec[ vec[DIR_FstClus]]表示文件第2簇(扇区)的位置
- 。。。。。。
9、FAT12数据物理组织示意

eg:首地址C的地址可以根据目录项获取,通过C地址数据簇所对应的FAT表项,拿到下一个数据簇O的地址。….直到结束符。
10、FAT12数据逻辑组织示意

11、实验︰加载FAT12中的文件数据
- 步骤:
- 在根目录区查找目标文件对应的项
- 获取目标文件的起始簇号和文件大小
- 根据FAT表中记录的逻辑先后关系读取数据
12、小贴士一
- FAT表中的每个表项只占用12比特(1.5字节)
- FAT表一共记录了BPB_BytsPerSec * 9* 2/3个表项
- 可以使用一个short表示一个表项的值
- 如果表项值大于等于0xFF8,则说明已经到达最后一个簇
- 如果表项值等于0xFF7,则说明当前簇已经损坏
13、小贴士二
- 数据区起始簇(扇区)号为33,地址为0x4200
- 数据区起始地址所对应的编号为2(不为0)
- 因此,DIR_FstClus 对应的地址为:
0x4200 +(DIR_FstClus - 2) * 512
0x4200 对应的是 编号为2处的地址
14、编程实验:读取指定文件内容
附最后
15、小结
- FAT12根目录区记录了文件的起始簇号和长度
- 通过查找根目录区能够确定是否存在目标文件
- FAT12文件数据的组织使用了单链表的思想
- 文件数据离散的分布于存储介质中
- 文件数据通过FAT项进行关联
#include <QtCore/QCoreApplication>
#include <QFile>
#include <QDataStream>
#include <QDebug>
#include <QVector>
#include <QByteArray>
#pragma pack(push)
#pragma pack(1) // 定义结构体按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)
// 读取fat12中与文件系统相关的信息
void PrintHeader(Fat12Header& rf, QString p) // p:虚拟软盘文件路径路径
{
QFile file(p);// 创建文件对象
if( file.open(QIODevice::ReadOnly) )// 只读的形式打开
{
QDataStream in(&file);//通过数据流开始读取
file.seek(3);// 偏移到510字节
in.readRawData(reinterpret_cast<char*>(&rf), sizeof(rf));// 读取所有的数据,即结构体对象的数据
// 将数组以字符串形式打印出来,所以将数组最后一个元素赋值为0
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);// 偏移到510字节
uchar b510 = 0;
uchar b511 = 0;
in.readRawData(reinterpret_cast<char*>(&b510), sizeof(b510));
in.readRawData(reinterpret_cast<char*>(&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) )// 只读的方式打开,根目录区中,BPB_RootEntCnt最多有多少个文件项
{
QDataStream in(&file);// 创建流对象
file.seek(19 * rf.BPB_BytsPerSec + i * sizeof(RootEntry));// 定位到根目录区的起始位置 - 通过查表得知,起始位置在第19扇区
// 偏移i个结构体
in.readRawData(reinterpret_cast<char*>(&ret), sizeof(ret));// 读取数据
}
file.close();
return ret;
}
// 根据文件名 DIR_Name 判断是否为目标文件
RootEntry FindRootEntry(Fat12Header& rf, QString p, QString fn)
{
RootEntry ret = {
{0}};
for(int i=0; i<rf.BPB_RootEntCnt; i++)
{
RootEntry re = FindRootEntry(rf, p, i);
if( re.DIR_Name[0] != '\0' )
{
int d = fn.lastIndexOf(".");//文件后缀
QString name = QString(re.DIR_Name).trimmed();// 获取遍历获得的文件名
if( d >= 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<rf.BPB_RootEntCnt; i++)
{
RootEntry re = FindRootEntry(rf, p, i);
if( re.DIR_Name[0] != '\0' )//通过文件名过滤
{
qDebug() << i << ":";
qDebug() << "DIR_Name: " << hex << re.DIR_Name;
qDebug() << "DIR_Attr: " << hex << re.DIR_Attr;
qDebug() << "DIR_WrtDate: " << hex << re.DIR_WrtDate;
qDebug() << "DIR_WrtTime: " << hex << re.DIR_WrtTime;
qDebug() << "DIR_FstClus: " << hex << re.DIR_FstClus;
qDebug() << "DIR_FileSize: " << hex << re.DIR_FileSize;
}
}
}
// 读取FAT表
QVector<ushort> ReadFat(Fat12Header& rf, QString p)// 返回值为short数组 p:虚拟软盘文件路径路径
{
QFile file(p);// 打开文件
int size = rf.BPB_BytsPerSec * 9;// 计算FAT表的大小
uchar* fat = new uchar[size];// 存储用于从文件系统中读取出来的FAT表
QVector<ushort> ret(size * 2 / 3, 0xFFFF); // 总表项数,默认值
if( file.open(QIODevice::ReadOnly) )// 打开文件,只读0
{
QDataStream in(&file);
file.seek(rf.BPB_BytsPerSec * 1);// 定位到FAT表的起始位置
in.readRawData(reinterpret_cast<char*>(fat), size);// 读取所有表项
for(int i=0, j=0; i<size; i+=3, j+=2)
{
ret[j] = static_cast<ushort>((fat[i+1] & 0x0F) << 8) | fat[i];
ret[j+1] = static_cast<ushort>(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<ushort> 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<sizeof(buf); k++)
{
if( count < ret.size() )
{
ret[i+k] = buf[k];
count++;
}
}
}
}
file.close();
}
return ret;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QString img = "E:\\data.img";
Fat12Header f12;
qDebug() << "Read Header:";
PrintHeader(f12, img);
qDebug() << endl;
qDebug() << "Print Root Entry:";
PrintRootEntry(f12, img);
qDebug() << endl;
qDebug() << "Print File Content:";
QString content = QString(ReadFileContent(f12, img, "DELPHI.DT"));
qDebug() << content;
return a.exec();
}