ADS中堆地址的设置方法

作者在 2009-07-01 10:54:10 发布以下内容
在使用ADS编译器进行ARM开发时,如果程序需要一块内存,在不上OS的情况下,一般调用malloc()函数。然而在调用之前必须保证你已经为程序分配了堆内存。有的汇编引导代码替你完成了这一步,比如本人以前用过的ZLG系列的工程模板,有的需要自己设定堆内存。
查看ADS的inline books有如下描述:
Using a heap implementation from bare machine C
To use a heap implementation in an application that does not define main() and does not
initialize the C library:
1. Call _init_alloc(base, top) to define the base and top of the memory you want
   to manage as a heap.
2. Define the function unsigned __rt_heap_extend(unsigned size, void ** block)
   to handle calls to extend the heap when it becomes full.
以上说明如果入口函数为标准的C库入口_main函数,则_main函数会帮助我们初始化堆的基地址,比如ZLG的模板。很多时候我们会自定义入口函数_Main,这种情况下就需要我们自己定义堆的基地址,注意堆的地址范围不要和复制的向量中断表的地址空间冲突。
从online books中看到要定义堆空间,要调用_init_alloc(base,top)函数。_rt_heap_extend()用来扩展堆空间。其他堆操作函数可以在ADS的online books中找到。注意在调用这些函数是要包含头文件#inciude <rt_heap.h>。
附本人在44b0中应用的一段代码:
#include <stdlib.h>
#include <rt_heap.h>
#include "44B.h"
#include "def.h"
#include "Option.h"
#include "44blib.h"

#define  SIZE    3072

U8 *pblock=NULL;
void Main(void)
{
      _init_alloc(0xc7fe000, 0xc7ff000);           //申请4K空间的对内存,0xc7ff000为中断向量表起始地址
       pblock=(U8*)malloc(SIZE);
       /*memory use*/
       free(pblock);
 
       pblock=NULL;
 }

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/hbaizj/archive/2009/03/26/4026991.aspx
技术 | 阅读 5613 次
文章评论,共2条
vfdff(作者)
2009-07-06 21:46
1
倨《WINDOWS核心编程》上说“内存使用有3种:<br />
1,虚拟内存,2内存映射文件,3,堆栈。<br />
virtualalloc()用来保留和提交虚拟内存。<br />
heapalloc是用来在在堆上分配内存。<br />
而malloc是C/C++运行库函数。<br />
<br />
heapalloc()是内存堆栈,virtualalloc()是虚拟内存上分配,malloc()只是为了与过去的16位操作系统兼容才用的,可参看MSDN的解释。<br />
<br />
错误的:在反编中看new实际上是virtualalloc()<br />
更正为:在反汇编中看new实际上是virtualalloc()<br />
<br />
内存分配方式和调试机制 <br />
<br />
<br />
M内存分配 <br />
<br />
<br />
内存分配函数 <br />
<br />
MFC、Win32或者C语言的内存分配API,有四种内存分配API可供使用。<br />
<br />
<br />
Win32的堆分配函数 <br />
每一个进程都可以使用堆分配函数创建一个私有的堆──调用进程地址空间的一个或者多个页面。DLL创建的私有堆必定在调用DLL的进程的地址空间内,只能被调用进程访问。<br />
<br />
HeapCreate用来创建堆;HeapAlloc用来从堆中分配一定数量的空间,HeapAlloc分配的内存是不能移动的;HeapSize可以确定从堆中分配的空间的大小;HeapFree用来释放从堆中分配的空间;HeapDestroy销毁创建的堆。<br />
<br />
<br />
Windows传统的全局或者局部内存分配函数 <br />
<br />
由于Win32采用平面内存结构模式,Win32下的全局和局部内存函数除了名字不同外,其他完全相同。任一函数都可以用来分配任意大小的内存(仅仅受可用物理内存的限制)。用法可以和Win16下基本一样。<br />
Win32下保留这类函数保证了和Win16的兼容。<br />
<br />
<br />
C语言的标准内存分配函数 <br />
<br />
C语言的标准内存分配函数包括以下函数:<br />
malloc,calloc,realloc,free,等。<br />
这些函数最后都映射成堆API函数,所以,malloc分配的内存是不能移动的。这些函数的调式版本为<br />
malloc_dbg,calloc_dbg,realloc_dbg,free_dbg,等。<br />
<br />
<br />
Win32的虚拟内存分配函数 <br />
<br />
虚拟内存API是其他API的基础。虚拟内存API以页为最小分配单位,X86上页长度为4KB,可以用GetSystemInfo函数提取页长度。虚拟内存分配函数包括以下函数:<br />
<br />
LPVOID VirtualAlloc(LPVOID lpvAddress, DWORD cbSize,DWORD fdwAllocationType,DWORD fdwProtect);<br />
<br />
该函数用来分配一定范围的虚拟页。参数1指定起始地址;参数2指定分配内存的长度;参数3指定分配方式,取值MEM_COMMINT或者MEM_RESERVE;参数4指定控制访问本次分配的内存的标识,取值为PAGE_READONLY、PAGE_READWRITE或者PAGE_NOACCESS。<br />
<br />
<br />
LPVOID VirtualAllocEx(HANDLE process, <br />
<br />
LPVOID lpvAddress,<br />
<br />
DWORD cbSize,<br />
<br />
DWORD fdwAllocationType,<br />
<br />
DWORD fdwProtect);<br />
<br />
该函数功能类似于VirtualAlloc,但是允许指定进程process。VirtaulFree、VirtualProtect、VirtualQuery都有对应的扩展函数。<br />
<br />
<br />
BOOL VirtualFree(LPVOID lpvAddress, <br />
<br />
DWORD dwSize,<br />
<br />
DWORD dwFreeType);<br />
<br />
该函数用来回收或者释放分配的虚拟内存。参数1指定希望回收或者释放内存的基地址;如果是回收,参数2可以指向虚拟地址范围内的任何地方,如果是释放,参数2必须是VirtualAlloc返回的地址;参数3指定是否释放或者回收内存,取值为MEM_DECOMMINT或者MEM_RELEASE。<br />
<br />
<br />
BOOL VirtualProtect(LPVOID lpvAddress, <br />
<br />
DWORD cbSize,<br />
<br />
DWORD fdwNewProtect,<br />
<br />
PDWORD pfdwOldProtect);<br />
<br />
该函数用来把已经分配的页改变成保护页。参数1指定分配页的基地址;参数2指定保护页的长度;参数3指定页的保护属性,取值PAGE_READ、PAGE_WRITE、PAGE_READWRITE等等;参数4用来返回原来的保护属性。<br />
<br />
<br />
DWORD VirtualQuery(LPCVOID lpAddress, <br />
<br />
PMEMORY_BASIC_INFORMATION lpBuffer,<br />
<br />
DWORD dwLength<br />
<br />
);<br />
<br />
该函数用来查询内存中指定页的特性。参数1指向希望查询的虚拟地址;参数2是指向内存基本信息结构的指针;参数3指定查询的长度。<br />
<br />
<br />
<br />
BOOL VirtualLock(LPVOID lpAddress,DWORD dwSize); <br />
<br />
该函数用来锁定内存,锁定的内存页不能交换到页文件。参数1指定要锁定内存的起始地址;参数2指定锁定的长度。<br />
<br />
<br />
BOOL VirtualUnLock(LPVOID lpAddress,DWORD dwSize); <br />
<br />
参数1指定要解锁的内存的起始地址;参数2指定要解锁的内存的长度。<br />
<br />
<br />
malloc是c运行库函数,其实封装了HEAPALLOC()等堆栈分配函数。<br />
virtualalloc()是虚拟内存方面的函数。
vfdff(作者)
2009-07-29 11:41
2
存储器配置<br />
*ROM/RAM重定向<br />
当系统启动的时候,为了保证0地址处有正确的启动代码存在,需要非易失性的存储器。<br />
一种简单的方法,就是把系统0x0000开始的一块地址分配给ROM。其缺点是,由于ROM的访问速度比RAM慢很多,当执行中断响应需要从中断向量表跳转时,会给系统性能带来损失;同时,在ROM中的向量表内容也不能被用户程序动态修改。<br />
另外一种可行的方案如图11所示。ROM位于地址0x1000开始的地方,但是在系统复位时又被存储器控制器映射到0x0000地址处。这样当系统启动之后,在地址0x0000看到的是ROM,系统执行这块ROM中的启动代码,启动代码跳转到真正的ROM的地址,并让存储器控制器移除对ROM的地址映射。这时0x0000地址处的存储器又恢复回了RAM。__main中的代码把向量表copy到0x0000处的RAM中去,使得异常时能被正确响应。<br />
表1为ARM汇编中执行ROM/RAM重定向和映射的一个例子。它以ARM公司的Integrator平台为基础的,该方法适用于类似ROM/RAM重定向方法的所有平台。第一条指令完成从ROM的映射地址(0x00000)到真实地址的跳转。地址标号instruct_2是ROM的真实地址(0x180004)。然后通过设置Integrator平台上的相应控制寄存器,移除ROM的地址映射。代码在系统一启动就被执行。所有关于地址重定向/映射的操作必须在C库函数初始化之前完成。<br />
<br />
<br />
*本地存储器配置<br />
许多ARM处理器都有片上存储器系统,如cache和紧密耦合存储器(TCM)、存储器管理单元(MMU)或存储器保护单元(MPU)。这些设备都要在系统初始化过程中正确配置,并且有一些特殊的要求需要考虑。<br />
由前文可知,_main中的C库函数初始化代码负责程序运行时的存储器系统设置。因此,整个存储器系统本身必须得在__main之前完成初始化工作,如MMU或MPU必须在reset handler里面完成配置。<br />
紧密耦合存储器(TCM)的初始化同样须在_main之前完成(通常在MMU/MPU之前),因为一般程序都需要把代码和数据分散装入TCM。需要注意的是当TCM被使能后,不再访问被TCM屏蔽的存储器。<br />
关于cache的一致性问题,如果cache在_main之前使能的话,那么当_main里面进行从装载区到执行区的代码和数据拷贝时(因为在拷贝过程中指令和数据在本质上都是被当作数据处理),指令会出现在数据缓冲区。避免此问题的方法是在C库函数初始化完成后再使能cache。<br />
*Scatter loading与存储器配置<br />
无论是通过ROM/RAM重定向还是MMU配置的方法,如果系统在启动和运行时存储器分布不一致,scatterloading文件中的定义就要按照系统重定向后的存储器分布情况进行。<br />
以上文ROM/RAM重定向为例:<br />
LOAD_ROM 0x10000 0x8000<br />
{<br />
EXE_ROM 0x10000 0x8000<br />
{<br />
reset_handler.o (+RO, +FIRST)<br />
...<br />
}<br />
RAM 0x0000 0x4000<br />
{<br />
vectors.o (+RO, +FIRST)<br />
...<br />
}<br />
}<br />
装载区LOAD_ROM被放置在0x10000处,代表了重定向之后代码和数据的装载地址。<br />
<br />
堆栈的初始化<br />
程序中可能用到的处理器模式,都需要定义一个堆栈指针。<br />
在表2中,堆栈位于stack_base标识的地址中。这个符号可以是存储器系统中的一个直接地址,也可以在另外的汇编文件中定义,由scatter文件来定义分配地址。表2代码为FIQ和IRQ模式各分配了一个256字节的堆栈,用户可以用同样的方法为其他模式也分配堆栈。最简单的方法就是进入相应的模式,然后为SP寄存器指定相应的值。如果想使用软件堆栈检查,还必须指定一个堆栈长度限制值。<br />
堆栈指针和堆栈限制的数值会作为参数自动传递到C库函数的初始化代码__user_initial_stackheap中,在__user_initial_stackheap中不应该修改这些值。<br />
硬件初始化 $sub$$main()<br />
一般来说,应该把所有的系统初始化代码与主应用程序分离开来,但是有几个例外,比如cache和中断的使能,需要在C库函数初始化之后执行。<br />
表3代码显示了如何使用 $sub和 $supper 。连接器把呼叫main()的函数替换成呼叫$sub$$main(),完成cache和中断的使能,并最终跳向main()。<br />
<br />
<br />
执行模式考虑<br />
为主应用程序选择一个处理器执行模式非常重要,这取决于系统的初始化代码。<br />
许多在启动过程中使用到的功能,如MMU/MPU的配置、中断的使能等,只能在特权级模式下进行。如果需要在特权极模式下运行自己的应用程序,只要在退出初始化过程之前改变到相应的模式就行了,没有其他任何问题。<br />
如果使用user模式,必须保证所有只能在特权模式下执行的功能完成之后,才能进入user模式。因为system模式和user模式使用相同的寄存器组,reset handler应该从system模式退出,_user_initial_stackheap在system模式下完成应用程序堆栈的初始化。这样在处理器进入user模式后,所有的堆栈空间都已经被正确设置好了。<br />
<br />
对存储器布局的进一步考虑<br />
在scatter文件中分配硬件地址<br />
虽然可以在一个scatter文件中描述代码和数据的分散布局,但是目标硬件中的外设寄存器,堆栈和heap配置仍然直接采用硬件地址在程序源代码中进行设置。如果把所有存储器地址相关的信息都在scatter文件中进行定义,避免在源文件中引用绝对硬件地址,对程序的工程化管理是有大好处的。<br />
*在scatter文件中定义目标外设地址<br />
通常外设寄存器的地址在程序文件或头文件中定义,也可以声明一个结构类型指向外设寄存器,结构的地址定位在scatter文件中完成。<br />
举例来说,目标定时器上有2个32位的寄存器,可以用表4来映射这些寄存器。为了把结构放置在指定的存储器地址上面,创建一个新的执行区(见表5)。scatter文件便把timer_regs结构定位在了地址0x40000000。<br />
注意,在启动过程当中这些寄存器的内容不需要清零,改变寄存器的内容可能影响系统状态。在执行区上加UNINIT属性可以防止ZI数据在初始化过程中被清零。<br />
在scatter文件中分配堆栈和heap<br />
在许多情况下,用scatter文件来定义堆栈和heap的地址会带来一些好处,主要有:所有的存储器分配信息集中在一个文件里;改变堆栈和heap的地址只要重新连接就行了,不需要重新编译。<br />
*显式地放置符号<br />
在ADS1.2环境下,这是最简单的方法。在前文中引用过2个符号stack_base和heap_base,这2个符号在汇编模块中创建,在scatter文件中各自的执行区里定位(见表6)。<br />
表7文件中,heap基地址定位在0x20000上,堆栈基地址位于0x40000。现在heap和堆栈的位置就可以非常方便地进行编辑了。<br />
*使用连接器产生的符号<br />
这种方法需要在目标文件中指定好heap和堆栈的长度。这在一定程度上减弱了本节开头描述的两个优点。<br />
首先在汇编源程序中定义heap和堆栈的长度。关键词SPACE用来保留一块存储器空间,NOINT则可以阻止清零操作(见表8)。注意在这里的源文件中并不需要地址标号。<br />
然后这些部分就可以在scatter文件中对应的执行区里定位了(见表9)。连接器产生的符号指向每一个执行区的基地址和长度限制,这些符号可以被_user_initial_stackheap调用的重定向代码使用。在代码中使用DCD来给这些值定义更有意义的名字,可以增强代码的可读性(见表10)。<br />
文件把heap基地址定位在0x15000,堆栈地址定位在0x4000。Heap和堆栈的位置可以通过编辑对应执行区的地址方便地改变。
游客请输入验证码
浏览1936976次