详解FAT12文件系统

目录

1、详解FAT12文件系统

2、实验

1、向虚拟软盘写入文件

2、读取软盘文件系统信息

3、在根目录中查找目标文件

4、读取目标文件到内存

3、小结


1、FAT12文件系统

文件系统

    - 存储介质上组织文件数据的方法(数据组织的方式

详解FAT12文件系统_第1张图片

文件系统示例

    - FAT12是DOS时代的早期文件系统

    - FAT12结构非常简单,一直沿用于软盘

    - FAT12的基本组织单位

           字节(Byte):基本数据单位

           扇区(Sector):磁盘中的最小数据单元

           簇(Cluster):一个或多个扇区

深入FAT12文件系统

          FAT12文件系统由引导区,FAT表,根目录项表和文件数据区组成。

详解FAT12文件系统_第2张图片

FAT12的主引导区

            主引导区存储的比较重要的信息是文件系统的类型,文件系统逻辑扇区总数,

            每簇包含的扇区数等。主引导区最后以0x55AA两个字节作为结束,共占用一个扇区。

详解FAT12文件系统_第3张图片

二进制分析:(data.img由后文生成)

详解FAT12文件系统_第4张图片

根目录区的大小和位置

详解FAT12文件系统_第5张图片

                                                       可以看到根目录区起始于第19扇区

根目录区的目录项

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

详解FAT12文件系统_第6张图片

//创建 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文件系统_第7张图片

FAT12数据逻辑组织示意

详解FAT12文件系统_第8张图片


2、实验

模拟Boot 查找目标文件(Loader),并读取文件的内容!- 突破Boot512字节限制的解决方案

解决方案(★)

    - 使用FAT12对软盘(data.img)进行格式化

    - 编写可执行程序(Loader),并将其拷贝到软盘中

    - 主引导程序(Boot)在文件系统中查找Loader

    - 将Loader复制到内存中,并跳转到入口处执行


1、向虚拟软盘写入文件

实验:往虚拟软盘中写入文件

    - 原材料:FreeDos,Bochs,bximage

    - 步骤:

             ① 创建虚拟软盘data.img

             ② 在FreeDos中进行格式化(FAT12)

             ③ 将data.img挂载到Linux中,并写入文件

1、先创建一个虚拟软盘 data.img

详解FAT12文件系统_第9张图片

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

详解FAT12文件系统_第10张图片

详解FAT12文件系统_第11张图片

3、将data.img挂载到Linux中,并写入loader文件

详解FAT12文件系统_第12张图片

详解FAT12文件系统_第13张图片

                                     成功向FAT12格式的软盘写入文件


2、读取软盘文件系统信息

实验:读取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();
}

详解FAT12文件系统_第14张图片

                                  200H = 512

实验结论

1.FreeDos中的format程序在格式化软盘的时候自动在第0扇区生成了一个主引导程序,这个主引导程序只打印一个字符串

2.文件格式和文件系统都是用于定义数据如何存放的规则,只要遵循这个规则就能够成功读写目标数据(参考PE文件结构)


3、在根目录中查找目标文件

如何在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文件系统_第15张图片

            对于多出来的目录项不必理会

详解FAT12文件系统_第16张图片

文件以簇为单位存储,Fat12文件系统1簇等于1扇区,那么一个文件大于512字节如何存储?


4、读取目标文件到内存

实验:加载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

详解FAT12文件系统_第17张图片

编程实验

读取指定文件内容

详解FAT12文件系统_第18张图片

#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文件系统_第19张图片

 

3、小结

加载新程序需要依赖于文件系统

FAT12是一种早期用于软盘的简单文件系统

FAT12文件系统的重要信息存储于0扇区

FAT12根目录区记录了文件的起始簇号和长度

通过查找根目录区能够确定是否存在目标文件

FAT12文件数据的组织使用了单链表的思想

     - 文件数据离散的分布于存储介质中

     - 文件数据通过FAT项进行关联

 

你可能感兴趣的:(操作系统专题【笔记】)