什么是微控制器的不同存储器类型?


25

编译后,从C代码将不同类型的数据放入不同的内存段。即:.text.data.bss,栈和堆。我只想知道这些段中的每一个在微控制器存储器中的位置。也就是说,给定存储器类型为RAM,NVRAM,ROM,EEPROM,FLASH等,哪些数据将进入哪种类型的存储器。

我在这里找到了类似问题的答案,但是他们无法解释每种不同内存类型的内容。

任何帮助都将受到高度赞赏。提前致谢!


1
NVRAM,ROM,EEPROM和闪存几乎是同一事物的不同名称:非易失性存储器。
伦丁

与该问题略有关系,但代码(在例外情况下)几乎可以存在于所有这些问题中,尤其是在考虑使用补丁或校准用途时。有时它将在执行之前移动,有时会在适当位置执行。
肖恩·霍利哈内

@SeanHoulihane OP正在询问有关微控制器的信息,这些微控制器几乎总是在Flash之外执行的(您的评论确实有出色的表现)。微型处理器与MB的运行Linux例如外部RAM,将他们的程序复制到RAM中执行它们,也许关充当安装容量的SD卡。
tcrosley16年

@tcrosley现在有带有TCM的微控制器,有时微控制器是较大SoC的一部分。我还怀疑,在某些情况下,例如eMMC设备,mcu会将其自身引导到RAM之外的自己的存储中运行(基于几年前一些电话的硬件存储)。我同意,这不是直接的答案-但我认为与典型的映射绝不是硬性规定非常相关。
肖恩·霍利哈内

1
没有规则将一件事连接到另一件事,请确保理想情况下,诸如文本和rodata之类的只读内容将要放入闪存中,但是.data以及.bss的偏移量和大小也可以进入闪存中(然后通过引导代码)。这些术语(.text等)与微控制器无关,它是适用于编译器/工具链所有目标的编译器/工具链。在一天结束时,程序员通常会决定要去哪里,并通常通过链接描述文件通知工具链。
old_timer

Answers:


38

。文本

.text段包含实际代码,并被编程到微控制器的闪存中。当有多个不连续的闪存块时,可能会有多个文本段;例如,起始向量和中断向量位于存储器的顶部,并且代码从0开始;或引导程序和主程序的单独部分。

.bss和.data

可以在函数或过程外部分配三种类型的数据。第一个是未初始化的数据(历史上称为.bss,还包括0个初始化数据),第二个是未初始化的数据(非bss)或.data。历史上,“ bss”这个名字源于大约60年前在汇编器中使用的“以符号开头的块”。这两个区域都位于RAM中。

在编译程序时,会将变量分配给这两个常规区域之一。在链接阶段,所有数据项将一起收集。所有需要初始化的变量将保留一部分程序存储器以保留初始值,并且在调用main()之前,通常将通过一个称为crt0的模块来初始化变量。通过相同的启动代码将bss节初始化为全零。

在一些微控制器中,有较短的指令允许访问RAM的第一页(前256个位置,有时称为第0页)。这些处理器的编译器可以保留关键字,例如near指定要放置在此处的变量。同样,也有一些微控制器只能通过指针寄存器(需要额外的指令)来引用某些区域,并指定了此类变量far。最后,某些处理器可以一点一点地寻址内存的一部分,编译器将有一种方法来指定该部分(例如关键字bit)。

因此,可能还有其他段,如.nearbss和.neardata等,在这些段中收集了这些变量。

.rodata

函数或过程外部的第三种数据类型类似于初始化变量,只是它是只读的,不能被程序修改。在C语言中,这些变量使用const关键字表示。它们通常作为程序闪存的一部分存储。有时,它们被标识为.rodata(只读数据)段的一部分。在使用哈佛架构的微控制器上,编译器必须使用特殊指令来访问这些变量。

堆放

堆栈和堆都放在RAM中。根据处理器的体系结构,堆栈可能会变大或变小。如果长大,它将被放置在RAM的底部。如果它变小,它将被放置在RAM的末尾。堆将使用未分配给变量的剩余RAM,并向堆栈的相反方向增长。堆栈和堆的最大大小通常可以指定为链接器参数。

放置在堆栈中的变量是在函数或过程中定义的,没有关键字的任何变量static。它们曾经被称为自动变量(auto关键字),但是不需要该关键字。从历史上讲,auto之所以存在是因为它是C之前的B语言的一部分,因此需要它。功能参数也放置在堆栈上。

这是RAM的典型布局(假设没有特殊的第0页部分):

在此处输入图片说明

EEPROM,ROM和NVRAM

在闪存问世之前,EEPROM(电可擦可编程只读存储器)用于存储程序和const数据(.text和.rodata段)。现在,只有少量(例如2KB至8KB字节)的EEPROM可用(如果有的话),它通常用于存储配置数据或需要在断电加电时保留的其他少量数据周期。这些变量在程序中未声明为变量,而是使用微控制器中的特殊寄存器写入。EEPROM也可以在单独的芯片中实现,并可以通过SPI或I²C总线进行访问。

ROM本质上与Flash相同,区别在于ROM是在工厂编程的(用户不能编程)。它仅用于非常大容量的设备。

NVRAM(非易失性RAM)是EEPROM的替代产品,通常实现为外部IC。如果常规RAM由电池备份,则可以认为它是非易失性的。在这种情况下,不需要特殊的访问方法。

尽管可以将数据保存到闪存,但闪存的擦除/编程周期数量有限(1000到10,000个),因此并不是真正为此设计的。它还要求立即擦除内存块,因此仅更新几个字节是不方便的。它用于代码和只读变量。

EEPROM在擦除/编程周期上有更高的限制(100,000至1,000,000),因此在此方面要好得多。如果微控制器上有可用的EEPROM,并且EEPROM足够大,那么您就可以在其中保存非易失性数据。但是,在写入之前,您还必须先擦除块(通常为4KB)。

如果没有EEPROM或它太小,则需要一个外部芯片。一个32KB的EEPROM 只有66¢,可以擦除/写入1,000,000次。具有相同擦除/编程操作次数的NVRAM昂贵得多(x10)。NVRAM的读取速度通常比EEPROM快,但写入速度较慢。它们可以一次写入一个字节,也可以成块写入。

更好的替代方法是FRAM(铁电RAM),它具有无限的写入周期(100万亿),并且没有写入延迟。它与NVRAM的价格大致相同,32KB约为5美元。


那是一些真正有用的信息。您能否提供解释说明的参考?像课本或期刊一样,以防万一我想阅读更多有关此的信息。
Soju T Varghese

还有一个问题,能否请您介绍一个存储器相对于另一个存储器(EEPROM,NVRAM和FLASH)的优缺点?
Soju T Varghese

好答案。我发表了一篇补充文章,其中更详细地介绍了C语言的具体用途。
伦丁

1
@SojuTVarghese我更新了我的答案,并包括了一些有关FRAM的信息。
tcrosley16年

@Lundin我们使用相同的段名称(例如.rodata),因此答案可以很好地互补。
tcrosley16年

21

普通嵌入式系统:

Segment     Memory   Contents

.data       RAM      Explicitly initialized variables with static storage duration
.bss        RAM      Zero-initialized variables with static storage duration
.stack      RAM      Local variables and function call parameters
.heap       RAM      Dynamically allocated variables (usually not used in embedded systems)
.rodata     ROM      const variables with static storage duration. String literals.
.text       ROM      The program. Integer constants. Initializer lists.

另外,通常有用于启动代码和中断向量的单独的闪存段。


说明:

如果变量声明为static或驻留在文件范围内(有时简称为“全局”),则该变量具有静态存储持续时间。C有一条规则,指出程序员没有明确初始化的所有静态存储持续时间变量都必须初始化为零。

每个隐式或显式初始化为零的静态存储持续时间变量都以结尾.bss。而那些显式初始化为非零值的结果以结尾.data

例子:

static int a;                // .bss
static int b = 0;            // .bss      
int c;                       // .bss
static int d = 1;            // .data
int e = 1;                   // .data

void func (void)
{
  static int x;              // .bss
  static int y = 0;          // .bss
  static int z = 1;          // .data
  static int* ptr = NULL;    // .bss
}

请记住,嵌入式系统最常见的非标准设置是“最小启动”,这意味着程序将跳过具有静态存储持续时间的对象的所有初始化。因此,最好不要编写依赖于此类变量的初始化值的程序,而应在首次使用它们之前在“运行时”中设置它们。

其他细分的示例:

const int a = 0;           // .rodata
const int b;               // .rodata (nonsense code but C allows it, unlike C++)
static const int c = 0;    // .rodata
static const int d = 1;    // .rodata

void func (int param)      // .stack
{
  int e;                   // .stack
  int f=0;                 // .stack
  int g=1;                 // .stack
  const int h=param;       // .stack
  static const int i=1;    // .rodata, static storage duration

  char* ptr;               // ptr goes to .stack
  ptr = malloc(1);         // pointed-at memory goes to .heap
}

在优化过程中,可以传递到堆栈中的变量通常可能最终存储在CPU寄存器中。根据经验,任何不带地址的变量都可以放置在CPU寄存器中。

请注意,指针比其他变量稍微复杂一点,因为它们允许两种不同的const,具体取决于所指向的数据应该是只读的,还是指针本身应该是只读的。了解它们之间的差异非常重要,这样当您希望指针处于闪存状态时,指针就不会意外地进入RAM。

int* j=0;                  // .bss
const int* k=0;            // .bss, non-const pointer to const data
int* const l=0;            // .rodata, const pointer to non-const data
const int* const m=0;      // .rodata, const pointer to const data

void (*fptr1)(void);       // .bss
void (*const fptr2)(void); // .rodata
void (const* fptr3)(void); // invalid, doesn't make sense since functions can't be modified

对于整数常量,初始化程序列表,字符串文字等,根据编译器的不同,它们可能以.text或.rodata结尾。最终,它们可能是:

#define n 0                // .text
int o = 5;                 // 5 goes to .text (part of the instruction)
int p[] = {1,2,3};         // {1,2,3} goes to .text
char q[] = "hello";        // "hello" goes to .rodata

在您的第一个示例代码中,我不明白为什么“ static int b = 0;” 进入.bss以及为什么'static int d = 1;' 进入.data ..?以我的理解,这两个都是静态变量,它们已经由程序员初始化了。那么有什么区别呢?@Lundin
Soju T Varghese,

2
@SojuTVarghese因为.bss数据作为一个块初始化为0;d = 1之类的特定值必须存储在闪存中。
tcrosley16年

@SojuTVarghese添加了一些说明。
伦丁

@Lundin另外,从您的上一个示例代码开始,这是否意味着所有初始化值都进入.text或.rodata,而它们各自的变量仅进入.bss或.data?如果是这样,变量及其对应的值如何相互映射(即,在.bss / .data和.text / .rodata段之间)?
Soju T Varghese

@SojuTVarghese否,.data通常在闪存中具有一个所谓的加载地址,用于存储初始值,而在RAM中具有一个所谓的虚拟地址(在微控制器中不是真正的虚拟地址),在执行期间将变量存储在其中。在main开始之前,会将初始值从加载地址复制到虚拟地址。您不需要存储零,因此.bss不需要存储其初始值。sourceware.org/binutils/docs/ld/…– starblue 2013
6

1

尽管任何数据都可以放入程序员选择的任何内存中,但通常系统在数据的使用情况与内存的读取/写入情况相匹配的情况下运行最佳(并且打算使用)。

例如,程序代码是WFRM(写很多读很多),并且有很多。这非常适合FLASH。ROM OTOH是W一次RM。

堆栈和堆很小,有很多读写操作。那将最适合RAM。

EEPROM不能很好地适合这两种用途,但它确实适合于在上电期间持久存在的少量数据,因此用户可以指定初始化数据,甚至可以记录结果。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.