闪存和 RAM:代码执行

电器工程 闪光 内存 链接器脚本
2022-01-20 09:35:21

我最近开始学习汇编,并开始了解链接器脚本和其他硬件编程的低级细节。我也在自学计算机体系结构,并且在某个地方,我开始担心我对内存模型的描述可能一直都是错误的。

根据我目前的了解,在我们将二进制文件“烧录”到处理器上之后,所有代码和数据都驻留在非易失性存储器上——易失性 RAM 在重置时不包含任何内容。当程序开始“执行”时,它从地址 0x0000 开始执行,该地址几乎总是(AFAIK)闪存中的最低地址。因此,指令被锁存到将 Flash 连接到 CPU 内核的总线上,这就是实际执行发生的地方。但是,当我们谈论 CPU 从内存中检索或存储数据时,我们通常会谈论 RAM——我知道我们也可以从程序内存中读取/写入数据(我在 AVR 上看到过这种情况)但它不是很常见吗?是因为 RAM 比 ROM 快,我们更喜欢在那里存储数据吗?

这个问题的公认答案是大多数代码都在 RAM 之外执行。

这是否意味着启动运行时代码(它本身从 Flash 执行)必须将所有程序操作码从 Flash 复制到 RAM,并以某种方式将 Flash 中的地址映射到 RAM,以便 CPU 从那里获取操作码?它是否类似于我们在启动时将 .data 部分从 ROM 移动到 RAM 的过程?

我可以想象这在程序和数据存储器共享总线的冯诺依曼体系结构中更简单,但在哈佛体系结构中,这是否意味着所有代码和数据都必须首先通过 CPU 寄存器?

正如您可能猜到的那样,我对整个业务感到有点困惑。一直在更高的抽象级别进行编程,我很容易被这些细节困扰。任何帮助表示赞赏。

3个回答

这取决于设备。

RAM 可以比 Flash 更快地构建;这在大约 100MHz 范围内开始变得重要。

简单的微控制器

小型慢速微控制器直接在 Flash 之外执行。这些系统通常也有比 SRAM 更多的闪存。

中端系统

一旦您的设备变得更快,情况就会有所不同。中端 ARM 系统也可以这样做,或者它们可能有一个掩码 ROM 引导加载程序,可以做一些更智能的事情:可能从 USB 或外部 EEPROM 下载代码到内部 SRAM。

大型系统

更大、更快的系统将具有外部 DRAM 和外部闪存。这是典型的手机架构。此时,有足够的 RAM 可用并且它比 Flash 快,因此引导加载程序将复制并执行它。这可能涉及将其铲入 CPU 寄存器,或者如果 DMA 单元可用,则可能涉及 DMA 传输。

哈佛架构通常很小,因此不必为复制阶段而烦恼。我见过一个带有“混合哈佛”的 ARM,它是一个包含各种内存但有两个不同提取单元的单个地址空间。代码和数据可以并行获取,只要它们不是来自同一个内存。因此,您可以从闪存中获取代码和从 SRAM 中获取数据,或者从 SRAM 中获取代码和从 DRAM 中获取数据等。

RAM 通常比闪存快,但在您达到超过 80-100MHz 左右的时钟速度之前并不重要 - 只要闪存访问时间比运行指令所需的时间快,它应该没关系。

RAM 的物理结构使我们能够构建非常快速的设备;比闪存快得多。此时,在执行之前将代码块复制到 RAM 中是有意义的。这也为开发人员带来了额外的好处,例如能够在运行时修改代码。

在程序和数据存储器共享总线的冯诺依曼体系结构中,但在哈佛体系结构中,这是否意味着所有代码和数据都必须首先通过 CPU 寄存器?

不必要。这就是虚拟寻址的用武之地。它实际上引用的是虚拟地址空间,而不是引用原始硬件 RAM 地址的程序代码。虚拟地址空间块被映射到物理内存设备,这些设备可能是 RAM、ROM、闪存,甚至是设备缓冲区。

例如,当您在微控制器上引用地址 0x000f0004 时,您可能正在从闪存读取地址 0x0004。虚拟地址是0x000f0004 ,但物理地址只是 0x0004——整个 0x000fxxxx 地址空间映射到一个 4KB 的物理内存设备。当然,这只是一个示例,管理和组织虚拟地址空间的方法因架构而异。

因此,当您说“程序从地址 0x0000 开始执行 [...] 时,该地址几乎总是闪存中的最低地址”,并不能保证您是正确的。事实上,许多微控制器都是从 0x1000 开始的。

你所说的并不完全正确或错误。有不同的场景。

这取决于您是在原始硬件上还是在安装了操作系统的硬件上进行编程。

您在通用计算机上运行的操作系统从 HDD 获取代码并将其存储在 RAM 中以便更快地访问。如果您的处理器尝试直接从 HDD 持续获取数据,那么由于两者之间的速度不匹配,操作会慢得多。因此,您的 RAM 开始发挥作用,其中存储您的重复代码片段以便更快地访问。并且在处理器缓存内存上也提供了更进一步的功能,以使其更快。

现在,当您在微控制器上工作时,它完全取决于您将数据放在芯片上的哪个位置。如果数据是静态的,您可能希望将其定位在代码内存中,这将节省您的 RAM,而 RAM 比代码内存要小得多。在 C 语言中,当您使用静态或在某些编译器中初始化数据类型时,常量前缀数据将存储在代码内存中,否则将存储在 RAM 中。在程序集中,您直接使用 DB(在 Basic 8051 的情况下定义字节)来初始化特定位置的数据。现在即使在像 PIC ARM 这样的控制器中,您也可以在运行时写入 ROM,但获取数据需要很长时间。

另外,在中级和复杂的控制器中还有引导加载程序硬件,它告诉控制器或处理器从哪里执行启动代码,或者它本身就是实际被分段到内存中的启动代码,因此有很多可能性由于进步,我宁愿说行业中的混合先进,它破坏了传统 RAM ROM 和存储器的整个概念。所以基本上你的困惑是有效的。