想象你从老家飞往一座山顶度假村。按下电脑电源,就像拖着行李箱出门:BIOS是送你到机场的出租车司机,他把车稳稳停在机场航站楼门口——主引导记录(MBR)。接待大厅里,地勤小哥(stage1)手里只有一张登机牌大小的工作手册,512字节,他唯一会干的事,就是帮你叫来摆渡车(stage1.5)。摆渡车司机认识所有汽车品牌(文件系统),绕到核心候机楼(/boot/grub),把你送上一辆大巴(stage2)。大巴司机查看航班大屏(grub.cfg),算出该去哪个登机口,帮你把行李托运好(加载内核)。登机口的地勤会按机场《统一服务手册》(Multiboot规范)核对你的机票(头部魔数与校验和)。一切无误,你大步登机,飞向4GB大小的巡航空域(保护模式)。这篇文章把这段「机场转机」从头走到尾——理解这个流程,你就掌握了计算机最底层的开机舞蹈。

岗位关联

一句话价值:GRUB把最脏最累的启动活全包了,让你专注写内核本身。读懂多引导规范与阶段引导,是OS开发者的第一课,也是云端批量装机和嵌入式OTA的底层功底。

目录
  1. 可视化演示
  2. 启动流程全貌
  3. 多引导规范
  4. 阶段引导流程
  5. 配置文件语法
  6. 内核镜像格式
  7. 保护模式切换
  8. 构建与排错
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示GRUB多阶段引导加载流程与Multiboot规范:引导阶段模式演示BIOS到内核的五步启动链;头部结构模式展示Multiboot魔数与信息字段;内存布局模式展示内核加载地址与GRUB信息结构传递。

本节配有交互动效(进入视口后自动加载,约 3KB)。

引导阶段 GRUB五阶段启动链 BIOS→S1→S1.5→S2→OS 从实模式到保护模式自动切换

启动流程全貌

从按下电源键到屏幕出现登录界面,中间是一场「接力旅行」。第一棒是固件BIOS,它像机场的摆渡车调度员——接你出车门(POST自检),确认所有硬件到位,然后把你送到第一个值机柜台(MBR)。这段旅程全程跑在一张非常古老的地图上:实模式。在实模式下,CPU用「段+偏移」拼出物理地址,就像用「航站楼+登机口」定位一个座位:段寄存器是航站楼编号,偏移是登机口号。

BIOS把硬盘第一个扇区(512字节)读到固定地址0x7C00,然后跳过去执行。这个扇区叫MBR,它的最后两个字节必须是0x55AA,相当于「本班摆渡车已盖章放行」。MBR里躺着的就是GRUB的第一段代码stage1。控制权在这里完成第一次交接:BIOS → 引导扇区 → GRUB → 内核,每一棒都只向前跑一小段,跑完就把「控制权接力棒」交给下一棒。

为什么引导扇区偏偏被搬到0x7C00?因为这是BIOS与操作系统之间的「古老约定」——就像机场摆渡车永远停在同一个接驳口,只有停在这个固定位置,后面的大巴(GRUB)才能顺利接手。搞懂这张老地图,你就明白引导代码为什么总在内存里「搬家」。

PhysAddr=Seg×16+Offset\text{PhysAddr} = \text{Seg} \times 16 + \text{Offset}
实模式没有「平铺」的地址空间,只能靠段值乘16再加偏移来摸清位置,这是理解引导代码的一把钥匙。
  1. Seg(段值)实模式的段寄存器(CS、DS等)保存16位段基址,如 0x07C0。它乘以16相当于左移4位,得到20位物理地址的高位部分。
    Base=Seg×16\text{Base} = \text{Seg} \times 16
  2. Offset(偏移)在段内继续往后的16位偏移量,表示从段基址起数多少字节,如 0x0000 就是不往后走。
    PhysAddr=Base+Offset\text{PhysAddr} = \text{Base} + \text{Offset}

多引导规范

假设机场有十几个地勤团队,每队都用自己的暗号接待旅客,那旅客一定晕头转向。于是机场发布《统一服务手册》——多引导规范(Multiboot Spec)。它规定:任何想被GRUB接待的内核镜像,必须把「行李牌」(Multiboot头部)挂在同一个显眼位置(文件前8KB内),并按统一格式写清三个关键数字:魔数、标志、校验和。

魔数是0x1BADB002,它像行李牌上的机场LOGO,一看到就知道「这箱子符合手册」。GRUB扫描内核镜像前8192字节,按4字节对齐搜这个魔数;一旦命中,就读出flags(标志位,告诉GRUB内核想要什么服务)和checksum(校验和),三者相加必须能在32位世界里归零。这样设计,是为了防止某位旅客随手写个假行李牌骗过地勤——任何一位改动,校验立刻爆雷。

有了这本统一手册,任何语言写出的内核,只要把头部按规格挂好,GRUB都能顺利「值机」。后来UEFI启动规范也沿用了这个思想——用标准接口隔离「硬件」与「操作系统」,让两个世界各干各的活。

checksum=−(magic+flags) mod 232\text{checksum} = -(\text{magic} + \text{flags}) \bmod 2^{32}
魔数+标志+校验和必须刚好凑成0,这是GRUB识别「合法旅客」的暗号。
  1. magic + flags(先求和)把魔数0x1BADB002与flags相加,比如典型值 0x00000003,得到中间的sum。
    sum=0x1BADB002+0x00000003\text{sum} = 0\text{x}1\text{BADB002} + 0\text{x}00000003
  2. 取负回绕(凑零)对sum取32位补码的相反数,就是checksum。GRUB反过来把三个字段相加,结果为0才算数。
    checksum=(−sum) mod 232\text{checksum} = (- \text{sum}) \bmod 2^{32}

阶段引导流程

我们把镜头拉近,看地勤三兄弟如何接力。stage1是手里只有一张小纸条的引导员,纸条上写着「去第N号停机位找摆渡车」,它被塞在MBR那512字节里,多一个字都放不下。stage1.5是摆渡车司机,他认识路——准确说是认识文件系统——能开进停车场(/boot分区),从车库里把大巴调出来。stage2是大巴司机,他把一车人带到航站楼大屏前,等大家选航班(GRUB菜单)。

三阶段的地址换算很简单:磁盘按扇区编号,每个扇区固定512字节。stage1读盘时向BIOS报「LBA逻辑区块地址」,BIOS按编号把数据从指定扇区搬进内存。stage1先拉stage1.5或stage2,stage2再读grub.cfg、显示菜单、把内核与initrd放入内存。每段代码只干一件事,件件精炼到极致。

三兄弟的接力虽然繁琐,但这就是引导程序的生存哲学:每一棒都小到能塞进硬件给的「抽屉」里。如今的GRUB2已经把三阶段改成了模块化的core.img,但「小步接力」的底层思路从未改变。

byte_offset=LBA×512\text{byte\_offset} = \text{LBA} \times 512
LBA是磁盘的「门牌号」,乘512就是这门牌号对应的字节位置。引导程序全靠这个公式在磁盘上精确搬家。
  1. LBA(门牌号)LBA从0开始编号,0号扇区就是MBR,1号是紧随其后的扇区,以此类推。它取代了老式CHS那种「柱面/磁头/扇区」的绕口地址。
    byte_offset=LBA×512\text{byte\_offset} = \text{LBA} \times 512
  2. 0号扇区到0x7C00MBR在0号扇区,BIOS把它搬到物理地址0x7C00后跳转执行。stage1从这里开始,逐步把「接力棒」传下去。
    MBR=LBA0→0x7C00\text{MBR} = \text{LBA}_0 \rightarrow 0\text{x}7\text{C00}

配置文件语法

航站楼里那块航班大屏,对应磁盘上的grub.cfg。它会倒计时:如果时间到你还犹豫,就按默认航班(第一个菜单项)执行;如果你提前按下方向键,就尊重你的选择。每个航班(menuentry)的登机牌上都写着登机口(linux行)和行李车(initrd行),GRUB照着这些信息把内核和临时文件系统装进内存。

grub.cfg中最核心的三行:set default=0表示默认启动第一项,set timeout=5表示倒计时5秒。menuentry定义一个菜单项,内部的linux行指向内核镜像并附上启动参数(比如root=指定根分区),initrd行加载内存盘给内核当临时根文件系统。老式写法里的root (hd0,msdos1)指定「哪个硬盘哪个分区」,新式写法改用set root=配合UUID,但含义一脉相承:先找到行李在哪,再决定登机口。

看懂grub.cfg,就像读懂登机牌:set root告诉你身处哪个航站楼,menuentry决定你的目的地,linux行写了最后的登机口。定制启动菜单、做双系统、调内核参数,全都从这三行开始。

tboot=max⁡(0,  timeout−telapsed)t_{\text{boot}} = \max(0,\; \text{timeout} - t_{\text{elapsed}})
倒计时归零是GRUB菜单的「默认决策」:你不选,它就选default那一项。
  1. timeout(倒计时长)set timeout=5就是给5秒等待窗口,在这期间按键可手动选择菜单项。
    timeout=5\text{timeout} = 5
  2. 归零自动启动每过一秒,剩余时间减一;归零时GRUB直接启动default索引对应的menuentry,相当于大屏广播「默认航班马上起飞」。
    tboot=timeout−⌊telapsed⌋t_{\text{boot}} = \text{timeout} - \left\lfloor t_{\text{elapsed}} \right\rfloor

内核镜像格式

登机前还有一道核验:你的行李(内核镜像)上必须贴两张标签。一张是行李标签——ELF头部,写着箱子该放进哪个货舱(段)、货舱在哪一层(加载地址);另一张是专用贴纸——Multiboot头部,贴在箱子最容易看到的位置(文件前8KB内),写着「此箱符合统一服务手册」。GRUB就是靠这两张标签完成「托运」的。

Linux和BSD内核大多采用ELF格式(Executable and Linkable Format,可执行与可链接格式)。ELF头部记录了程序入口点e_entry,相当于「箱子的提手在哪个位置」;program header table则列出每个段要加载到内存的哪个虚拟地址,相当于货舱分布图。GRUB读这些信息,把内核各段搬到正确位置,再回头校验Multiboot头部的魔数与校验和——两张标签对得上,才放心放行。

行李标签和专用贴纸缺一不可:没有ELF头,GRUB不知道内核放哪;没有Multiboot头,GRUB不认这个「箱子」。正是因为GRUB包揽了这些脏活,内核作者才能从「自己写引导扇区」中解放出来,开机第一行代码就站在保护模式的跑道上。

Vaddr=Base+OffsetsegV_{\text{addr}} = \text{Base} + \text{Offset}_{\text{seg}}
ELF段在内存中的最终位置,由「基址」加「段内偏移」决定,GRUB只是照单搬家的搬运工。
  1. Base(加载基址)GRUB从program header里读出段的物理地址p_paddr,作为这段数据的落脚基址,相当于货舱的楼层号。
    Base=ppaddr\text{Base} = p_{\text{paddr}}
  2. Offset_seg(段内偏移)数据在段内的相对位移,与Base相加得到最终虚拟地址V_addr,一切地址计算都围绕这个公式展开。
    Vaddr=Base+OffsetsegV_{\text{addr}} = \text{Base} + \text{Offset}_{\text{seg}}

保护模式切换

你终于走到登机口,但登机前还有最后一道安检:从「国内小航站楼」(实模式,1MB地址空间)转入「国际大型枢纽」(保护模式,4GB地址空间)。地勤先打开安检通道(GDT),再按下总开关(CR0的PE位),顺手开启一条通向远方的传送带(A20地址线),然后你大步走进机舱——远跳转进入内核入口。

进入保护模式需要四步配合:①用lgdt指令加载GDT,把「段描述符」这张地图准备好;②用mov cr0, eax把CR0寄存器第0位置1,按下模式切换总开关;③通过键盘控制器端口写命令开启A20地址线,让地址总线能摸到1MB以上;④执行远跳转刷新CS段寄存器,完成「换地图」。四步环环相扣,缺一步都会在内存寻址上翻车。

这道安检是全程最丝滑的一站:多亏GRUB全包了,内核醒来时已经在保护模式的巡航高度,不必从实模式一步步爬升。你只管在入口处检查eax和ebx两个寄存器里递来的「登机信息」——魔数0x2BADB002和Multiboot信息结构指针。

CR0=CR0∨1\text{CR0} = \text{CR0} \lor 1
CR0的最低位PE是「保护模式开关」,0是实模式,1是保护模式。但按下开关只是开始,配套的GDT与段刷新一步都不能少。
  1. CR0与PE位CR0是CPU的控制寄存器,第0位叫PE(Protection Enable),它决定CPU活在20位的老地图还是32位的新地图。
    PE=CR0&1\text{PE} = \text{CR0} \mathbin{\&} 1
  2. 置位与远跳转把CR0的低位置1后,立即执行远跳转jmp 0x08:entry,段选择子0x08指向GDT里的代码段,CS被刷新成32位模式。
    CR0←CR0  ∣  1,jmp(0x08:entry)\text{CR0} \leftarrow \text{CR0} \;|\; 1, \quad \text{jmp} (0\text{x}08 : \text{entry})

构建与排错

出发前,机场地勤会按《行李检查清单》逐项核对:登机牌(grub.cfg)、行李牌(Multiboot头)、签证(A20线)都齐了吗?GRUB的安装与排错也是一张可重复的检查清单。组装一台能启动的机器,通常只需一条命令:grub-install /dev/sda。它会往MBR写stage1,把stage1.5、stage2和模块复制到/boot/grub,最后用grub-mkconfig生成grub.cfg。

启动失败是裸机开发的家常便饭。最常听到的三句抱怨:no such partition——GRUB没找到/boot所在的磁盘分区,多半是UUID变了;missing multiboot header——内核镜像头部没按规范放,魔数不在前8KB或没对齐;invalid magic number——镜像是一堆随机字节被误认成内核。排查时先用file看ELF格式对不对,用hexdump -C验证魔数和0x55AA,最后上QEMU搭配GDB单步跟踪引导流程。

有了这张清单,即使旅途颠簸,你也能像老地勤一样迅速定位问题。裸机开发最宝贵的不是背诵指令,而是这套「怀疑—验证—定位」的调试直觉——它会在未来每一次内核崩溃时救你于水火。

aligned=(addr+4095)&∼4095\text{aligned} = (\text{addr} + 4095) \mathbin{\&} \sim 4095
很多引导协议要求段地址按4KB对齐。这个公式是「向上取整到4KB边界」的经典写法,排错时经常要手动核算。
  1. 加4095(进位准备)地址加上4095后,只要低12位不是0,就会向4KB边界进位,相当于「四舍五入前的抬价」。
    addr+4095\text{addr} + 4095
  2. 清除低12位与~4095按位与,把低12位抹成0,得到不小于原地址的最近4KB倍数,这就是对齐后的地址。
    aligned=(addr+4095)&∼4095\text{aligned} = (\text{addr} + 4095) \mathbin{\&} \sim 4095

AI 应用场景

云数据中心批量装机

阶段:AI训练集群环境交付与运维

解决的问题:AI训练集群动辄上千台GPU服务器,每次升级驱动或重建环境如果用U盘/光盘逐台手动安装,耗时按天计算。GRUB支持PXE网络引导:服务器开机后从DHCP拿地址、从TFTP拉取grub.cfg与内核镜像,自动进入预置系统,配合云编排工具实现「分钟级批量重建环境」。

具体案例:某AI训练平台管理1000台GPU服务器,需要频繁切换不同版本的CUDA与驱动。运维团队把GRUB网络引导与镜像仓库联动,服务器重启后自动从PXE菜单拉取目标版本内核,整机环境刷新时间从2小时压到10分钟。

嵌入式Linux A/B系统升级

阶段:边缘AI设备系统升级与回滚

解决的问题:传统单分区升级一旦遇到断电或镜像损坏,设备直接变砖,返修成本极高。GRUB的菜单脚本能力让bootloader根据「槽位标记」选择A/B分区启动:先启动新系统验证,失败自动回滚旧系统,实现OTA安全升级。

具体案例:某自动驾驶边缘计算单元运行AI推理系统,OTA升级时GRUB按状态标记进入新内核,若新系统10秒内未上报健康信号,再次重启会由GRUB自动切回旧槽位,设备全程无需人工干预。

Python 演示

python48 行
#!/usr/bin/env python3"""解析 Multiboot 头部并校验魔数与校验和"""import structimport sys MULTIBOOT_MAGIC = 0x1BADB002HEADER_SIZE = 12SCAN_BYTES = 8192  def find_header(data):    for off in range(0, SCAN_BYTES - HEADER_SIZE, 4):        magic = struct.unpack_from("<I", data, off)[0]        if magic == MULTIBOOT_MAGIC:            return off    return -1  def parse_header(data, off):    magic, flags, checksum = struct.unpack_from("<III", data, off)    total = (magic + flags + checksum) & 0xFFFFFFFF    return {        "magic": hex(magic),        "flags": hex(flags),        "checksum": hex(checksum),        "valid": total == 0,    }  def main():    path = sys.argv[1] if len(sys.argv) > 1 else "kernel.bin"    with open(path, "rb") as f:        data = f.read(SCAN_BYTES)    off = find_header(data)    if off < 0:        print("未找到 Multiboot 头部,魔数缺失或不在前8KB内")        return 1    info = parse_header(data, off)    print(f"头部偏移: 0x{off:X}")    print(f"magic:    {info['magic']}")    print(f"flags:    {info['flags']}")    print(f"checksum: {info['checksum']}")    print(f"校验通过: {info['valid']}")    return 0 if info["valid"] else 1  if __name__ == "__main__":    sys.exit(main())
目的:读取一个内核镜像文件的前8KB,按Multiboot规范自动扫描头部、解析magic/flags/checksum三个字段,并验证校验和是否合法。写完自己的内核后,用这份脚本先自检,省得被GRUB一句missing multiboot header打回原形。
  1. struct.unpack_from("<I", data, off)[0]从指定偏移处按小端序读一个32位无符号整数,<I就是“小端、无符号整型”。
  2. range(0, SCAN_BYTES - HEADER_SIZE, 4)规范要求头部在前8KB内且4字节对齐,所以步长是4,逐点扫描而非全文搜索。
  3. (magic + flags + checksum) & 0xFFFFFFFF让三字段相加并在32位世界回绕,回绕后为0才算校验通过,模拟GRUB的判定逻辑。
  4. open(path, "rb")以二进制模式打开文件,避免文本模式在Windows上把换行符偷偷改掉,破坏字节对齐。
  5. f.read(SCAN_BYTES)只读前8192字节,扫描范围与GRUB完全一致,符合规范也省内存。

常见误区

误区我一直以为MBR那512字节就是GRUB本体,平时备份一下MBR就等于备份了引导程序,重装系统也不怕。

正解MBR里只躺了stage1,它唯一的任务是加载后续代码。真正的GRUB主体——stage2、模块、grub.cfg——都住在/boot分区里。打个比方:值机台前的引导员只有一张小纸条,真正办手续的是身后一整排柜台。只备份MBR,换块硬盘照样启动不了,因为引导员手里的纸条只写着一句「往前走,找摆渡车」。

为何常见:教材总把MBR画成启动流程的「入口」,512字节这个数字又好记,初学者很容易把入口误当成全部。

误区我直接编辑/boot/grub/grub.cfg加了个新启动项,重启后却发现菜单里没有,一定是GRUB没保存。

正解grub.cfg是「生成物」,不是「源文件」。发行版用grub-mkconfig从/etc/default/grub和/etc/grub.d/*.sh脚本自动生成它,每次更新内核或重建配置都会把你的手改覆盖掉。正确姿势是改「源配置」——比如往/etc/grub.d/40_custom里写入自定义菜单,然后执行grub-mkconfig -o /boot/grub/grub.cfg。这跟改源码、让编译器重新生成可执行文件是同一个道理。

为何常见:新手看到它是文本文件就以为可以直接编辑,不知道它背后还有一层「配置的配置」。

误区我在内核文件最前面写了0x1BADB002,为什么GRUB还是报missing multiboot header?

正解魔数只是三把锁中的一把。它必须位于文件前8192字节内且4字节对齐,同时flags与checksum要让三字段之和在32位回绕下为0。GRUB按4字节对齐做扫描,不是全文搜索,放错位置等于没放。好比值机要求登机牌贴在护照袋前8KB、编号校验无误,你只在箱底写了名字,地勤当然不放行。

为何常见:魔数0x1BADB002太显眼,初学者容易只记住这一个数字,忽略了位置、对齐、校验和这三条隐藏约束。

误区我把CR0最低位置1后直接访问4GB内存,结果电脑三连重启。教程明明说设置这一位就进入保护模式了啊?

正解置PE位只是按下「模式切换总开关」,接下来必须马上加载GDT、用远跳转刷新CS段寄存器,还要打开A20地址线,否则内存地址回绕会让一切访问乱套。GRUB在跳转内核前已经把这些全做完,所以内核醒来时才是干净的32位世界。整个过程像驾照换证:交旧证(加载GDT)→ 盖章(置PE)→ 领新证(远跳转刷新段),缺一步都上不了路。

为何常见:教程插图总爱单独放大CR0那个开关,把配套的GDT与段刷新画成「省略号」,读者自然误会成单步操作。

面试题

Q1. Multiboot头部的魔数(magic)是多少?

单选题 · easy

  • A.0x1BADB002← 正确答案
  • B.0x0BADB002
  • C.0x1BADB001
  • D.0xDEADBEEF

答案:A。0x1BADB002是Multiboot规范钦定的魔数,GRUB靠它在镜像前8KB内识别兼容内核。0x0BADB002差了一位,0xDEADBEEF是常见的调试占位符,均不符合规范。

Q2. GRUB从MBR中加载的第一段引导代码叫什么?

单选题 · easy

  • A.stage1.5
  • B.stage2
  • C.stage1← 正确答案
  • D.boot.img

答案:C。MBR只有512字节,放的是stage1——负责把后续代码拉进内存的跳板。stage1.5和stage2体积大,存放在其他扇区,由stage1接力加载。boot.img是GRUB内部对引导扇区镜像的称呼,不等同于阶段名。

Q3. Multiboot头部中哪些字段是必需的标准字段?

多选题 · mid

  • A.magic← 正确答案
  • B.flags← 正确答案
  • C.checksum← 正确答案
  • D.load_addr

答案:ABC。标准Multiboot头只需要magic、flags、checksum三个32位字段,三者相加须在32位回绕下为0。load_addr是可选的地址字段,用于指定内核加载位置,不属于必需项。

Q4. GRUB认为Multiboot头部合法的条件是什么?

单选题 · mid

  • A.magic + flags + checksum = 0← 正确答案
  • B.magic + checksum = flags
  • C.magic × flags = checksum
  • D.checksum = 0xFFFFFFFF
  • E.BIOS只负责读取第一个扇区
  • F.GRUB开发者想保持精简
  • G.磁盘接口硬性要求
  • H.编译器默认输出大小

答案:A。BIOS固件设计如此:POST后读取引导设备的第一个扇区到0x7C00并跳转执行,一个扇区传统上就是512字节,这是stage1的天花板。GRUB「想精简」是结果而非原因,磁盘扇区大小是存储介质的特性,两者共同塑造了MBR的尺寸。

Q5. 关于GRUB三阶段引导,下列说法正确的有哪些?

多选题 · mid

  • A.stage1存储在MBR中← 正确答案
  • B.stage1.5负责识别文件系统← 正确答案
  • C.stage2负责读取grub.cfg← 正确答案
  • D.stage1直接跳入内核入口

答案:ABC。stage1是跳板,只按LBA把stage1.5或stage2拉进内存;stage1.5提供文件系统驱动,帮GRUB读文件;stage2解析配置文件、显示菜单并加载内核。真正跳进内核入口发生在stage2完成任务之后,不是stage1。

Q6. 想让GRUB默认启动第一个菜单项并等待5秒,正确配置是?

单选题 · mid

  • A.set default=0 和 set timeout=5← 正确答案
  • B.set timeout=0 和 set default=5
  • C.default 0 和 timeout 5← 正确答案
  • D.set default=5 和 set timeout=0← 正确答案
  • E.加载GDT
  • F.设置CR0的PE位
  • G.使能分页
  • H.刷新段寄存器
  • I.由GRUB填写
  • J.通过eax寄存器传给内核入口
  • K.通过ebx寄存器传给内核入口
  • L.包含内存布局与命令行参数

答案:ACD。GRUB在跳转前构造好mbi,把信息结构指针放进ebx寄存器,eax则存放魔数0x2BADB002供内核校验。mbi中包含mods_count(模块数量)、mem_upper(高位内存)、cmdline(命令行)、mmap(内存映射)等启动关键信息。把edx当作mbi载体是常见误解,规范明确规定走ebx。

Q7. 实模式下段寄存器值为0x07C0、偏移为0x0000时,物理地址是多少?

单选题 · easy

  • A.0x7C00← 正确答案
  • B.0x07C0
  • C.0x7C000
  • D.0x77C00

答案:A。按公式Seg×16+Offset计算:0x07C0×16+0 = 0x7C00。这正是BIOS把引导扇区载入内存的固定物理地址。0x7C000多乘了一个16,0x77C00是把段值与偏移错误相加的结果。