手把手图解Linux 0.11 启动时那场关键的‘内存大搬家’从 0x10000 到 0x0当计算机从通电到操作系统完全启动的短短几秒内内存中发生着一场精密的数据迁移。这场迁移不仅关乎系统能否正常启动更体现了早期操作系统设计者对硬件资源的极致掌控。本文将带您深入Linux 0.11启动过程中那段关键的汇编代码揭示为何需要将0x10000至0x90000的数据搬迁到物理内存起始处以及这一操作如何为后续的保护模式切换铺平道路。1. 启动初期的内存布局在理解大搬家之前我们需要先了解计算机刚完成自检时的内存状态。此时BIOS已经完成了硬件检测并将控制权交给了位于0x7C00处的bootsect.s引导扇区代码。这段512字节的代码随后将自己复制到0x90000处并加载setup.s和system模块到0x90200和0x10000位置。此时的内存布局如下内存地址范围内容描述0x00000-0x003FFBIOS中断向量表0x00400-0x004FFBIOS数据区0x00500-0x07BFF可用内存0x07C00-0x07DFF原始bootsect.s0x10000-0x8FFFFsystem模块0x90000-0x901FF移动后的bootsect.s0x90200-0x90FFFsetup.s这种布局看似合理但存在一个关键问题当系统准备进入保护模式时需要重新配置全局描述符表(GDT)和中断描述符表(IDT)而这些数据结构最好放置在内存起始位置。2. 为何需要内存搬迁2.1 保护模式的内存需求实模式下程序通过段寄存器:偏移地址的方式访问内存每个段最大64KB。而保护模式下内存管理完全由操作系统控制需要通过GDT和IDT来定义内存段的属性和权限。Linux 0.11需要在0x00000处放置新的GDT和IDT确保system模块位于连续的低地址空间保留BIOS获取的硬件信息(存储在0x90000附近)如果保持原有布局0x00000处仍被BIOS中断向量表占据无法满足这些需求。2.2 搬迁的具体目标setup.s中的do_move循环执行以下操作将0x10000-0x8FFFF的内容下移到0x00000-0x7FFFF保留0x90000-0x90FFF的硬件信息腾出0x00000-0x0FFFF空间用于GDT/IDT这样调整后内存布局变为内存地址范围内容描述0x00000-0x0FFFF新GDT/IDT空间0x10000-0x8FFFFsystem模块(原位置)0x90000-0x90FFF硬件信息区3. 深入do_move汇编实现让我们逐行分析这段经典的汇编代码mov ax,#0x0000 ; 目标段初始化为0x0000 cld ; 清除方向标志确保movs向前移动 do_move: mov es,ax ; 设置目标段地址 add ax,#0x1000; 每次移动4KB(0x1000字节) cmp ax,#0x9000; 是否到达0x90000(段地址表示) jz end_move ; 完成则跳转 mov ds,ax ; 设置源段地址 sub di,di ; 目标偏移清零 sub si,si ; 源偏移清零 mov cx,#0x8000; 设置计数器(0x8000字64KB) rep movsw ; 重复移动字数据 jmp do_move ; 继续下一块 end_move:这段代码的精妙之处在于批量传输使用rep movsw指令每次传输64KB数据段地址递增每次循环源段和目标段都增加0x1000(4KB)高效循环通过简单的比较和跳转控制流程实际传输过程如下表所示循环次数源地址范围目标地址范围传输量10x10000-0x1FFFF0x00000-0x0FFFF64KB20x20000-0x2FFFF0x10000-0x1FFFF64KB............80x80000-0x8FFFF0x70000-0x7FFFF64KB4. 搬迁后的关键操作内存搬迁完成后系统紧接着执行以下关键步骤4.1 加载段描述符end_move: mov ax,#SETUPSEG mov ds,ax lidt idt_48 ; 加载IDT lgdt gdt_48 ; 加载GDT这里idt_48和gdt_48是预先定义好的描述符表指针其结构如下IDT描述符idt_48: .word 0 ; 界限 .long 0 ; 基址GDT描述符gdt_48: .word 0x800 ; 界限(2KB) .long 0x00000 ; 基址4.2 切换到保护模式mov ax,#0x0001 lmsw ax ; 加载机器状态字(CR0) jmpi 0,8 ; 跳转到保护模式代码这个jmpi 0,8指令中的8是段选择子指向GDT中的代码段描述符。5. 实际调试技巧如果想在模拟器(Bochs/QEMU)中观察这一过程可以在do_move循环开始前设置断点使用内存查看命令观察0x10000和0x00000处内容单步执行每条汇编指令特别注意ES、DS、SI、DI寄存器的变化调试示例命令(Bochs)b 0x90200:0x00 # 在setup.s开始处设断点 c # 继续执行 s # 单步执行 x /16x 0x10000 # 查看源内存 x /16x 0x00000 # 查看目标内存 info registers # 查看寄存器状态6. 历史背景与现代对比这一设计反映了早期PC硬件的限制实模式下1MB内存限制BIOS服务的依赖保护模式切换的特殊要求现代Linux启动过程已经大为简化使用GRUB等引导加载程序直接进入保护模式内存管理更灵活不再需要手动搬迁系统代码然而理解这一经典机制仍有价值学习x86架构的演变理解操作系统与硬件的交互掌握底层内存操作技巧在开发嵌入式系统或微内核时类似的低层次内存操作仍然常见。