千家信息网

简单介绍主引导程序的扩展知识

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,亿速云服务器与全球多个国家顶级机房直接合作,提供包括香港、美国、日本等国家和地区的服务器,需要的请联系官方客服! 优质的服务器租用!我们在前面讲解了主引导程序的基础知识,今天我们就来讲讲主引导程序的扩
千家信息网最后更新 2026年11月02日简单介绍主引导程序的扩展知识

亿速云服务器与全球多个国家顶级机房直接合作,提供包括香港、美国、日本等国家和地区的服务器,需要的请联系官方客服! 优质的服务器租用!

我们在前面讲解了主引导程序的基础知识,今天我们就来讲讲主引导程序的扩展。 不过主引导程序有个限制,那便是主引导程序的代码量不能超过 512 字节!那么我们如何来突破这个限制呢?

我们现在的基本思路是在主引导程序中做如下工作:

1、完成最基本的初始化工作;

2、从存储介质中加载程序到内存中;

3、将控制权交由新加载的程序执行;

4、......


那么我们具体该如何做呢?思路如下图所示

那么主引导程序应如何来加载存储介质中的其他程序呢?通过一定的文件系统的格式。那么什么是文件系统呢?它是指存储介质上组织文件数据的方法(数据组织的方式)。我们在此处以 FAT12 文件格式为例来进行讲解,为什么选用它呢?因为它的文件格式最为简单,也最适用于用来学习。FAT12 文件格式如下图所示

FAT12 是 DOS 时代的早期文件系统;它的结构非常简单,一直沿用于软盘;它的基本组织单位有 3 个:字节(Byte)是基本的数据单位,扇区(Sector)是磁盘中的最小数据单元,簇(Cluster)是一个或多个扇区。我们的解决方案便是:1、使用 FAT12 对软盘(data.img)进行格式化;2、编写可执行程序(Loader),并将其拷贝到软盘中;3、主引导程序(boot)在文件系统中查找 Loader;4、将 Loader 复制到内存中,并跳转到入口地址处执行。

我们下来要进行的实验是:往虚拟软盘中写入文件。原材料有 FreeDos,Bochs,bximage。步骤是:1、创建虚拟软盘(data.img);2、在 FreeDos 中进行格式化(FAT12);3、将 data.img 挂载到 Linux 中,并写入文件。

我们先在 Linux 中创建一张虚拟软盘,然后将其挂载到 /mnt/hgfs/ 目录下(命令是 mount -o loop data.img /mnt/hgfs/),然后在 Linux 中创建两个文件 test.txt 和 loader.bin。test.txt 的内容是 This is test for virtual floopy ...,loader.bin 中的内容是很多行的 D.T.Software. ;然后将他们两个拷贝到虚拟软盘中(拷贝到 /mnt/hgfs/ 目录下),最后进行虚拟软盘的卸载(umount /mnt/hgfs/)。我们看看虚拟软盘中的文件

我们看到在虚拟软盘中已经有两个文件了,再来看看他们的内容

我们看到已经出现了我们所写的文件内容。那么我们现在已经有了一张虚拟软盘,里面包含有我们自己写的文件内容。那么我们现在已经做完了前两步了,下来我们的工作是 boot 查找目标文件(Loader),并读取文件的内容!下来我们来深入看看 FAT12 文件系统,FAT12 文件系统由引导区、FAT 表、根目录项表和文件数据组成。如下图所示

FAT12 的主引导区存储的比较重要的信息是文件系统的类型,文件系统逻辑扇区总数,每簇包含的扇区数等。主引导区最后以 0x55AA 拉你个字节作为结束,共占用一个扇区。它的信息如下图所示

下一步的是实验是读取 data.img 中的文件系统信息。步骤:1、创建 Fat12Header 结构体类型;2、使用文件流读取前 512 字节的内容;3、解析并打印相关的信息。下来我们进行相关的实验,我们以 C++ 语言来写出一个解析 Fat12Header 的程序,源码如下所示

#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];};#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;    // 将数组的最后一个成员赋 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.close();}int main(int argc, char *argv[]){    QCoreApplication a(argc, argv);    Fat12Header f12;    PrintHeader(f12, "E:\\data.img");        return a.exec();}

编译结果如下

我们看到输出信息和上面的表格是一一对应的,下来我们在 PrintHeader 函数中添加几行代码,如下

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;

我们定位到 510 字节处,看看 510 字节和 511 字节处是什么内容

是最后的两个字节 55 和 aa。我们再来看看在创建的虚拟软盘中启动的时候,它会打印出什么,如下

我们看到这个空的虚拟软盘在开机时输出的是这一行字符串,那么这行字符串是哪来的呢?通过上面的实验,我们可以得出下面的结论:1、在 FreeDos 中的 format 程序在格式化软盘的时候自动在第 0 扇区生成了一个主引导程序,这个主引导程序只打印一个字符串;2、文件格式和文件系统都是用于定义数据如何存放的规则,只要遵循这个规则就能成功读写目标数据。我们下来的问题就是如何在 FAT12 根目录中查找是否存在目标文件呢?我们首先来看看根目录区的大小和位置,如下

我们看到根目录区中的第19个扇区中存放的是目录文件项,它的大小为 7168 个字节,计算公式如上图所示。FAT12 文件系统中的根目录区是由目录项构成,每一个目录项代表根目录中的一个文件索引。一些数据成员如下图所示

下来我们就进行一个实验:读取 FAT12 文件系统的根目录信息。

步骤如下:

1、创建 RootEntry 结构体类型;

2、使用文件流顺序读取每个项的内容;

3、解析并打印相关的信息。


我们在进行实验之前,还是先来介绍下目录项中的一些关键成员吧。a> DIR_Name 文件名(用于判断是否为目标文件);b> DIR_FstClus 文件数据起始存储位置(用于确定读取位置);c> DIR_FileSize 文件大小(用于确定读取的字节数)。

我们还是基于 C++ 语言来写这个程序,源码如下

#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;}void PrintRootEntry(Fat12Header& rf, QString p){    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;}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);    return a.exec();}

我们来看看输出信息

我们看到在输出的信息中就有我们自己写的 test.txt 文件和 loader.bin 文件。下来我们来使用下 FindRootEntry 这个函数来进行文件的查找,在 main.cpp 中添加如下代码

    RootEntry re = FindRootEntry(f12, img, "LOADER.BIN");        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;

我们来看看会输出什么信息

我们看到输出的是 loader.bin 文件的相关信息,我们将其换成 test.txt 呢,看看结果

我们再来随便写个文件名试试呢

我们看到打印的全是 0。下来我们来看看 FAT 表中的先后关系,它是以簇(扇区)为单位存储文件数据,每个表项(vec[i])表示文件数据的实际位置(簇)。即 DIR_FstClus 表示文件第 0 簇(扇区)的位置,vec[DIR_FstClus] 表示文件第 1 簇(扇区)的位置;vec[vec[DIR_FstClus]] 表示文件第 2 簇(扇区)的位置。下面来看看 FAT12 数据物理组织示意图,如下如所示

即比如它的起始为 C,然后下一个表项为 O,O 指向的下一个表项又是 Z,以此类推直至最后的 S 指向的表项为 NULL。其实它的数据组织方式和我们之前所接触的单链表是有点类似的,它的逻辑组织示意图如下图所示

下来我们再来进行一个实验,加载 FAT12 中的文件数据。

步骤如下:

1、在根目录区查找目标文件对应的项;

2、获取目标文件的起始簇号和文件大小;

3、根据 FAT 表中记录的逻辑先后关系读取数据。


值得注意的是:FAT 表中的每个表只占用 12 比特(1.5字节);FAT 表一共记录了 BPB_BytsPerSec * 9 * 2 / 3 个表项;可以使用一个 short 表示一个表项的值;如果表项值大于等于 0xFF8,这说明已经到达最后一个簇;如果表项值等于 0xFF7,则说明当前簇已经损坏;数据区起始簇(扇区)号为 33,地址为 0x4200;数据区起始地址所对应的标号为 2 (不为 0);因此,DIR_FstClus 对应的地址为:0x4200 + (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;}void PrintRootEntry(Fat12Header& rf, QString p){    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;}QVector ReadFat(Fat12Header& rf, QString p){    QFile file(p);    int size = rf.BPB_BytsPerSec * 9;    uchar* fat = new uchar[size];    QVector ret(size * 2 / 3, 0xFFFF);    if( file.open(QIODevice::ReadOnly) )  {        QDataStream in(&file);        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

我们来看看打印信息

这便是我们之前自己写的内容,说明已经成功获取到了。我们再来随便写个文件的名字,看看会获取到什么

我们看到是空的,什么也没有。直到现在,我们已经对主引导程序中的文件成功的进行了加载并解析内容。通过今天对主引导程序扩展的学习,总结如下:1、主引导程序的代码量不超过 512 字节,可以通过主引导程序加载新程序的方式突破限制;2、加载新程序需要依赖于文件系统;3、FAT12 是一种早期用于软盘的简单文件系统,它的重要信息存储于 0 扇区;4、FAT12 根目录区记录了文件的起始簇号和长度;5、通过查找根目录区能够确定是否存在目标文件;6、FAT12 文件数据的组织使用了单链表的思想,文件数据离散的分布于存储介质中,文件数据通过 FAT 项进行关联。

的服务器不仅具有高稳定性,高速访问,而且易于管理,安全和轻松使用,以减少用户在服务器维护中的能量和时间成本,并专注于自己的业务的开发和推广。服务器,致力于为用户提供性价比最高的服务器!



文件 程序 数据 系统 软盘 信息 字节 内容 根目录 格式 存储 位置 服务器 目标 实验 服务 目录 起始 输出 代码 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 手冲咖啡和咖啡机区别 重生之互联网科技帝国 安徽鑫晟辉互联网科技 朗涯互联网科技 苏州互联网科技贷款公司 互联网 新科技 玖二柒互联网科技公司待遇 现代互联网科技股 互联网对科技的发展 呼和浩特新华互联网科技学院 烟台灵鸽互联网科技有限公司 互联网第三次科技革命特征 工业互联网科技创业 米泉互联网科技专业怎么样 北京军民互联网科技公司 互联网科技和医药行业 优雇互联网科技有限公司 现代科技的标志之一的互联网 每日互联网科技新闻 山东亿川互联网科技有限责任公司 互联网科技论文 互联网科技公司的市场部门 互联网之后的科技是什么 团飞科技与伯堂互联网服务平台 南山科技园互联网公司有哪些 湖南一路畅行互联网科技 互联网科技机房 玉溪新华互联网科技怎么选 深圳互联网科技园地址 互联网科技创业计划的风险分析
0