就操作系统本身而言,它们选择在哪里加载程序,然后进行实际加载,并将执行权转移给它。例如,在DOS的情况下,简单的小应用程序(以COM文件形式分发)在100地址处被加载,然后命令提示符执行跳转到该地址,有效地开始了在该地址加载的代码的执行。对于采用虚拟内存的更先进的系统,问题当然更加复杂。
以后你可能会加载另一个程序,操作系统,它有自己遵循的规则,如果它选择允许加载其他程序,那么这些程序必须遵守规则。有时,当你具有MMU时,可以使程序认为它正在运行一个地址,而实际上它处于另一个地址中,但这样做会强制所有程序都假定它们从特定地址运行,然后只需为该地址编写程序(大多数你为Windows/Linux等编写的程序都是这样)。但如果你没有MMU,就可能有不同的规则,例如整个程序必须是位置无关代码(任何地方都没有硬编码地址),据我所知,uclinux就是这样的。
微控制器是学习这类技术的好地方,因为你可能已经有了一个引导加载程序(如Arduino),LPC和ST芯片通常有引导加载程序(串行或USB或两者都有)。有时像AVR一样,引导加载程序在可以修改的闪存中,分离的引导加载程序闪存和应用程序闪存。有时,引导加载程序在闪存中,但只有芯片制造商知道如何修改/更改它,因此您将永远被困在他们的引导加载程序中。无论哪种情况,您仍然可以创建自己的引导加载程序,使其在其上运行,您可以创建一种加载其他程序的方式,为这些程序提供退出到引导加载程序的规则,以便其他程序可以运行等。我有一些指令集模拟器(处于不同阶段的调试状态,Thumbulator一直用于清除它,pcemu是我从其他人那里fork的),您可以在周围包装一个简单终端,并创建自己的环境,而无需实际购买任何东西(可以使用qemu和gdb进行此操作,但不易)。变量和数据由您和编译器管理。您使用的语言和编译器将创建 .text、.data、.bss 等段。通过编译器或链接器,您必须为这些二进制文件中的每个部分指定特定的地址。在嵌入式系统中,这意味着需要查找系统(芯片加板)文档以了解每个部分应该放置在哪里才能正确启动和运行。并且您还必须以某种方式将该信息放置在那里。例如,如果您编写创建 .data(通常不会这样做)段的代码,并且正在从闪存引导,则必须有一些方案,使得编译器将 .data 段编译为位于 RAM 中,但是当您启动时它不在 RAM 中。您必须在 ROM 中有 .data 段的副本,然后在进入主体代码之前将其复制到 RAM 中。同样,您必须设置堆栈指针,并在需要时修改向量表等。如果使用 DRAM,则必须使用可能需要在没有内存的情况下运行的代码初始化内存,以启动 DRAM,然后复制 .data,清零 .bss,设置堆栈指针,然后跳转到 main()。
.org 0x7c00
这样的指令(用于传统的 x86 PC BIOS MBR 引导扇区)并不会导致代码被加载到该地址。相反,它告诉汇编器代码/数据应该在哪个地址。x86(在x86-64之前)没有任何PC相关寻址模式,因此像 mov dx,OFFSET msg
这样的指令需要将数据的绝对地址嵌入立即数中。为了正确地执行此操作,汇编器需要知道假定位置相关代码的正确起始点(基地址)。对于位置无关代码,例如使用 call/pop
,这是不必要的。 - Peter Cordesnasm
默认直接生成平面二进制文件,并仅为具有nasm -felf
或类似选项的链接器生成.o
。因此,org 0x7c00
(NASM或MASM指令,而不是GAS的.org
)纯粹是在汇编时进行的,没有链接。但是,GAS则不同;您无法直接生成平面二进制文件,需要使用ld
。您甚至可以提供链接脚本来控制布局。如何像nasm-f bin一样使用GNU GAS汇编器生成普通二进制文件? - Peter Cordes