ADS1.2内存分配

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值