什么管理内存?

5
当一个汇编程序在没有操作系统的机器上被编译并运行时,如何选择一个正确的RAM起始地址,以便使用数据指令声明的变量被正确地分配?

1
我认为这通常是由CPU预定义的。例如,8086被硬连线到从地址FFFF0开始执行,这被认为是在ROM内部。您使用哪种处理器架构?每个处理器架构可能都不同。 - Joe White
x86。所以你的意思是第一个运行的程序得到那个地址吗?那么后续的程序呢? - TheResolute
3
对于后续的程序,来管理它。另外,它们真的能被称为程序吗?此时,它们只是一些数据块,希望能够被执行成代码。 - harold
第一个程序或任何后续程序的汇编代码是否包含任何显式的RAM地址,例如0x00000? - TheResolute
操作系统管理RAM;我不确定我理解你的问题。操作系统从某个预定地址开始。 - Adrian
丹尼尔似乎回答了我的问题。 - TheResolute
2个回答

7
正如所说,CPU期望启动程序的地址通常是硬编码的。在某些非常特定的CPU中,它可能是可编程的,但在x86的情况下,它是 FFFF0,或者更准确地说是FFFFFFF0,因此比CPU最高物理地址低16个字节。主板通常将这些地址映射到包含(很可能)BIOS代码跳转的ROM中,然后引导计算机。

就操作系统本身而言,它们选择在哪里加载程序,然后进行实际加载,并将执行权转移给它。例如,在DOS的情况下,简单的小应用程序(以COM文件形式分发)在100地址处被加载,然后命令提示符执行跳转到该地址,有效地开始了在该地址加载的代码的执行。对于采用虚拟内存的更先进的系统,问题当然更加复杂。


1
编写一个可以在没有操作系统的机器上运行的程序需要显式地定义RAM地址,因为操作系统负责为其他程序分配内存。正确吗? - TheResolute
是的。如果您在没有操作系统的情况下运行程序,那么该程序本身实际上就是操作系统。 :) 因此,您必须通过给出其显式地址来访问每个内存单元。但是请记住,由于内存映射设备等原因,在实模式下,即使对于普通应用程序,所有地址也并不总是可用的。 - Daniel Kamil Kozar
实模式是一个非常过时的概念。Ring 0可能会更好: http://en.wikipedia.org/wiki/File:Priv_rings.svg - Prof. Falken

4
每个处理器,有时是处理器家族的子集,都有不同的引导方案,通常有以下几种类别:
1) 一张不同中断/事件(复位、中断、NMI等)地址的表,通常称为向量表,这张表在硬编码位置上,硬件上电后会读取重置向量处的地址,然后开始执行重置向量指定地址上的代码。
2) 一张指令表,这些指令的地址在已知位置上,例如ARM使用这个方法,每个向量/事件只有一个指令,所以一个指令必须是load pc或者branch(否则后续向量不能使用,你需要使用更多指令来跳出向量表)。
3) 让我想想其他的方式。
一些公司(处理器架构)使用高地址,例如0xFFFE作为重置向量和0xFFFC作为中断。MSP430和6502好像也是这样的。正如你在其他答案中看到的,8088/86和其它系列使用0xFFFxxxFFFF0作为入口点/重置地址。
其他人,如ARM和Atmel AVR使用地址0x0000作为入口点。
当处理器内存接口非常简单时,有一些地址位、数据位、读写 strobe 和可能的片选或输出使能等。你需要使用外部逻辑,例如74LSxx部件来解码一些高地址位,全零和全一的解码很容易,因此你要么在高地址处放置ROM(所有高地址位都是1),在低地址处放置RAM(所有高地址位都是0),或者反过来。即使在今天的微控制器和处理器中,这也使得生活变得容易。所以你的ARM和AVR倾向于假设地址0是ROM,而RAM则在其他地方。x86、MSP430和其他处理器则将ROM放在顶部,RAM放在下面或中间某个位置。
你是否需要在汇编语言中硬编码这个地址?不一定,通常你会为链接器硬编码一些东西,以便它将向量放置在正确的位置,但还有一些汇编器支持.org类型的指令,其中你声明该指令后的代码将被放置在内存空间中的该地址上。
每个处理器都有一种引导程序,即使你的电脑也是如此。这是一个单独的程序,编译时知道处理器的引导规则,它是在该处理器上运行的第一位代码。从那里开始可能会有所不同,你只能拥有那一个程序,除了启动后不打算更改程序以外,有时这只被称为固件。现在有时引导程序会有某种提示,也许你使用串口和一个愚笨的终端来输入命令或使用xmodem下载程序等(Uboot、redboot等也会做这种事情)。通常情况下,你希望引导并进行工作而无需人为干预,因此在引导期间可能会有机会在终端/串口上按键或在板子上按按钮等。否则,引导程序具有一组规则,以确定接下来要做什么,这些规则对于引导程序的作者是独特的,这只是软件,你可以随心所欲地编写。例如,在计算机中,此引导加载程序通常被称为BIOS,并且你可能可以按F1、F2或DEL或其他键或键序列来打断正常的启动过程,以执行其他操作(更改BIOS设置等)。否则,BIOS有一组规则(有时/经常可以修改)用于查找下一个要加载和运行的内容。通常尝试查找一些介质,如硬盘、USB存储设备、软盘或CD/DVD光驱,其中包含引导扇区。该引导扇区只是更多的代码,BIOS执行关于该代码必须长什么样子或必须编译为哪个地址等规则,然后加载该代码成为另一种形式的引导程序,该代码可以做任何想做的事情,启动Linux、启动Windows、启动DOS、询问你想要启动什么、运行内存测试程序等。

以后你可能会加载另一个程序,操作系统,它有自己遵循的规则,如果它选择允许加载其他程序,那么这些程序必须遵守规则。有时,当你具有MMU时,可以使程序认为它正在运行一个地址,而实际上它处于另一个地址中,但这样做会强制所有程序都假定它们从特定地址运行,然后只需为该地址编写程序(大多数你为Windows/Linux等编写的程序都是这样)。但如果你没有MMU,就可能有不同的规则,例如整个程序必须是位置无关代码(任何地方都没有硬编码地址),据我所知,uclinux就是这样的。

微控制器是学习这类技术的好地方,因为你可能已经有了一个引导加载程序(如Arduino),LPC和ST芯片通常有引导加载程序(串行或USB或两者都有)。有时像AVR一样,引导加载程序在可以修改的闪存中,分离的引导加载程序闪存和应用程序闪存。有时,引导加载程序在闪存中,但只有芯片制造商知道如何修改/更改它,因此您将永远被困在他们的引导加载程序中。无论哪种情况,您仍然可以创建自己的引导加载程序,使其在其上运行,您可以创建一种加载其他程序的方式,为这些程序提供退出到引导加载程序的规则,以便其他程序可以运行等。我有一些指令集模拟器(处于不同阶段的调试状态,Thumbulator一直用于清除它,pcemu是我从其他人那里fork的),您可以在周围包装一个简单终端,并创建自己的环境,而无需实际购买任何东西(可以使用qemu和gdb进行此操作,但不易)。
如果特别研究这些微控制器,则是所有处理器的问题,通常引导代码,包括向量都在ROM中,但一旦运行,您并不总是希望那些向量是硬编码在ROM中的,您要么必须制定某种方案,使ROM代码在一些地方转到RAM中的某些东西,或者在引导后可以在某个地方切换开关(向寄存器中的某个字节写入一个位),并更改在该地址处解码的内存,从而允许您切换到RAM并替换向量表中的柔性内容。有些微控制器对此有过度复杂的方案。在这里,您必须查看该处理器和/或芯片的文档以了解其工作原理。有时候是板上芯片之外的东西导致地址空间发生变化,因此您必须阅读相关知识。
由于大多数在这个级别的程序员已经了解这些内容,因此文档通常不会详细说明。他们可能仅指出向量表从这里开始,这里是项,或者可能逐个查看表中的每个项目,指定地址,但可能永远不会说明该表的内容是内存地址还是要执行的指令...你必须“知道”。学习的方法就是通过询问其他人,例如查看其他人的代码。

变量和数据由您和编译器管理。您使用的语言和编译器将创建 .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 Cordes
@PeterCordes 没错,这有助于链接器,但你做的任何事都不能“导致”代码出现在某个地方。虽然有一些前提和信任,但它们很容易崩溃... - old_timer
有趣的事实:在使用许多x86汇编器组装平面二进制文件时,没有链接器。例如,nasm默认直接生成平面二进制文件,并仅为具有nasm -felf或类似选项的链接器生成.o。因此,org 0x7c00(NASM或MASM指令,而不是GAS的.org)纯粹是在汇编时进行的,没有链接。但是,GAS则不同;您无法直接生成平面二进制文件,需要使用ld。您甚至可以提供链接脚本来控制布局。如何像nasm-f bin一样使用GNU GAS汇编器生成普通二进制文件? - Peter Cordes
当我说链接器时,指的是将链接工作的部分放置在内存空间中,然后将它们放置在输出二进制文件中,无论是已组装的图像块还是实际对象。如果有多个.org项,则以某种方式将它们链接到二进制文件中。我考虑的是masm、tasm和basm等早期汇编语言,而nasm是我们目前维护的克隆版本。不过我同意,一个简单的单文件和单个.org使跳过正式步骤并针对最终二进制文件更容易。 - old_timer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接