ADS1.2编译器内存分配
ADS使用C语言编译器需要为C环境库指定堆栈(Stack)与堆(Heap)的位置。通常,堆栈的分配可以通过给cpu各模式下的sp寄存器赋值来实现. 而堆的分配是通过__user_initial_stackhelp函数来分配指定的。
__user_initial_stackheap这个函数功能是为C环境指定堆与栈的起始与大小等信息。具体定义如下:
r0: 指定堆基址
r1: 指定栈基址,即栈区中的最高地址(高位地址向低位地址分配)
r2: 指定堆限制
r3: 指定栈限制,即栈区中的最低地址(低位地址向高位地址分配)
(注意栈是从高地址向低地址分配的,而堆刚好相反是从低地址向高地址分配的)
需要注意的是ADS为__user_inital_stackheap提供的默认实现是: r0指定为|Image$$ZI$$Limit|符号变量,而这个符号变量的意思零初始化段的结尾. 也就是将零初始化段之后的空间作为堆的起始地址.(但是这里一定要注意,在使用分散加载机制时系统并没有定义这个符号变量. 因此,如果使用的是分散加载描述文件,则必须重新实现 __user_initial_stackheap(),否则,链接步骤将会失败。更高版本的RealView没有这个限制,不需要重新实现。这里只谈ADS1.2及以下版本)
显而易见:
1. 一般应用我们可以不用理会堆的分配. 系统提供了默认的堆分配首地址。
2. 我们可以重新实现这个函数,来规划内存的使用。
注意:
1. 在使用分散加载机制时,用户必须重新实现__user_initial_stackheap函数(为什么?前面不是说了系统为此函数提供了默认的实现吗? ). 那是因为在使用分散加载机制时,系统没有定义|Image$$ZI$$Limit|这个符号变量.
2. 有单区内存模型与双区内存模型的区别
单区内存模型: 在同一个内存区定义堆、栈.(堆向上、栈向下增长,指针相遇时即堆栈区用完).由堆管理的内存从来不会缩减。 不能将通过调用 free() 释放的堆内存再次用于其他用途?。
双区内存模型: 在不同的区域定义堆、栈.(各自都有长度限制值).为了使用这种双区(two-region)模型,用户需要导入符号use_two_region_memory.
对这两种模型来说,缺省情况下对堆栈的生长都不进行检查。用户可以在程序编译时使用 -apcs/swst 编译器选项来进行软件堆栈检查。如果使用two-region模型,必须得在执行_user_initial_stackheap时指定一个堆栈限制值。堆区大小可以是零。 堆栈区可以位于分配的内存中,也可以从执行环境中继承。要使用双区模型而不是缺省的单区模型,请使用以下任一方法:
汇编语言中的 IMPORT __use_two_region_memory
C 中的 #pragma import(__use_two_region_memory)。
3. 控制运行时内存模型
要修改堆和堆栈管理器的行为,可以使用下列任意方法:
>使用分散加载描述文件
>重新定义 __user_initial_stackheap() //(__user_setup_stackheap()和 __user_heap_extend())
>定义用于指定初始堆栈指针以及堆的开头和末尾的符号。
4. 对于缺省的单区模型,将忽略 r2 和 r3 中的值,并且 r0 和 r1 之间的所有内存始终可供堆使用。 对于双区模型,堆限制是由 r2 设置的,堆栈限制是由 r3 设置的.
5. 如果重新实现了此函数,则必须满足下列条件:
> 最多使用堆栈中的 88 个字节
> 不能破坏 r12 (ip) 以外的寄存器
> 保持堆的 8 字节对齐方式。
6. 要创建从执行环境中继承 sp 并且不使用堆的 __user_initial_stackheap() 版本,请将 r0 和 r2 设置为 r3 的值并返回。
; asm example.
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, = (Stack_Mem + USR_Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
//C语言版:
#include <rt_misc.h>
__value_in_regs struct __initial_stackheap __user_initial_stackheap(unsigned R0, unsigned SP, unsigned R2, unsigned SL)
{
struct __initial_stackheap config;
R0 = UBOOT_HEAPBASE;
R2 = UBOOT_HEAPLIMIT;
SL = UBOOT_STACKLIMIT;
SP = UBOOT_STACKBASE;
config.heap_base = R0;
config.heap_limit = R2;
config.stack_base = SP;
config.stack_limit = SL;
return config;
}
重点注意:
现在有个案例,想控制堆、栈的地址到指定的位置,按照上述所说,重新实现__user_initial_stackheap就可以高枕无忧了吗?那就大错特错啦~ 关键还得看代码实现部份。在这种情况下要想控制堆、栈的位置,除了实现上述的函数外,还得在跳转到C语言的main函数时使用系统预定义的__main()函数。为什么? 看看__main函数都干了些什么:
在ADS1.2中__main()作为c语言的入口函数,它主要做了以下工作:
1.把RO,RW从他们的加载域复制到他们的运行域中去(可以用在LINKER中设置RO=,RW=,来确定,也可以用scatter文件来定义)
2.初始化ZI域
3.跳到__rt_entry.
而库函数__rt_entry()会完成以下工作:
//__rt_entry [0xebffff74] bl __rt_stackheap_init
//30000c44 [0xebfffede] bl __rt_lib_init
//30000c48 [0xebfffea5] bl main
//30000c4c [0xea000027] b exit
1.调用__rt_stackheap_init()设置stack和heap,会调用__user_initial_stackheap函数
2.调用__rt_lib_init()初始化相应的库函数,
3.调用main(),即是我们自己的应用程序了
4.调用exit()来处理main()函数的返回值
好了,到此明白两件事情:
1. __user_initial_stackheap这个函数是系统调用的,我们自已的代码去直接调用无效。
2. 不要自作主张的直接跳转到自定义的Main()函数. 虽然程序也许可以运行,但决不是正规手段。
非要跳转到自义的Main()函数? 好吧,试试这样操作: 在汇编代码跳转到自定义Main函数这前,加上下面两句:
bl __rt_stackheap_init ;初始化堆、栈
bl __rt_lib_init ;初始化C环境库
b Main
或者在汇编代码中直接跳转到__rt_entry。但是前题是得使用默认的main函数名(其实这样做跟跳转到__main,只是省掉了代码搬运与ZI清零的动作。话说回来,这两个动作在我使用过的系统中确实都是自已实现的).
其实上面提到的__rt_stackheap_init函数也是可以自已实现的:从该函数返回时,SP 必须指向堆栈区域的顶部,r0 必须指向堆区域的底部,而 r1 必须指向堆区域的限制。但是自定义该函数会涉及到很多其它方面的东西。实际上我们大多数情况下只要使用默认实现就可以了。
分散加机制一般也不太使用,我们系统通常也不会如此复杂。
再写一点吧:
C语言中的全局变量和静态变量存储在RW或者ZI段(具体要看变量是否有初值).
C语言中的局部变量栈上分配
C语言中动态申请的内存在堆上分配
char g_szBuf[1024]; // 零初始段
DWORD g_dwCount=100; // RW段
int main()
{
DWORD dwLocalVar;
char* pHeapBuf;
static DWORD dwStaticInitVar = 1000; //RW段
static DWORD dwStaticNoInitVar; //ZI段(BSS)
dwLocalVar = 0xAA;
memset(g_szBuf,0,sizeof(g_szBuf));
pHeapBuf = (char*)malloc(0x1000);
Uart_Init();
Uart_SendString("内存管理测试/r/n");
Uart_SendString("Local var addr:");
Uart_SendDWORD((DWORD)&dwLocalVar,TRUE);
Uart_SendString("Global var addr:");
Uart_SendDWORD((DWORD)g_szBuf,TRUE);
Uart_SendString("Heap var addr:");
Uart_SendDWORD((DWORD)pHeapBuf,TRUE);
Uart_SendString("Global var(init) addr:");
Uart_SendDWORD((DWORD)&g_dwCount,TRUE);
Uart_SendString("Static var(init) addr:");
Uart_SendDWORD((DWORD)&dwStaticInitVar,TRUE);
Uart_SendString("Static var(noinit) addr:");
Uart_SendDWORD((DWORD)&dwStaticNoInitVar,TRUE);
while(1);
return 0;
}
参考:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0349bc/Cihhdahf.html
http://blog.21ic.com/user1/1457/archives/2006/13082.html