使用MMU进行地址重映射的启动代码结构探讨

发布者:WhisperingRain最新更新时间:2016-07-20 来源: eefocus关键字:MMU  地址重映射  启动代码 手机看文章 扫描二维码
随时随地手机看文章
本文是对《使用AXD调试MMU地址映射程序手记(二)》一文的补充,首先对编写启动代码所需要了解的基本知识和大多数初学者可能比较模糊的基本概念作了简单介绍,然后对启动代码的结构或说流程做了一些探讨。

事实上,对于一个简单的嵌入式应用,编写启动代码并不困难,但如果要在启动代码中使用MMU并完成地址重映射,有一些关键的步骤就值得商酌。

本文仅是笔者学习过程中的一点心得,纰漏之处在所难免,旨在抛砖引玉,给初学者一个思考的方向,更多以及更权威内容请参考文后所列的参考资料。

 

一、映像文件基本组成

映像文件加载时域包括RO和RW段,运行时域则包括RO、RW和ZI三个段。其中RO和RW段的内容在加载时和运行时是一样的,只是存储空间可能不同,而ZI段则是运行时由初始化函数创建的。

       RO段:Read-Only段,包括源程序中的CODE段,只读数据段(包括变量的初始化值——可以是任意变量,全局/局部、静态/动态变量的初值;还包括数据常量——这个常量也可以是全局的或局部的。也就是说,编译器既要为变量分配存储空间——变量是可读写的,并不放在RO段,又要为变量的初值分配存储空间,两者是两回事)。

       RW段:可读写段,主要指RW-DATA,也可能有RW-CODE。RW-DATA是指已经初始化的全局变量。

       ZI段:Zero-Initialized段,主要包括未初始化的全局变量,编译器用0值对其进行初始化。该段中的数据由于是变量,因而也是可读写的,但在映像文件加载时,并不为ZI段分配存储空间,虽然在ADS编译器的Memory map文件中认为Total RW Size = (RW Data + ZI Data)。

 

       二、代码,数据和变量在映像文件中的位置

       上面简单总结了映像文件各段的组成。从程序的组成看,可以分为变量、数据和代码,其中变量又分为全局/局部的或静态/动态的,它们的存储空间又是如何分配的呢?

       代码:一般是只读的,由编译器分配存储空间并放到映像文件的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

              LDR              R0, =0x20000        ;heap base

              LDR              R1, =0x40000        ;stack base, optional

              MOV             PC, 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)在嵌入式应用中,启动代码分为两个部分:一是系统的初始化,包括中断向量表的建立、时钟、存储系统初始化、关键I/O口初始化、各处理器模式下的栈指针初始化等;二是应用程序初始化(或说C库函数初始化),包括RW段的搬移和ZI段的清零、C应用程序堆栈区的建立(__user_initial_stackheap()函数初始化堆栈指针)等。

       从这个意义上说,两者并没有直接关系。

 

       (2)但两者并不是没有联系的。以单区模型的堆栈区为例,由于栈是向下生长的,堆是向上生长的,系统模式的栈指针(与用户模式相同,共用一个R13寄存器来描述)实际上定义了用户模式下单区模型堆栈区的上限,而__user_initial_stackheap()函数中指定的heap基址则成为该堆栈区的下限。

       因此,如果之前已经对系统模式(用户模式)的栈指针进行了初始化,则在重定义__user_initial_stackheap()函数时,就不需要重新定义stack base了。

 

       四、启动代码的内容和初始化顺序探讨

       前面已经指出,启动代码包括系统初始化以及应用程序运行环境的初始化两个部分,完成初始化后,就可以呼叫用户主程序了。参考资料[1]、[3]和[5]等都对两个部分的内容以及过程列出了非常清晰但又简单明了的步骤,这对于初学者来说稍微有点抽象。

如果不需要使用MMU进行地址重映射,那么,结合网上可以搜集的示例boot代码以及分析文档,加上自己动手移植和调试,也是比较容易理解的。如果是使用处理器自带的Remap控制寄存器来进行地址重映射,网上也有相关的代码,例如网友twentyone的boot代码【4510 bootloader的实现与分析(附源代码)】就非常清楚,另外,在《ARM学习报告》系列文章中也对其有详细的分析。

 

对于在启动过程中要使用MMU进行地址重映射的系统初始化顺序,在《使用AXD调试MMU地址映射程序手记(二)》一文中给出了一个参考步骤,并做了一定的说明。通过进一步参考权威资料,这里,对系统初始化顺序作了小的改进与修正如下:

①禁止所有中断→②初始化时钟→③初始化存储器→④初始化各模式下的栈指针→⑤初始化GPIO→⑥拷贝映像文件到SDRAM→⑦建立地址重映射表→⑧使能MMU→⑨应用程序初始化(RW&ZI区)→⑩使能异常中断→⑾呼叫主程序(dummyOS)。

主要对使能异常中断和应用程序初始化的顺序做了调整,即先进行应用程序的初始化,再使能异常中断,可参考[3]和[10]。

......

 

———————————————————————————————————————

【参考资料】

[1]《ARM体系结构与编程》,杜春雷,清华大学出版社,2003年2月

[2]《ARM嵌入式系统开发——软件设计与优化》,沈建华译,2005年5月

[3]《基于ARM的嵌入式系统程序开发要点》,费浙平,2003年8月

[4]《RealView编译工具开发者指南》,ARM Limited,2003年1月

[5]《ADS Developer Guide》,ARM Limited,2001年11月

[6]《ADS Compilers and Libraries Guide》,ARM Limited,2001年11月

[7]《ADS Linker and Utilities Guide》,ARM Limited,2001年11月

[8]《MAP文件认识初步》,JOHNNY LEE

[9]《堆与栈的区别》,未知网友

[10]《使用ADS1.2进行嵌入式软件开发》,ARM Limited

关键字:MMU  地址重映射  启动代码 引用地址:使用MMU进行地址重映射的启动代码结构探讨

上一篇:从ADS到RealView MDK(MDK ARM)
下一篇:对ARM7 LPC2210的Bootloader源码分析

推荐阅读最新更新时间:2024-03-16 15:01

如何编写ARM7的启动代码(LPC2119为例)
随着生活水平的提高和IT技术的进步,8位处理器的处理能力已经不能满足嵌入式系统的需要了;而16位处理器在性能和成本上都没有很大的突破。并且在8位机的开发中,大多使用汇编语言来编写用户程序。这使得程序的可维护性、易移植性等都受到了极大的挑战。正是基于此,ARM公司适时的推出了一系列的32位嵌入式微控制器。目前广泛使用的是ARM7和ARM9系列,ARM7TDMI内核的ARM7处理器广泛应用于工业控制、仪器仪表、汽车电子、通讯、消费电子等嵌入式设备。本文主要以philips公司ARM7TDMI核的LPC2119为例来分析如何编写ARM7的启动代码。 1、启动代码 在嵌入式系统软件的开发中,应用程序通常是在嵌入式操作系统的开发平台上采
[单片机]
ARM简单启动代码及中断处理分析
前言:本篇分析的是一个最精简的启动代码,并且包含一个简单的中断处理,C程序部分省略,重点分析汇编部分,这是因为对于我来说,汇编代码实在是让人厌烦,但是又不能不用。 下面是对代码的分析,红色部分是分析结果 .extern main //.extern 表示main在另外的文件中定义,在这里要引用,至于为什么只声明了extern而没 //有声明别的,目前还不知道,不过都声明一下,应该没问题 .text //.text 表示后面的内容编译出来放在代码段 .global _start //.gl
[单片机]
从汇编代码,看STM32的启动过程
分享这篇文章,谈一下STM32启动流程。如果读者朋友已经有过汇编相关基础,能更好理解本文内容。汇编语言是比C语言更接近机器底层的编程语言,能让我们更好的理解和操纵硬件底层。 STM32的三种启动模式 下好程序后,重启芯片时,SYSCLK的第4个上升沿,BOOT引脚的值将被锁存,这就是所谓的启动过程。 STM32上电或者复位后,代码区始终从0x00000000开始,其实就是将存储空间的地址映射到0x00000000中。 三种启动模式如下: 从主闪存存储器启动:将主Flash地址0x08000000映射到0x00000000,这样代码启动之后就相当于从0x08000000开始。主闪存存储器是STM32内置的Flash,作为芯
[单片机]
STM32与ARM启动代码比较分析
从ARM转到STM开发,开发工具也由ADS转到了Keil。借助STM的固件库,使得开发效率更加高效,比如你可以不用关心启动代码的具体实现,只需要专注于具体的应用代码,嵌入式开发也变得越来越“傻瓜”。此事好坏,暂且不论,来看看STM启动代码的特点,或者说相对于ARM的区别。 通常的启动代码结构: 1. 首先是中断向量表的定义. Ø ARM ARM代码在这块的代码为跳转语句,因为指令长度的限制,4个字节也就能放个跳转语就差不多了。通常两种实现方式: 1. B Reset_Handler 2. LDR PC, Reset_Handler 其实都是一个意思,跳转到真正实现Reset_Han
[单片机]
STM32F10x 启动代码分析
;******************** (C) COPYRIGHT 2010 STMicroelectronics ******************** ;* File Name : startup_stm32f10x_hd_vl.s ;* Author : MCD Application Team ;* Version : V3.4.0 ;* Date : 10/15/2010 ;* Description : STM32F10x High Density Value Line Devices vector table ;* for MDK-ARM t
[单片机]
s3c2440时钟+nandflash拷贝至SDRAM+开启mmu
涉及6个文件 head.S,init.c,main.c,makefile,nand.c,out.lds head.S .text .global _start _start: b Reset HandleUndef: b HandleUndef HandleSWI: b HandleSWI HandlePrefetchAbort: b HandlePrefetchAbort HandleDataAbort: b HandleDataAbort HandleNotUsed: b HandleNotUsed b HandleIRQ HandleFIQ: b HandleFIQ R
[单片机]
Mini2440 SDRAM、NAND、MMU
1. SDRAM 当加电默认从NAND启动时,先将4K代码复制到Steppingstone内RAM执行,在执行Steppingstone代码时,会将剩余的代码复制到SDRAM执行,但是使用SDRAM必须先对其有关SDRAM的寄存器进行初始化,以便能使用SDRAM 主要包括寄存器: BWSCON、BANKCON0~7、REFRESH、BANKSIZE、MRSRB6~7 这些寄存器要根据相应的单板进行设置,如下为Mini2440默认设置 0x22011110, //BWSCON span style= font-family:Comic Sans MS;font-size:12px; /span 0x0000070
[单片机]
Mini2440 SDRAM、NAND、<font color='red'>MMU</font>
ARM7启动代码的分析与设计
引言 随着生活水平的提高和IT技术的进步,8位处理器的处理能力已经不能满足嵌入式系统的需要了;而16位处理器在性能和成本上都没有很大的突破。并且在8位机的开发中,大多使用汇编语言来编写用户程序。这使得程序的可维护性、易移植性等都受到了极大的挑战。正是基于此,ARM公司适时的推出了一系列的32位嵌入式微控制器。目前广泛使用的是ARM7和ARM9系列,ARM7TDMI内核的ARM7处理器广泛应用于工业控制、仪器仪表、汽车电子、通讯、消费电子等嵌入式设备。本文主要以philips公司ARM7TDMI核的LPC2119为例来分析如何编写ARM7的启动代码。 1、启动代码 在嵌入式系统软件的开发中,应用程序通常是在嵌入式操作系统的开发
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
热门活动
换一批
更多
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

更多精选电路图
换一换 更多 相关热搜器件
更多每日新闻
随便看看
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved