http://blog.sina.com.cn/s/blog_61500ed601012amr.html
今天来复习下8051单片机的内存空间模型,之前看过嵌入式开发的书籍,提到内存空间主要分为三大块:段、堆和栈,其中段主要分为指令段、数据段,即为.text,.data,.bss,.rdata。用于存放程序段及相应的静态数据(全局变量或者static类型的数据)。而动态数据主要存放在堆和栈中。其中堆主要用于用户自定义的内存分配,如malloc(c语言)或者new(java或者c++语言)操作导致的内存分配,而栈主要用于函数调用过程中形参及局部变量的分配。而对于8051单片机来说,存储模式完全是另外一种形式,与avr与MSP430不同,8051的存储模式主要有两个特征:(1)有限的栈空间(2)code banking--代码分页
(支持原创,如需转载,请注明地址:http://blog.sina.com.cn/litianping0709 作者:叶雨荫城(阿雨))
当然本文只介绍contiki支持的特性,不是sdcc手册或者soc的数据手册,如果想详细了解,请参考它们相关的数据手册。
SoC Physical Memory and Memory Spaces
首先介绍一下物理内存及地址映射机制。
Physical Memory
soc上的物理内存主要分为如下几大块:
Flash:存储代码段及数据常量
Static RAM - (S)RAM: 数据存储
Special Function Registers (SFRs): 控制硬件
Flash Information Page (Info Page): 设备信息和配置
XREG: 外部寄存器,不能称之为SFRS,原因下面有介绍
Memory Spaces
用非常简单的形式,8051片上系统有四种不同但是覆盖的地址空间:DATA, CODE, SFR and XDATA。
CODE: 只读程序存储空间. 地址最大空间为64KB. 映射到flash。
DATA: 快速读取(一条指令), 读/写数据存储空间. 地址大小为256bytes. 映射到SRAM.
DATA的低128字节可以利用间接或者直接寻址
DATA的高128字节只能间接寻址
SFR: 读/写SFR存储空间, 一条cpu指令就能直接存取.
SFR的地址被8整除,但也可bit-addressable。
XREG没有映射到SFR地址空间(which is why they are not called SFRs)
XDATA: 存取比较慢,通常需要4-5个指令周期, 宽度16-bit , 读/写存储空间. XDATA可以寻址整体RAM区,同时可以寻址SFRS,部分flash空间,RF寄存器,XREGS,在cc2530上,XDATA同时映射到了Info Page上。
SDCC Terminology
sdcc利用自己的一些术语(storage classes)来定位存储空间,下表列出了这种对应关系
SoC Memory Space | SDCC | Storage Class Specifier | Physical Memory |
---|---|---|---|
XDATA | xdata / far / external RAM | __xdata or __far |
SRAM, (parts of) Flash, SFR, RFR, XREG, Info Page... |
DATA | idata / internal RAM | __idata |
SRAM |
lower 128 bytes of DATA (the directly-addressable part) |
data / near | __data or __near |
SRAM |
lower 256 bytes of XDATA | pdata / Paged Data | __pdata |
SRAM |
CODE | code | __code |
Flash |
SFR | sfr | __sfr or __sfr16 or __sfr32 or __sbit |
SFR |
Building Software with SDCC
明白了硬件,接下来我们需要明白相应的变量是分配到什么内存空间的。这个主要由SDCC的命令行参数来决定(或者由#pragma directives来决定,这里暂且不做讨论)。
SDCC Memory Models. Variables, Function Parameters and the Stack
当为8051-compatible芯片创建代码时,SDCC利用四种模型之一来创建:small,medium,large,huge。这些模型利用命令行参数指定,--model-foo
(e.g. --model-large
). 模型的选择主要影响下列因素:
本篇文章主要集中讨论第一个方面,而关于第二个方面的内容我们将在下篇文章中进行详细的讨论 guide on code banking.
当然,用户可以还有另外一个选项--stack-auto。stack-auto的出现与否直接影响到函数参数和自动变量的存储空间分配策略。同时还会对函数的可重入性(reentrancy)产生间接但是非常重要的影响。
当--stack-auto参数没有指定时,编译器默认的操作就是将局部变量放在内部或者外部RAM中(取决于存储模型),这样的操作导致的结果就好像这些变量是static!因此没有--stack-auto选项,函数是non-reentrant。当--stack-auto指定之后,局部变量将会放到stack中。
当然这听起来有点迷糊,下面的表格列出了一些例子,展示了根据不同的存储模型/stack-auto相应的结合所带来的不同效果。
Example Code | Small | Medium | Large / Huge | Small + stack-auto | Medium + stack-auto | Large / Huge + stack-auto |
---|---|---|---|---|---|---|
File Scope | ||||||
int foo; | data | pdata | xdata | data | pdata | xdata |
static int foo; | data | pdata | xdata | data | pdata | xdata |
__data int foo; | data | data | data | data | data | data |
__xdata int foo; | xdata | xdata | xdata | xdata | xdata | xdata |
__data static int foo; | data | data | data | data | data | data |
__xdata static int foo; | xdata | xdata | xdata | xdata | xdata | xdata |
Local Scope | ||||||
int foo; | data | pdata | xdata | stack | stack | stack |
static int foo; | data | pdata | xdata | data | pdata | xdata |
__data int foo; | data | data | data | error | error | error |
__xdata int foo; | xdata | xdata | xdata | error | error | error |
__data static int foo; | data | data | data | data | data | data |
__xdata static int foo; | xdata | xdata | xdata | xdata | xdata | xdata |
Function Parameters | ||||||
void bar(int a, int b, int c); | data | pdata | xdata | registers and stack | registers and stack | registers and stack |
基于contiki的大小限制,需要加上--stack-auot进行编译。同样,small和medium模型不适合。因此,对与8051的移植,我们要不适用large或者huge模型。如此,在创建contiki的代码时,我们的变量将会根据绿色部分的规则进行存储空间的分配。
The Stack
现在大家应该清楚什么变量将会被分配到且开发者怎么样决定这样的操作。这很重要,因为我们知道8051的stack非常有限,基本上,栈就位于DATA的存储空间上。DATA当然也支持bit变量。栈会位于DATA剩余的任何部分,因此,栈的理论最大深度为256bytes,当然此时是假设没有任何数据位于DATA中,contiki的8051的移植保留的栈空间大小为223字节。
Understand (and Avoid) Stack Overflows
从上可以看到,栈的最大深度为223字节。这很重要:假设在一次执行的过程中stack的大小超过这个值,节点将会崩溃。为了避免这些情况,有些小tips帮助你写出stack-friendly的代码。先总结下变量分配规则吧(--model-large(或者huge) --stack-auto):
看一个例子:
struct some_big_struct {
uint32_t first_field;
uint32_t second_field;
uint8_t and_a_buffer[64];
};
static struct some_big_struct do_this;
int some_function {
struct some_big_struct eeek;
unsigned char huge_buffer[128];
printf("Avoid this way of printing the values of a, b, c and d, which are %u, %lu, %lu and %d respectively\n", a, b, c, d);
printf("Vals: \n");
printf("a= %u ", a);
printf("b= %lu ", b);
printf("c= %lu ", c);
printf("d= %d\n", d);
}
在上述的例子中,some_big_struct的结构体为72个字节,eeek为一个动态分配的局部变量。根据上述的规则,他们将会被分配在stack空间中。这样就意味着你用一个简单的变量就消耗了我们mcu的30%的栈空间
The do_this
variable is allocated in XRAM, of which we have just under 8KBytes. do_this变量分配到XRAM中,这个变量同样占据了72个字节,但是不会是我们的节点崩溃。但是有两点不好:(1)我们必须主要不要过量消耗我们的XRAM空间。(2)这个变量是静态的,只要进行了分配,它就一直存在,如果你只是用一次,那么你就是在浪费我们mcu的空间。为了更好的做出决定,下面是一些参考。
Pros | Cons | |
---|---|---|
On Stack |
|
|
On XDATA |
|
|