使用MMU进行地址重映射的启动代码结构探讨
事实上,对于一个简单的嵌入式应用,编写启动代码并不困难,但如果要在启动代码中使用MMU并完成地址重映射,有一些关键的步骤就值得商酌。
本文仅是笔者学习过程中的一点心得,纰漏之处在所难免,旨在抛砖引玉,给初学者一个思考的方向,更多以及更权威内容请参考文后所列的参考资料。
一、映像文件基本组成
映像文件加载时域包括RO和RW段,运行时域则包括RO、RW和ZI三个段。其中RO和RW段的内容在加载时和运行时是一样的,只是存储空间可能不同,而ZI段则是运行时由初始化函数创建的。
RO段:Read-On
RW段:可读写段,主要指RW-DA
ZI段:Zero-Initialized段,主要包括未初始化的全局变量,编译器用0值对其进行初始化。该段中的数据由于是变量,因而也是可读写的,但在映像文件加载时,并不为ZI段分配存储空间,虽然在ADS编译器的Memory map文件中认为Total RW Size = (RW Da
二、代码,数据和变量在映像文件中的位置
上面简单总结了映像文件各段的组成。从程序的组成看,可以分为变量、数据和代码,其中变量又分为全局/局部的或静态/动态的,它们的存储空间又是如何分配的呢?
代码:一般是只读的,由编译器分配存储空间并放到映像文件的RO段。
数据:这里所指的数据都是常量(若可变则为变量),也包括指针常量,那么也属于只读的数据,也由编译器分配存储空间放到映像文件的RO段。
变量:主要根据生存期来分,因为生存期是按在内存中的生存时间来定义的,而作用域与存储空间分配无关。
1.全局变量和静态变量:包括静态局部变量和全局/静态指针变量在内,由编译器分配存储空间,已初始化的放到RW段,否则放到ZI段;
2.动态变量:主要是指局部变量,包括局部指针变量在内,占用栈空间。
三、启动过程中的堆栈初始化释疑
堆与栈:对于ARM,堆是向上生长的,栈是向下生长的。
局部变量占用栈(stack)空间(但其初始化值为数据,占用RO空间);
程序中动态申请的如malloc()和new函数申请的内存空间占用堆(heap)空间。
————×以下讨论不使用semihosting机制×————
因此,在转入C应用程序前,必须要为C程序准备堆栈空间。根据具体的目标平台的存储器资源,要对堆栈的初始化函数__user_initial_stackheap( )进行移植,主要是正确设置堆(heap)和栈(stack)的地址。它可以使用C或ARM汇编语言来编写,并至少返回堆基址(保存在R0中),栈基址(保存在R1)可选。因而一个简单的汇编语言编写的__user_initial_stackheap( )函数如下:
EXPORT__user_initial_stackheap
__user_initial_stackheap
LDRR0, =0x20000;heap base
LDRR1, =0x40000;stack base, optional
MOVPC, R14
该函数的C语言实现可见参考资料[6]P158页。
注意,如果在工程中没有自定义这个函数,那么缺省情况下,编译器/链接器会把|Image$$ZI$$Limit|作为堆(heap)的基址(即把heap和stack区放置在ZI区域的上方,这也被认为是标准的实现[7])。但是,如果使用scatter文件实现分散加载机制,链接器并不生成符号|Image$$ZI$$Limit|,这时就必须自己重新实现__user_initial_stackheap( )函数并且设置好堆基址和栈顶,否则链接时会报错。
堆栈区还分为单区模型和双区模型,在双区模型中,还必须设置堆栈限制[4,6,7]。
关于重定义__user_initial_stackheap( )函数时几点要注意的地方:一是不要使用超过96字节的stack,二是不要影响到R12(IP,用作进程间调用的暂存寄存器),三是按规则返回参数值(R0:heap base;R1:stack base;R2:heap limit;R3:stack limit),四是让堆区保持8字节对齐[6]。
在启动代码中,还要对各个处理器模式的栈指针进行初始化。这个问题很容易与上面谈到的__user_initial_stackheap()函数的作用相混淆。可从以下几点来加以说明:
(1)在嵌
MMU地址重映射启动代 相关文章:
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)
- Windows CE 进程、线程和内存管理(二)(11-09)