第四载、FAT12文件系统剖析2

在上一章节《FAT12文件系统剖析1》中,我们把a.img启动软盘使用FreeDos系统格式化为FAT12文件系统的组织方式,并且向a.img软盘写入两个文件,同时通过FreeDos操作系统也查看了a.img软盘中的文件。之所以这么做,是因为主引导程序不可以超过512字节,所以主引导程序需要在基本的初始化工作完成后加载启动介质(这里是软盘a.img)中的启动程序到内存,然后跳转到对应内存处执行。

上一节我们是通过FreeDos操作系统来查看a.img软盘中FAT12根目录的目标文件,那么在程序中我们怎去判断目标文件是否存在FAT12根目录中?要解决这个问题,我们首先来了解FAT12的根目录在文件系统中的位置
第四载、FAT12文件系统剖析2_第1张图片

上图中直接给了计算根目录大小的公式:根目录个数 * 每个根目录项大小 / 每个扇区大小 = 根目录占用的扇区数量,
其中根目录个数在上一章节我们已经求出,为0xe0,十进制为224,不熟悉的可以参考上一章节。每个根目录项大小为32字节,每个扇区大小为512字节,那么有 224 * 32 / 512 = 14,即根目录文件项在FAT12文件系统中总共占用14个扇区,也就是7168字节。

除了上边说的根目录区,我们还需要了解下根目录项,根目录由根目录项组成,上文中的224,就是FAT12文件系统有224个根目录项,一个根目录项代表根目录中的一个文件索引,每个根目录项中,包含了这个文件的基本信息,如下:
第四载、FAT12文件系统剖析2_第2张图片

下边需要做的实验,便是读取FAT12中每个根目录项的内容并打印(代码在上章代码基础上增加更目录项的打印)

#include 
#include 
#include 
#include 

#pragma pack(push)
#pragma pack(1)

struct Fat12Header
{
    char BS_OEMName[8];
    ushort BPB_BytsPerSec;  //每扇区字节数,默认512字节
    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);   //偏移开头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);  //偏移512字节的最后两字节处

        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);

    //BPB_RootEntCnt为最大根目录文件数
    if( file.open(QIODevice::ReadOnly) && (0 <= i) && (i < rf.BPB_RootEntCnt) )
    {
        QDataStream in(&file);

        //定位到19扇区的各个根目录项开始处
        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文件系统剖析2_第3张图片

从输出看到,已经成功打印了我们上章节往a.img软盘拷贝的两个文件,test.txt和loader.bin ,置于其他两个文件输出,暂时不需要理会,这是FAT12文件系统默认的处理方式;同时也可以打印指定的文件信息。

以上我们打印了根目录目录项的信息和指定文件的文件信息,那么下边需要做的就是读取文件指定文件内容,在上述读取的目录项信息中,对我们比较有用的是如下三个成员:
第四载、FAT12文件系统剖析2_第4张图片
理论上有了文件名、文件数据存储起始位置、文件大小,就可以读取文件的内容,但是在FAT12中,需要知道的是:文件数据不是连续存储的,可能是存储于不同的扇区中,也就是分散存储的。为了读取分散在不同扇区的内容,需要借助FAT表项来组织存储在不同扇区上的数据,这里可以理解为数据结构中的链表,虽然是分散的,但是读取过程给人的感觉是读的一片连续的内存。

FAT12文件系统中有两个FAT表项(FAT1和FAT2),他们内容是一样的,这里我们只关注FAT1即可,下图为FAT表与数据区物理组织示意图:
第四载、FAT12文件系统剖析2_第5张图片
当我们读取一个文件项的内容时,可以获取DIR_FstClus(即存储的第一个扇区的位置),这里用C表示,当我们读完C内存处的数据库时,需要读下一个内存数据,对应到FAT表中查找位置,得到内存O的地址,然后读取数据区中O内存处的数据,一次类推,直到最后一个内存S对应的FAT表项没有内存地址(读取结束),这个过程,可以把数据区的C->O->Z->Q->S共5片内存的数据全部读取完成。FAT表与数据区的关系更直观一点则如下逻辑示意图:
第四载、FAT12文件系统剖析2_第6张图片
逻辑示意图跟数据结构里边的链表是一样的。

以上是FAT表与数据区的基本关系,下边就通过FAT表来读取a.img软盘中的文件内容,在此之前有一些小点需要注意:
第四载、FAT12文件系统剖析2_第7张图片
第四载、FAT12文件系统剖析2_第8张图片
以下是代码,在上文代码基础上增加两个函数:获取FAT表ReadFat、读取文件内容ReadFileContent
 

#include 
#include 
#include 
#include 

#pragma pack(push)
#pragma pack(1)

struct Fat12Header
{
    char BS_OEMName[8];
    ushort BPB_BytsPerSec;  //每扇区字节数,默认512字节
    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);   //偏移开头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);  //偏移512字节的最后两字节处

        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);

    //BPB_RootEntCnt为最大根目录文件数
    if( file.open(QIODevice::ReadOnly) && (0 <= i) && (i < rf.BPB_RootEntCnt) )
    {
        QDataStream in(&file);

        //定位到19扇区的各个根目录项开始处
        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);
    int size = rf.BPB_BytsPerSec * 9; //FAT表占用的大小(9个扇区字节)
    uchar* fat = new uchar[size];
    QVector ret(size * 2 / 3, 0xFFFF); //每个FAT表项占用1.5字节,FAT表个数为: 占用内存 / 1.5,默认值为0xFFFF

    if( file.open(QIODevice::ReadOnly) )
    {
        QDataStream in(&file);

        file.seek(rf.BPB_BytsPerSec * 1); //定位到第一个扇区(FAT表起始扇区)

        in.readRawData(reinterpret_cast(fat), size); //读取FAT表内容

        //分配规划FAT表
        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); //获取FAT表
        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])
            {
                //定位到文件数据扇区(33区),数据区起始地址对应编号为2,所以要-2
                file.seek(rf.BPB_BytsPerSec * (33 + j - 2));

                in.readRawData(buf, sizeof(buf));

                for(uint k=0; k
main函数中调用读取文件内容函数ReadFileContent,读取TEST.TXT文件内容

如上便是TEXT.TXT文件中的内容。

如果对文件内容的读取不大理解,暂时不需要深究,只要知道FAT12文件系统是通过FAT表来将不同扇区的文件数据组织起来的,也就是数据结构中的单链表思想。

总结:
    1、FAT12根目录记录了文件的文件名起始簇号文件长度
    2、通过查找根目录区能够确定是否存在目标文件
    3、FAT12文件数据离散的分布于存储介质不同的扇区中
    4、文件数据通过FAT表进行关联,采用了单链表的思想

学自 --《狄泰软件学院》- 门徒操作系统

 

你可能感兴趣的:(操作系统连载)