嵌入式8051的堆栈和堆混淆

时间:2015-05-20 00:42:27

标签: c memory embedded 8051

我试图理解一些关于8051 MCU架构的内存布局的基本概念。如果有人能给我一些澄清,我将不胜感激。

因此,对于8051 MCU,我们有几种类型的存储器:

  • IRAM - (idata) - 用于通用寄存器和SFR

  • PMEG - (代码) - 用于存储代码 - (FLASH)

  • XDATA

    • on chip(数据) - 用于数据的缓存(RAM)/
    • 片外(xdata) - 外部存储器(RAM)

问题:

  1. 那么堆栈实际位于何处? 我会假设在IRAM(idata)但它很小(30-7Fh) - 79字节

  2. 堆栈有什么作用?

    现在,我一方面读到它在调用函数时存储了返回地址(例如,当我调用一个函数时,返回地址存储在堆栈中并且堆栈指针递增)。

    http://www.alciro.org/alciro/microcontroladores-8051_24/subrutina-subprograma_357_en.htm

    另一方面,我读到堆栈存储来自函数的局部变量,一旦我们从该函数返回就被“删除”的变量。 http://gribblelab.org/CBootcamp/7_Memory_Stack_vs_Heap.html

  3. 如果我使用动态内存分配(堆),该内存是否总是保留在片外RAM(xdata)中,还是取决于编译器/优化?

5 个答案:

答案 0 :(得分:2)

我没有直接使用您的筹码经验,但我过去曾使用非常有限的系统。所以我可以回答:

问题1和2:堆栈很可能是在很早的启动例程中设置的。这将设置一个寄存器来告诉它应该从哪里开始堆栈。通常,您希望在内存中访问速度非常快,因为编译后的代码总是喜欢从堆栈中推送和弹出内存。这包括调用中的返回地址,局部变量声明以及偶尔调用直接分配堆栈内存(alloca)。

对于第3个问题,在您的启动例程将其设置为的任何位置都会设置堆 堆不需要存在特定区域。如果您希望它存在于外部存储器中,则可以在那里进行设置。你想要它在你真正小/快的区域,你也可以这样做,虽然这可能是一个非常糟糕的主意。同样,您的芯片/编译器的手册或包含的代码应该向您显示对malloc()的重载调用。从这里开始,您应该能够向后走,看看哪些地址被传递到其内存例程中。

您的IRAM非常小,以至于感觉更像是指令RAM - RAM,您可以在其中放置一个或两个子程序,以便更有效地运行代码。在典型的C函数调用框架中,80字节的堆栈空间将非常快速地蒸发。实际上,对于这样的尺寸,您可能需要手工组装以充分利用各种东西,但这可能超出了您的范围。

如果您还有其他问题,请与我们联系。这是我喜欢做的事情:)

更新

This page为您的特定芯片提供了大量有关堆栈管理的信息。看起来这个芯片的堆栈确实在IRAM中并且非常受限制。看来这个芯片上的汇编级编码也是常态,因为这个RAM的数量确实非常小。

哎呀,这是我多年来第一个将银行转换作为访问更多RAM的方式的系统。自Color Gameboy的Z80芯片以来,我还没有做到这一点。

答案 1 :(得分:2)

8051起源于20世纪70年代/ 80年代初。因此,它的资源非常有限。原始版本(例如)甚至没有XRAM,那是"修补"稍后,需要特殊(慢速)访问。

IRAM是主要内存"。它确实包括堆栈(是的,只有几个字节)。其余用于全局变量("数据"和" bss"部分:初始化和未初始化的全局变量和静态)。出于同样的原因,编译器可能会使用XRAM。 请注意,对于这些小型MCU,您不使用许多局部变量(如果只有8位类型)。一个聪明的编译器/链接器(我实际上使用了其中一些)可以分配静态重叠的局部变量 - 除非使用递归(非常不可能)。

最值得注意的是,此类系统的程序通常不使用堆(即动态内存分配),而只使用静态分配的内存。最多,他们可能会使用一个内存池,它提供固定大小的块,并且不会合并块。

请注意,IRAM包含一些特殊寄存器,可由硬件进行位寻址。通常,您将使用可以利用这些功能的专用编译器。很可能某些功能需要特殊的汇编程序功能(这些功能可能在标题中作为C函数提供,只是生成相应的机器代码指令),称为内在函数。

不同的内存区域可能还需要使用编译器扩展。 您可以查看sdcc以获得合适的编译器。

另请注意,8051具有扩展harvard architecture(代码和数据与XRAM分离为第三方)。

关于你的第二个链接:这是一篇非常普遍的文章;它不包括像8051(或AVR,PIC等)的MCU,但更广泛的CPU,如x86,ARM,PowerPC,MIPS,MSP430(也是一个较小的MCU)等,使用外部< / strong> von Neumann architecture(内部大多数(如果不是全部)32位苦味使用哈佛架构。)

答案 2 :(得分:1)

关于堆:

还有一个malloc / free couple

你必须调用init_mempool(),这在编译器文档中有说明,但它有点不常见。

下面的伪代码说明了这一点。

但是我只是这样使用它并没有尝试像动态链表管理那样大量使用malloc / free,所以我不知道你从中获得的性能。

//A "large" place in xdata to be used as heap
static char xdata heap_mem_pool [1000];

//A pointer located in data and pointing to something in xdata
//The size of the pointer is then 2 bytes instead of 3 ( the 3rd byte 
//store the area specification data, idata, xdata )
//specifier not mandatory but welcome
char xdata * data shared_memory;

//...

u16 mem_size_needed;

init_mempool (heap_mem_pool, sizeof(heap_mem_pool));

//..

mem_size_needed = calcute_needed_memory();

shared_memory  =  malloc(mem_size_needed); 
if ( 0 == shared_memory   ) return -1;

//...use shared_memory pointer

//free if not needed anymore
free(shared_memory);

答案 3 :(得分:0)

由于无堆栈微控制器,通常没有功能可重入(或付出一些努力)这一事实附加后果。< / p>

我会打电话给#34;我的系统&#34;系统我目前正在研究:C8051F040(Silab)与Keil C51编译器(我对这两家公司没有特别的兴趣)

(函数返回地址)堆栈位于iram的低位(系统上的idata)。

如果它从30(dec)开始,则表示您在代码中有全局或局部变量,要求您在数据RAM中(因为您选择&#34; & #34;内存模型或因为你在变量声明中使用了关键字 data

每当调用一个函数时,调用函数的返回2字节地址将保存在此堆栈中(16位代码空间) 并且全部 :没有寄存器保存,没有参数被推送到(不存在)(数据)堆栈。您的编译器也可能限制函数调用深度。

必要的参数和局部变量(当然还有保存的寄存器)放在RAM(数据RAM或XRAM)的某处

所以现在想象你想在你的中断和正常的无限循环中使用相同的无辜函数(比如memcpy()),它会导致零星的错误。为什么?

由于缺少堆栈,编译器必须共享多个函数之间的参数,局部变量...... 的RAM内存位置不属于同一个调用树分支强>

缺陷是中断是它自己的调用树。

因此,如果在正常任务&#34;中执行例如memcpy()时发生中断,您可能损坏 memcpy()的执行,因为当你离开时中断执行时,专用于正常任务中执行的复制的指针将具有在中断中执行的复制的(结束)值。

在我的系统上,当编译器检测到一个函数被多个独立的&#34;分支调用时,我得到一个 L15链接器错误

您可以通过添加可重入关键字来创建一个可重入的函数,该关键字需要在顶部创建模拟堆栈例如XRAM。我没有在我的系统上测试,因为我已经缺少只有4kB的XRAM内存。

请参阅链接 C51: USING NON-REENTRANT FUNCTION IN MAIN AND INTERRUPTS

答案 4 :(得分:0)

在标准8051 uC中,默认情况下,堆栈在启动时占用与寄存器组1(08H至0FH)相同的地址空间。这意味着,堆栈指针(SP寄存器)在启动时将具有值07H(当堆栈被推送时增加到08H)。如果寄存器组2(从10H开始)被占用,这可能会将堆栈存储器限制为8个字节。如果不使用寄存器组2和3,即使可以由堆栈(08H到1FH)占用。

如果在给定的程序中我们需要超过24个字节(08到1FH = 24个字节)的堆栈,我们可以将SP更改为指向RAM位置30 - 7FH。这是通过指令“MOV SP,#xxx”完成的。这应该澄清围绕8051堆栈使用的疑虑。