• armv6vfp下载 > 基于ARM嵌入式系统的通用bootloader的设计与实现
  • 基于ARM嵌入式系统的通用bootloader的设计与实现

    免费下载 下载该文档 文档格式:DOC   更新时间:2006-02-02   下载次数:0   点击次数:1
    文档基本属性
    文档语言:Simplified Chinese
    文档格式:doc
    文档作者:C2040
    关键词:
    主题:
    备注:
    点击这里显示更多文档属性

    基于ARM嵌入式系统的通用bootloader的设计与实现
    摘要
    随着移动设备的流行和发展,嵌入式系统已经成为一个热点.它并不是最近出现的新技术,只是随着微电子技术和计算机技术的发展,微控制芯片功能越来越大,而嵌入微控制芯片的设备和系统越来越多,从而使得这种技术越来越引人注目.它对软硬件的体积大小,成本,功耗和可靠性都提出了严格的要求.嵌入式系统的功能越来越强大,实现也越来越复杂,随之出现的就是可靠性大大降低.最近的一种趋势是一个功能强大的嵌入式系统通常需要一种操作系统来给予支持,这种操作系统是已经成熟并且稳定的,可以是嵌入式的Linux,WINCE等等.
    相应地,这也给处理器提出了要求.当今,众多的半导体厂商都生产基于ARM体系结构的通用微处理芯片,ARM技术已经在当今的嵌入式微处理器领域中占据了它的领先地位.究其原因,它的精简指令构架为主又不放弃与复杂指令平衡的设计,使得在获得高性能的同时又能做到低功耗.
    本文所要研究的就是基于ARM嵌入式系统通用bootloader的设计与实现.Bootloader是嵌入式系统中执行在内核操作系统前的一段代码,它的基本作用就是加载内核镜像.
    在实践平台上,本课题硬件上采用了Intel Xscale系列的PXA255作为处理器,Xscale核心是与ARM V5TE构架兼容的,因此具有一定的代表性,并以此来搭建课题的硬件实践平台,软件上以Linux作为操作系统.而在具体的实现上目标是除了实现bootloader的基本功能外,还将它实现成一个更加复杂的系统,即增加对硬件电路板的支持,以方便开发人员进行调试以及开发.因此,本文重点阐述了实践上具体的设计,具体的实现以及简单评估了它的作用.最后,对实现的bootloader进行了扩展:即在阐明ARM嵌入式系统中bootloader的一些共同点之后,介绍了如何对其他ARM系统进行移植,以做到通用性.
    关键词:嵌入式系统 ARM Bootload目录
    绪言
    1. 研究背景
    随着人民生活水平提高带来巨大的消费需求,信息化进程的推进,电信运营业的发展,信息产业市场空间将得到进一步拓展.在这良好的经济环境下,嵌入式系统领域得到进一步的拓宽与发展.嵌入式系统是以应用为中心,以计算机技术为基础,软件硬件可剪裁来适应系统对功能,可靠性,成本,体积,功耗严格要求的专用计算机系统.嵌入式系统是将先进的计算机技术,半导体技术,电子技术和各行业的具体应用相结合后的产物,它是一个技术密集,资金密集,高度分散,不断创新的知识集成系统.嵌入式系统广泛地应用在信息终端,工业控制和信息家电等领域.
    在嵌入式系统的开发中,嵌入式软件是实现各种系统功能的关键,也是计算机技术最活跃的研究方向之一.不同应用对嵌入式软件系统有不同的要求,并且随着计算机技术的发展,这些要求也在不断变化.通常,应用系统对嵌入式软件的基本要求是体积小,执行速度快,具有较好的可剪裁性和可移植性.特别地,现在对嵌入式软件来说,都需要操作系统的支持.简单的嵌入式系统根本没有操作系统,而只是一个控制循环.但是,当系统变得越来越复杂时,就需要一个嵌入式操作系统来支持,否则应用软件就会变得过于复杂,使开发难度过大,安全性和可靠性都难以保证.
    嵌入式软件,特别是操作系统的支持,使得对硬件的要求越来越高.针对嵌入式系统的各种微处理器遍布各个角落.特别是采用ARM技术IP核的各种微处理器遍及各类产品市场.ARM技术以其较高的性能和功效,使得在嵌入式系统中得到广泛应用.世界上几乎所有的主要半导体厂商都生产基于ARM体系结构的通用芯片,如TI,Motorola,Intel,NS,Ateml,Philips,SAMSUNG等.目前,ARM芯片广泛应用于无线产品,PDA,GPS,网络,消费电子产品,STB及智能卡.应该说,ARM无处不在.
    随着嵌入式产品中高端微处理器ARM的加入以及软件上操作系统的支持,使得整个嵌入式系统拥有了完整的构架.现在,专门为嵌入式产品开发的各个操作系统层出不穷,WINDOW CE,POCKET PC,Linux等等,各界关注地也特别多.然而,如何进行加载操作系统这个问题却很少有人提出.这就产生了另一个相关主题bootloader.Bootloader本身的功能就是引导与加载内核镜像.如何实现bootloader的基本功能,如何针对基于ARM体系的微处理器来实现bootloader,就成为本课题的一个基本论题.,除了基本功能,bootloader还能有什么更加具体的扩展功能来方便各个系统开发者,都属于本课题讨论的范畴.
    2. 主要研究工作
    本文的题目尽管涉及到ARM系统,但是,由于bootloader的特殊性以及个别性,即bootloader与具体的处理器以及具体的硬件系统紧密联系在一起,需要实际的完整硬件系统支持,而基于ARM体系的芯片实在太多,每款芯片除了具有基本的ARM体系结构的通性外,通常还具有各自独特的特性,还需分别处理.因此,在真正的开发环节上,本课题主要基于Intel Xscale体系构架的PXA255处理器,Xscale本身是与ARM V5TE构架兼容的,因此很具有代表性,并由此在理论上扩展到整个ARM系统.
    在实现过程中具体研究开发如下:
    选择基于Intel Xscale构架的PXA255芯片作为处理器,并以此构建一套硬件开发系统
    与实际项目接轨,选择GNU的linux作为系统的操作系统
    在上述软硬件开发系统上实现基于PXA255的bootloader的基本功能,即引导加载内核镜像的功能
    在实现bootloader基本功能的基础上,增加bootloader的扩展功能,包括:通过串口打印调试信息,通过串口更新镜像,通过USB接口更新镜像,实现bootloader加载模式与下载模式的转换以及实现对FLASH的擦写操作支持
    在Xscale的构架基础上在理论上扩展到ARM系统,分析对于ARM系统,bootloader实现的一些共性以及一些限制
    分析对于实现bootloader,移植到其他ARM平台的相关处理
    3. 主要内容
    根据所涉及的主要研究工作,本文的内容主要包括一下几个部分:第一章简要叙述ARM世界的一些主流处理器以及其优势特点;紧接着第二章则详细描述了ARM的体系结构,指令系统并在此基础上简单的描述了一下PXA255的相关结构;第三章的内容是介绍bootloader的概念,并专门针对ARM bootloader进行了详细说明;第四章在首先介绍软硬件平台之后详细介绍了bootloadr的实现,包括基本功能,扩展功能等,以及软件上的编译连接的实现,代码组织结构等;第五章针对第四章的实现,对结果进行分析以及扩展功能的一些数据测试,以及如何在实现的bootloader基础上进行移植;最后一章进行一下总结与展望.
    简单阐述ARM的发展,基于ARM体系的各类主流处理器以及ARM的优势特点.
    详细描述了ARM的体系结构,包括ARM编程模型方面的内容,简单介绍了ARM core,ARM最小系统的组成以及硬件系统相关的PXA255处理器的介绍.
    阐述bootloader的一些基本概念以及ARM bootloader的一些共性.
    简单介绍了其实现的软硬件平台,详细介绍了基于PXA255处理器的bootloader基本功能的实现以及扩展功能的实现以及软件上编译连接的处理.
    实验结果的一些分析与测评,包括一些测试数据以及程序的稳定性与可移植性,剪裁性的分析.
    总结与展望.
    ARM简介
    嵌入式系统的核心部件是各种类型的嵌入式处理器.目前据不完全统计,全世界嵌入式处理器的品种总量已经超过1000多种,流行体系结构有30多个系列.嵌入式微处理目前主要有Am186/88,386EX,SC-400,Power PC,MIPS,ARM系列等.
    其中,ARM是一种今年来在嵌入式系统中有着强大影响力的微处理器设计商和制造商,ARM的设计非常适合与小的电源供电系统.特别是,随着近年来,微处理器结构由RISC(精简指令集)全面取代传统的CISC(复杂指令集),因为ARM是著名的RISC的拥护者.
    1.1 ARM 简介
    ARM(Advanced RISC Machines),既可以认为是一个公司的名字,也可以是对一类微处理器的通称,还可以认为是一种技术的名字.
    1991年ARM公司成立于英国剑桥,主要出售芯片设计技术的授权.目前,采用ARM技术只是产权(IP)核的微处理器,即通常我们所说的ARM微处理器,已经遍及工业控制,消费类电子产品,通信系统,无线系统等各类产品市场,基于ARM技术的微处理应用约占据了32位RISC微处理器75%以上的市场份额,ARM技术正在逐步渗入我们生活的各个方面.
    ARM公司是专门从事基于RISC技术芯片设计开发的公司,作为知识产权供应商,本身不直接从事芯片生产,靠转让设计许可由合作公司生长各具特色的芯片.世界各大半导体生产商从ARM公司购买其设计的ARM微处理器核,根据各自不同的应用领域,加入适当的外围电路,从而形成自己的ARM微处理器芯片进入市场.目前,全世界有几十家大的半导体公司都使用ARM公司的授权,因此既使得ARM技术获得更多的第三方工具,制造,软件的支持,又使得整个系统成本降低,使产品更容易进入市场被消费者所接受,更具有竞争力.
    1.1.1 ARM处理器介绍
    ARM处理器目前包括下面几个系列的处理器产品以及其他厂商实现的基于ARM体系结构的处理器:ARM7系列,ARM9系列,ARM9E系列, ARM10E系列,SecurCore系列,Intel的Xscale系列,Intel的StrongARM系列.
    ARM体系有变种,也就是说有些版本具备特定功能,在各个版本的版本号上可以体现出来,说明如下[5]:
    T: 支持16位压缩指令集Thumb;
    D: 支持片上Debug;
    M:内嵌硬件乘法器(Multiplier),增加用于长乘法操作的指令;
    I: 嵌入式ICE,支持片上断点和调试点;
    E:增强型DSP指令;
    ARM构架诞生至今,已经产生了多次变革,每一次都带来性能上的极大飞跃,其过程包括:
    - V1构架(ARM1)
    基本的数据处理指令(无乘法)
    字节,半字和字的Load/Store指令
    转移指令,包括子程序的调用和链接指令
    软件中断指令
    寻址空间64MB
    - V2构架(ARM2,ARM3)
    增加乘法和乘法指令
    增加支持协处理器的操作
    增加快速中断模式
    增加SWP/SWPB的存储器和寄存器交换指令
    - V3构架(ARM6)
    增加了MRS/MSR指令,访问新增的CPSR/SPSR寄存器
    增加了异常处理返回
    寻址空间4GB
    - V4构架(ARM7, ARM9)
    增加符号化和非符号化半字及符号化字节的存取指令
    增加16位的Thumb指令
    完善软件中断SWI指令
    - V5构架(ARM10)
    带有链接和交换的转移BLX指令
    计数前导零CLZ指令
    BRK中断指令
    增加了一些信号处理的指令
    - V6构架
    增加了SIMD功能扩展,为包括音频/视频处理在内的应用系统提供优化功能
    接下来就简要介绍一下各主要系列处理器的特点.
    (1)ARM7微处理器系列[5]
    ARM7系列微处理器为低功耗的32位RISC处理器,最适合用于对价位和功耗要求较高的消费类应用.ARM7微处理器系列具有如下特点:
    - 具有嵌入式ICE-RT逻辑,调试开发方便.
    - 极低的功耗,适合对功耗要求较高的应用,如便携式产品.
    - 能够提供0.9MIPS/MHz的三级流水线结构.
    - 代码密度高并兼容16位的Thumb指令集.
    - 对操作系统的支持广泛,包括Windows CE,Linux,Palm OS等.
    - 指令系统与ARM9系列,ARM9E系列和ARM10E系列兼容,便于用户的产品升级.
    - 主频最高可达130MIPS,高速的运算处理能力能胜任绝大多数的复杂应用.
    ARM7系列微处理器的主要应用领域为:工业控制,Internet设备,网络和调制解调器设备,移动电话等多种多媒体和嵌入式应用.
    ARM7系列微处理器包括如下几种类型的核:ARM7TDMI,ARM7TDMI-S,ARM720T,ARM7EJ.其中,ARM7TMDI是目前使用最广泛的32位嵌入式RISC处理器,属低端ARM处理器核.
    (2)ARM9微处理器系列[5]
    ARM9系列微处理器在高性能和低功耗特性方面提供最佳的性能.具有以下特点:
    - 5级整数流水线,指令执行效率更高.
    - 提供1.1MIPS/MHz的哈佛结构.
    - 支持32位ARM指令集和16位Thumb指令集.
    - 支持32位的高速AMBA总线接口.
    - 全性能的MMU,支持Windows CE,Linux,Palm OS等多种主流嵌入式操作系统.
    - MPU支持实时操作系统.
    - 支持数据Cache和指令Cache,具有更高的指令和数据处理能力.
    ARM9系列微处理器主要应用于无线设备,仪器仪表,安全系统,机顶盒,高端打印机,数字照相机和数字摄像机等.
    ARM9系列微处理器包含ARM920T,ARM922T和ARM940T三种类型,以适用于不同的应用场合.
    (3)ARM9E微处理器系列[5]
    ARM9E系列微处理器为可综合处理器,使用单一的处理器内核提供了微控制器,DSP,Java应用系统的解决方案,极大的减少了芯片的面积和系统的复杂程度.ARM9E系列微处理器提供了增强的DSP处理能力,很适合于那些需要同时使用DSP和微控制器的应用场合.
    ARM9E系列微处理器的主要特点如下:
    - 支持DSP指令集,适合于需要高速数字信号处理的场合.
    - 5级整数流水线,指令执行效率更高.
    - 支持32位ARM指令集和16位Thumb指令集.
    - 支持32位的高速AMBA总线接口.
    - 支持VFP9浮点处理协处理器.
    - 全性能的MMU,支持Windows CE,Linux,Palm OS等多种主流嵌入式操作系统.
    - MPU支持实时操作系统.
    - 支持数据Cache和指令Cache,具有更高的指令和数据处理能力.
    - 主频最高可达300MIPS.
    ARM9系列微处理器主要应用于下一代无线设备,数字消费品,成像设备,工业控制,存储设备和网络设备等领域.
    ARM9E系列微处理器包含ARM926EJ-S,ARM946E-S和ARM966E-S三种类型,以适用于不同的应用场合.
    (4)ARM10E微处理器系列[5]
    ARM10E系列微处理器具有高性能,低功耗的特点,由于采用了新的体系结构,与同等的ARM9器件相比较,在同样的时钟频率下,性能提高了近50%,同时,ARM10E系列微处理器采用了两种先进的节能方式,使其功耗极低.
    ARM10E系列微处理器的主要特点如下:
    - 支持DSP指令集,适合于需要高速数字信号处理的场合.
    - 6级整数流水线,指令执行效率更高.
    - 支持32位ARM指令集和16位Thumb指令集.
    - 支持32位的高速AMBA总线接口.
    - 支持VFP10浮点处理协处理器.
    - 全性能的MMU,支持Windows CE,Linux,Palm OS等多种主流嵌入式操作系统.
    - 支持数据Cache和指令Cache,具有更高的指令和数据处理能力
    - 主频最高可达400MIPS.
    - 内嵌并行读/写操作部件.
    ARM10E系列微处理器主要应用于下一代无线设备,数字消费品,成像设备,工业控制,通信和信息系统等领域.
    ARM10E系列微处理器包含ARM1020E,ARM1022E和ARM1026EJ-S三种类型,以适用于不同的应用场合.
    (5)StrongArm微处理器系列
    Intel StrongARM SA-1100处理器是采用ARM体系结构高度集成的32位RISC微处理器.它融合了Intel公司的设计和处理技术以及ARM体系结构的电源效率,采用在软件上兼容ARMv4体系结构,同时采用具有Intel技术优点的体系结构.
    Intel StrongARM处理器是便携式通讯产品和消费类电子产品的理想选择,已成功应用于多家公司的掌上电脑系列产品
    (6)Intel Xscale微处理器系列
    有关Intel Xscale微处理器系列,会在后面相关篇幅中进行介绍.
    1.2 ARM处理器的优势特点
    采用RISC结构的ARM微处理器一般具有如下特点:
    体积小,低功耗,低成本,高性能;
    支持Thumb(16位)/ARM(32位)双指令集,能很好地兼容8/16位器件;
    大量使用寄存器,指令执行速度更快;
    大多数数据操作都在寄存器中完成;
    寻址方式灵活简单,执行效率高;
    指令长度固定;
    ARM处理器之所以能够广泛应用于各个领域,与以上所列的各个特点密切相关,综合起来,主要包括以下几点.
    1.2.1 RISC指令集
    传统的CISC(Complex Instruction Set Computer,复杂指令集计算机)结构有其固有的缺点,即随着计算机技术的发展而不断引入新的复杂的指令集,为支持这些新增的指令,计算机的体系结构会越来越复杂,然而,在CISC指令集的各种指令中,其使用频率却相差悬殊,大约有20%的指令会被反复使用,占整个程序代码的80%.而余下的80%的指令却不经常使用,在程序设计中只占20%,显然,这种结构是不太合理的.
    基于以上的不合理性,1979年美国加州大学伯克利分校提出了RISC(Reduced Instruction Set Computer,精简指令集计算机)的概念,RISC并非只是简单地去减少指令,而是把着眼点放在了如何使计算机的结构更加简单合理地提高运算速度上.RISC结构优先选取使用频率最高的简单指令,避免复杂指令;将指令长度固定,指令格式和寻址方式种类减少;以控制逻辑为主,不用或少用微码控制等措施来达到上述目的.
    到目前为止,RISC体系结构也还没有严格的定义,一般认为,RISC体系结构应具有如下特点:
    - 采用固定长度的指令格式,指令归整,简单,基本寻址方式有2~3种.
    - 使用单周期指令,便于流水线操作执行.
    - 大量使用寄存器,数据处理指令只对寄存器进行操作,只有加载/ 存储指令可以访问存储器,以提高指令的执行效率.
    ARM主要使用32位的RISC指令,但是指令代码利用率低,ARM为了弥补此不足,在新型ARM构架(V4T版本以上)定义了16位的Thumb指令集.Thumb指令集比通常的8位和16位RISC/CISC处理器具有更好的代码密度,而芯片面积只增加6%,可使程序存储器更小.
    另外,为了加速通信和多媒体中有关数字信号的处理,ARM处理器在RISC的基础上增加了许多原属DSP核中的关键部件.双核协同工作,各用所长,有效地提高了数据处理能力核传输速度,但是RISC+DSP分立的双核结构存在两核之间互相通信的困难和分别编程的麻烦,为此发展了两核融合在一起的单核结构,同时将指令集也合二为一,简化编程提高效率,将CPU提升到可快速运算多媒体的算法,实现许多音视频的解码功能.
    1.2.2 低功耗
    由于ARM架构的处理器主要用于手持式嵌入式系统之中,因此ARM构架在设计中十分注意低电压,低功耗这一点,因而在手持式嵌入式系统得到广泛的应用.ARM架构的设计采用了以下一些措施:
    - 降低电源电压
    - 减少门的翻转次数,当某个功能电路不需要时,禁止门翻转
    - 减少门的数目,即降低芯片的集成度
    - 降低时钟频率
    ARM还其他采用了一些特别的技术,在保证高性能的前提下尽量缩小芯片的面积:
    - 所有的指令都可以根据前面的执行结果决定是否被执行,从而提高指令的执行效率;
    - 可用加载/存储指令批量传输数据,以提高数据的传输效率;
    - 可在一条数据处理指令中同时完成逻辑处理和移位处理;
    - 在循环处理中使用地址的自动增减来提高运行效率;
    ARM的低功耗,使得ARM在专门针对嵌入式系统的处理器市场中始终处于有利的地位.一组典型数据是:对于ARM7系列处理器,当核电压是3.3V时,每MHz,其功耗只为1.1mW.
    第二章 ARM体系结构介绍
    在介绍具体的ARM Bootloader的实现之前,首先介绍一下ARM的体系结构,因为Bootloader是与处理器芯片紧密相连的.为了更好的在后面篇幅中介绍实现,先简单介绍相关的体系结构以及相关指令集.
    2.1 ARM core描述
    尽管本本文实现主要涉及软件上的开发,但是软件上代码的实现更多的都涉及芯片构架,因此在这里简要介绍一下一般ARM core构架.
    下图所示的是ARM构架图.它由32位ALU,若干个32位通用寄存器以及状态寄存器,32×8位乘法器,32×32位桶形移位寄存器,指令译码以及控制逻辑,指令流水线和数据/地址寄存器组成.
    ALU:它有两个操作数锁存器,加法器,逻辑功能,结果以及零检测逻辑构成.
    桶形移位寄存器:ARM采用了32×32位的桶形移位寄存器,这样可以使在左移/右移n位,环移n位和算术右移n位等都可以一次完成.
    高速乘法器:乘法器一般采用"加一移位"的方法来实现乘法.ARM为了提高运算速度,则采用两位乘法的方法,根据乘数的2位来实现"加一移位"运算 ;ARM高速乘法器采用32×8位的结构,这样,可以降低集成度(其相应芯片面积不到并行乘法器的1/3).
    浮点部件:浮点部件是作为选件供ARM构架使用.FPA10浮点加速器是作为协处理方式与ARM相连,并通过协处理指令的解释来执行.
    控制器:ARM的控制器采用的是硬接线的可编程逻辑阵列PLA.
    寄存器:具体的介绍参考下节的相关介绍.

    不同的ARM core可能还有所不同.因此,图2.1[7]描述的仅仅针对一般而言.
    A[31:0] 控制

    增值器


    寄存器堆
    A
    L
    U
    总 乘法器
    线 线

    A
    桶式 B
    移位器 总
    线
    ALU
    数据输出寄存器 数据输入寄存器

    D[31:0]
    图 2.1 ARM构架图
    2.2 编程模型介绍
    对于ARM编程,需要了解的一下几个方面:
    2.2.1 ARM处理器模式
    ARM处理器共有7种运行模式,如表2.1[6]所示:
    处理器模式
    处理器模式描述
    用户模式(User,usr)
    正常程序执行的模式
    快速中断模式(FIQ,fiq)
    用于高速数据传输和通道处理
    普通中断模式(IRQ,irq)
    用于通常的中断处理
    监管模式(Supervisor,svc)
    供操作系统使用的一种保护模式
    数据访问中止模式(Abort,abt)
    用于虚拟存储及存储保护
    未定义指令中止模式(Undefined,und)
    用于支持通过软件方针硬件的协处理器
    系统模式(System,sys)
    用于运行特权级的操作系统任务
    表格 2.1
    除了用户模式之外的其他6种处理器模式称为特权模式(Privileged Modes).在这些模式下,程序可以访问所有的系统资源,也可以任意地进行处理器模式的切换.其中,除系统模式外,其他5种特权模式又称为异常模式.
    处理器模式可以通过软件控制进行切换,也可以通过外部中断或异常处理过程进行切换.大多数的用户程序运行在用户模式下,这时,应用程序不能够访问一些受操作系统保护的系统资源,应用程序也不能直接进行处理器模式的切换.当需要进行处理器模式的切换时,应用程序可以产生异常处理,在异常处理过程中进行模式的切换.这种体系结构可以使操作系统控制整个系统的资源.
    当应用程序发生异常中断时,处理器进入相应的异常模式.在每一种异常模式中都有一组寄存器,供相应的异常处理程序使用,这样就可以保证在进入异常模式时,用户模式下的寄存器(保证了程序运行状态)不被破坏.
    系统模式并不是通过异常过程进入的,它和用户模式具有完全一样的寄存器.但是系统模式属于特权模式,可以访问所有的系统资源,也可以直接进行处理器模式的切换.它主要供操作系统任务使用.通常操作系统的任务需要访问所有的系统资源,同时该任务仍然使用用户模式下的寄存器组,而不是使用异常模式下相应的寄存器组,这样可以保证当异常中断发生时任务状态不被破坏[6].
    2.2.2 ARM寄存器组介绍
    ARM处理器一般共有37个寄存器,其中包括[5]:
    31个通用寄存器,包括程序计数器(PC)在内,这些寄存器都是32位寄存器;
    6个状态寄存器,这些寄存器也是32位寄存器;
    上一节已经说过,ARM处理器共有7种不同的处理器模式,在每一种处理器模式中有一组相应的寄存器.任意时刻(也就是任意的处理器模式下),可见的寄存器包括15个通用寄存器(R0-R14),一个或者两个状态寄存器以及程序计数器(PC).在所有的寄存器中,有些是各模式共用的同一个物理寄存器;有些寄存器是各模式自己拥有的独立的物理寄存器.图3.1表示了各处理器模式下的可见寄存器:

    R0
    R1
    R2
    R3
    R4
    R5
    R6
    R7
    R8
    R9
    R10
    R11
    R12
    R13
    R14
    R15(PC)

    用户模式和系统模式寄存器组

    异常模式下的寄存器组
    R8_fiq
    R9_fiq
    R10_fiq
    R11_fiq
    R12_fiq
    R13_fiq
    R14_fiq

    R13_irq
    R14_irq
    R13_abt
    R14_abt
    R13_und
    R14_und
    R13_svc
    R14_svc
    SPSR_und
    SPSR_abt
    SPSR_irq
    SPSR_fiq
    SPSR_svc
    CPSR
    图2.2
    从表3.1中可以看出,通用寄存器R0-R7在所有的处理器模式下指的都是同一个物理寄存器.
    而对于R8-R12寄存器组,除了在快速中断模式下有自己专有的物理寄存器,其它模式下也共有统一物理寄存器.
    R13寄存器对应6个不同的物理寄存器,即除了前面提过的用户模式与系统模式共用一个寄存器外,其它模式都有相应的物理寄存器;一般地,R13寄存器在ARM中通常用作栈指针,在ARM指令中这只是一种习惯的用法,并没有任何指令强制性的使用R13作为栈指针.
    R14寄存器又被称为连接寄存器(Link Register,LR),在ARM体系中有下面两种特殊的作用[6]:
    每一种处理器模式自己的物理R14中存放当前子程序的返回地址.当通过跳转指令调用子程序时,R14被设置为该子程序的返回地址;在子程序中,把R14的指赋值到程序计数器PC中时,子程序返回;
    当异常中断发生时,该异常模式特定的物理R14被设置成该异常模式将要返回的地址,对于有些异常模式,R14的值可能与将返回的地址有个常数的偏移量.
    程序计数器R15又被记作PC.由于ARM采用了流水线机制,当正确读取了PC的值时,该值为当前指令地址加8个字节.也就是说,对于ARM指令集来说,PC指向当前指令的下两条指令的地址.由于ARM指令是字对齐的,PC值的第0位和第1位总为0.
    CPSR(当前程序状态寄存器)可以在任何处理器模式下被访问.它包含了标志位,中断禁止位,当前处理器模式标志以及其它的一些控制和状态位.每一种处理器异常模式下都有一个专用的物理状态寄存器,称为SPSR(备份程序状态寄存器).当特定的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容.在异常中断返回退出时,可以用SPSR种保存的值来恢复CPSR.由于用户模式和系统模式不是异常中断模式,所以它们没有SPSR.当用户模式或者系统模式中访问SPSR,将会产生不可预知的结果.
    SPSR与CPSR的格式相同.CPSR的格式如图2.3[6]所示:
    31 30 29 28 27 8 7 6 5 4 0
    N
    Z
    C
    Q
    Unused
    I
    F
    T
    Modes
    图2.3
    N(Negative),Z(Zero),C(Carry),V(Overflow)统称为标志位.大部分的ARM指令可以根据CPSR中的这些条件标志位来选择性地执行.各条件标志位的具体含义如下:
    N:当两个补码表示的有符号整数运算时,N=1表示运算的结果为负数;N=0表示运算结果为正数或零;
    Z:Z=1表示运算结果为零;Z=0表示运算的结果不为零;
    C:在加法指令中,当结果产生了进位,则C=1;其他情况下C=0;
    在减法指令中,当运算中发生借位,则C=0;其他情况下C=0;
    V:对于加/减法运算指令,当操作数和运算结果为二进制的补码表示的带符号数时,V=1表示符号位溢出
    CPSR的低8位称为控制位,当异常中断发生时这些位发生改变.在特权模式下,软件可以修改这些控制位:
    I:普通中断禁止位;I=1时禁止IRQ中断.
    F:快速中断禁止位;F=1时禁止FIQ中断.
    T:对于ARMv4以及更高的版本的T系列的ARM处理器
    T=0表示执行ARM指令
    T=1表示执行Thumb指令
    对于ARMv5以及更高版本的非T系列的ARM处理器
    T=0表示执行ARM指令
    T=1表示强制下一条执行的指令产生为定义指令中断
    Modes位控制处理器模式,在这不再赘述.
    2.2.3 ARM存储系统
    这里仅仅介绍ARM编程模型中与存储系统相关的一些概念.
    ARM体系中的存储空间
    ARM体系中使用单一的平板地址空间.该地址空间的大小232个8位字节.这些字节单元的地址是一个无符号的32位数值,其取值范围为0到232-1.
    ARM的地址空间也可以看作是230个32位的字单元.这些字单元的地址可以被4整除,也就是说该地址的低两位为0b00.地址为A的字数包括地址A,A+1,A+2,A+3这4个字节单元的内容.
    ARM的存储器格式
    在ARM体系中,每个字单元中包含4个字节单元或者两个半字单元,其中1个半字单元中包含两个字节单元.但是在字单元中,4个字节哪一个是高位字节,哪一个是低位字节,则有两种不同的格式:big-endian格式和little-endian格式.
    在big-endian格式中,对于地址为A的字单元包括字节单元A,A+1,A+2,A+3,其中字节单元由高位到低位字节顺序为A,A+1,A+2,A+3;地址为A的字单元包括半字单元A,A+2,其中半字单元由高位到地位字节顺序为A,A+2;地址为A的半字单元包括字节单元A,A+1,其中字节单元由高位到低位字节顺序为A,A+1.这种存储器格式如下图所示:
    31 24 23 16 15 8 7 0
    字单元A
    半字单元A
    半字单元A+2
    字节单元A
    字节单元A+1
    字节单元A+2
    字节单元A+3
    图2.4 big-endian格式的存储系统
    在little-endian格式中,地址为A的字单元包括字节单元A,A+1,A+2,A+3,其中字节单元由高位到低位字节顺序为A+3,A+2,A+1,A;地址为A的字单元包括半字单元A,A+2,其中半字单元由高位到地位字节顺序为A+2,A;地址为A的半字单元包括字节单元A,A+1,其中字节单元由高位到低位字节顺序为A+1,A.这种存储器格式如下图所示:
    31 24 23 16 15 8 7 0
    字单元A
    半字单元A+2
    半字单元A
    字节单元A+3
    字节单元A+2
    字节单元A+1
    字节单元A
    图2.5 little-endian格式的存储系统
    2.2.4 ARM指令集介绍
    ARM指令系统属于RISC指令系统.标准的ARM指令每条都是32位,有些ARM核还可以执行16位的Thumb指令集.一般地,ARM指令具有以下特点[9]:
    指令的条件执行
    ARM指令都是条件执行,条件标志位位于程序代码字节的cond位(位28-31,下文会介绍).根据cond的不同编码,可以选择根据条件码标志决定指令的执行.
    灵活的第二操作数
    ARM指令的另一个重要的特点是数据处理类指令有灵活的第二操作数operand2(位与程序代码的位0-11).它既可以是立即数,也可以是逻辑运算数,使得ARM指令可以在读取数值的同时进行算术和移位操作.
    协处理器的作用
    ARM内核可提供协处理接口,通过扩展协处理器完成更加复杂的功能.因此ARM
    指令中还包括了多条协处理器指令,使用多达16个协处理器,允许将其他处理器通过协处理器接口进行紧耦合;ARM还包括几种内存管理单元的变种,包括简单的内存保护到复杂的页面层次.
    Thumb指令
    ARM在有的版本支持16位Thumb指令.Thumb指令集的整体执行速度比ARM
    32位指令集快,而且提高了代码密度.
    具有RISC指令的特点
    由于ARM指令属于RISC指令,所以多具有RISC指令的特点,指令少,且等长,
    便于充分利用流水线技术,使用多寄存器,且为简单的Load和Store指令.
    ARM指令按功能大致可以分为跳转指令,数据处理指令,乘法类指令,数据传送指令,协处理器类指令以及杂项指令(包括状态寄存器传送指令,乘法类指令,软件中断指令和断点指令).ARM指令字长为固定的32位,一条典型的ARM指令编码格式如下[9]:
    31 28 27 26 2524 21 20 19 16 15 12 11 0
    Cond
    00
    I
    opcode
    S
    Rn
    Rd
    operand2
    图2.6
    其中:
    cond 位28-31,执行指令的条件编码.
    I 位25,决定operand2的格式.
    opcode 位21-24,操作码.
    S 位20,表示结果是否影响状态寄存器标志位:1影响,0不影响.
    Rn 位16-19,第一操作数的寄存器编码.
    Rd 位12-15,目标寄存器编码.
    operand2 位0-11,表示第二操作数编码.
    ARM指令包括60多个指令,并且支持多种寻址方式:寄存器寻址,立即数寻址,寄存器间接寻址,寄存器变址寻址,多寄存器寻址,堆栈寻址,块拷贝寻址以及相对寻址等.对于具体的指令集,可以参考相关文档.
    2.2.5 ARM体系的异常中断
    在ARM体系中通常有以下3种方式控制程序的执行流程[6]:
    在程序正常执行过程种,每执行一条ARM指令,程序计数器寄存器(PC)的值加4个字节;每执行一条Thumb指令,程序计数器寄存器的值加2个字节.整个过程是按顺序执行.
    通过跳转指令,程序可以跳转到特定的地址标号处执行,或者跳转到特定的子程序处执行.
    当异常中断发生时,系统执行完当前指令后,将跳转到相应的异常中断处理程序处执行.在异常中断处理程序执行完成后,程序返回到发生中断的指令的下一条指令处执行.
    ARM体系中中断向量表中指定了各异常中断及处理程序的对应关系.它可以放在存储地址的低端,也可以放在存储地址的高端.异常中断向量表的大小位32字节,其中每个异常中断占据4个字节大小,保留了4个字节空间.
    每个异常中断对应的中断向量表中的4个字节的空间中放了一个跳转指令或者一个向PC寄存器中赋值的数据访问指令.通过这两种指令,程序将跳转到相应的异常中断处理程序处执行.
    下面是ARM体系中各异常中断向量的描述表:
    中断向量偏移地址 异常中断类型 异常中断模式 优先级(6最低)
    0x00 复位 监管模式 1
    0x04 未定义的指令 未定义指令中止模式 6
    0x08 软件中断 监管模式 6
    0x0c 指令预取中止 中止模式 5
    0x10 数据访问中止 中止模式 2
    0x14 保留 未使用 未使用
    0x18 普通中断请求 外部中断模式 4
    0x1c 快速中断请求 快速中断模式 3
    表格2.2
    ARM处理器对异常中断的响应过程如下:
    保存处理器当前状态,中断屏蔽位以及各条件标志位.这是通过将当前程序状态寄存器CPSR的内容保存到将要执行的异常中断对应的SPSR寄存器中实现的.
    设置当前的程序状态寄存器CPSR中相应的位,包括设置CPSR中的位,使处理器进入相应的执行模式;设置CPSR中的位,禁止IRQ中断,当进入FIQ模式时,禁止FIQ模式.
    将寄存器lr_mode设置成返回地址.
    将程序计数器值PC设置成该异常中断的中断向量地址,从而跳转到相应的异常中断处理程序处执行.
    2.3 ARM最小系统描述
    对于任何一个系统,都希望不是一个虚拟系统,也就是说系统都希望是特定系统,即有具体的CPU,有具体的存储芯片以及有各种具体的外围控制模块.对于本课题来说,最终的bootloader也必须在具体的系统上运行起来.但是,任何一个ARM系统,在没有特定指定的条件下,仍然可以将通用特性(一些通用基本控制模块)描述出来,在此称之为ARM最小系统,见下图:

    Memory 控制器 GPIO控制
    模块
    系 外
    统 总线桥接 围
    总 串口
    线 总
    线
    ARM core

    中断控制器
    中断
    图2.7 ARM最小系统
    从上图中,可以了解一个基本的ARM最小系统通常包括以下几个部分:
    ARM CORE
    存储控制器:通过它系统可以接入各种DRAM以及ROM;
    总线桥接:系统总线与外围总线的连接器
    中断控制器:供各种设备提供中断服务
    串口
    GPIO控制模块:供各种外设以及系统扩充使用
    对于一些复杂系统,还可以有更多的功能性模块,特别是随着网络通信的发展,ARM系统中包括了更多的通讯控制模块.
    2.4 Intel Xscale系统构架
    本文最终实现以及验证平台是基于Intel Xscale构架的,因此在本节中主要介绍一下Intel Xscale的系统结构并简要介绍一下基于Xscale微架构的处理器PXA255.
    Xscale微构架处理器是为新一代无线手持式应用产品开发的嵌入式处理器,是PCA开发式平台架构中的应用子系统与通信子系统中的嵌入式处理器.图3.8[7]所示为Xscale系统结构图.
    IRQ FIQ
    Branch Target Buffer
    CP14 Debug/
    Management
    Trace
    Buffer
    Interrupt
    CP15 Config Request Instruction Instruction I-M
    Register Cache(32KB) MU
    Execution
    Coprocessor Core
    Interface Data Data Cache
    Address 32KB D-
    CP0 Data MMU Write
    Multiplier Mini D-Cache Buffer
    Accumulator
    JTAG Debug System
    Management
    图2.8
    Intel Xscale构架与ARM V5TE相兼容,其主要特性如下:
    采用7/8级超级流水线:动态跳转预测以及分支目标缓冲;
    支持多媒体处理技术:增加乘,加法器;累加器;兼容ARMV5TE指令;特定DSP型协处理器CP0;
    指令快存(I-Cache):32KB;
    数据快存(D-Cache):32KB;
    微小型数据快存(Mini D-Cache):2KB;
    指令存储器管理单元I-MMU:32路变换后备缓冲器TLB;
    数据存储器管理单元D-MMU:32路变换后备缓冲器TLB;
    中断控制器;
    总线控制器;
    调试接口;
    动态电源管理;
    Xscale微构架处理器的时钟可达1GHz,功耗1.6W,并能达到1200兆条指令/秒.目前,基于Xscale微构架的处理器有:IOP310,IOP321,PXA210,PXA250,PXA255,PXA26X等.
    2.4.1 PXA255介绍
    PXA250/PXA255应用处理器是Intel公司生产的第一代基于Xscale微构架的第一个系统芯片(ISOC)的处理器.它除了Xscale微内核外,还集成了许多世适用于手持式设备市场需要的外围设备.如图2.9[ 11]所示为PXA255应用处理器的框图.
    图2.9
    PXA255作为本课题的整个硬件平台核心,具有很强的处理能力,同时兼具低功耗和高集成度的特性.它内置了JTAG调试接口,存储器控制器,实时时钟和操作系统时钟,串口,并口,红外通信,蓝牙接口,AC97接口以及USB 1.1设备端控制模块等.
    第三章 Bootloader的概念
    本文在实现以及验证上都以开放源代码的linux为操作系统,因此,在众多举例以及图表描述中,多以linux系统为例子.但是,这并不防碍对许多概念的解释,其本质在任何操作系统中都是一致的,所不同的仅仅是实现形式.
    3.1 Bootloader的基本概念
    一个嵌入式Linux系统从软件的角度看通常可以分为四个层次:引导加载程序,Linux内核,文件系统,用户应用程序.
    用户应用程序
    文件系统

    Linux内核

    引导加载程序

    系统调用接口
    设备驱动
    操作系统核心机制
    (进程调度,内存管理,
    中断管理,时钟管理,
    文件系统,网络支持
    信号机制,同步机制
    等等)
    图 3.1
    引导加载程序是系统加电后运行的第一段代码.我们熟悉的PC中的引导程序一般由BIOS和位于MBR的OS bootloader(例如LILO或者GRUB)一起组成.然而在嵌入式系统中通常没有像BIOS那样的固件程序(有的嵌入式CPU有),因此整个系统的加载启动任务就完全由bootloader来完成.在嵌入式Linux中, 图3.1中的引导加载程序即等效为bootloader.
    简单地说,bootloader就是在操作系统内核运行前运行地一段小程序.通过这段小程序,我们可以初始化必要的硬件设备,创建内核需要的一些信息并将这些信息通过相关机制传递给内核,从而将系统的软硬件环境带到一个合适的状态,最终调用操作系统内核,真正起到引导和加载内核的作用.
    bootloader是依赖于硬件而实现的,特别是在嵌入式系统中.不同的体系结构需求的bootloader是不同的;除了体系结构,bootloader还依赖于具体的嵌入式板级设备的配置.也就是说,对于两块不同的嵌入式板而言,即使它们基于相同的CPU构建,运行在其中一块电路板上的bootloader,未必能够运行在另一块电路开发板上.
    Bootloader的启动过程可以是单阶段的,也可以是多阶段的.通常多阶段的bootloader能提供更为复杂的功能,以及更好的可移植性.从固态存储设备上启动的bootloader大多数是二阶段的启动过程,也即启动过程可以分为stage 1和stage 2两部分.
    3.2 Bootloader的操作模式
    大多数bootloader都包含两种不同的操作模式:"启动加载"模式和"下载"模式,这种区别对于开发人员才有意义.但从最终用户的角度看,bootloader的作用永远就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别.
    启动加载模式:这种模式也称为"自主"模式,即bootloader从目标机上的某个固体存储设备上将操作系统加载到RAM中运行,整个过程没有用户的介入.这种模式是bootloader的正常工作模式,因此当以嵌入式产品发布的时候,bootloader必须工作在这种模式下.
    下载模式:在这种模式下,目标机上的bootloader将通过串口或者网络连接或者其它通信手段从主机下载文件,比如:下载内核镜像和根文件系统镜像等.从主机下载的文件通常首先被bootloader保存到目标机的RAM中,然后被bootloader写到目标机上的FLASH类固态存储设备中.Bootloader的这种模式通常在第一次安装内核与根文件系统时使用;此外,以后的系统更新也会使用bootloader的这种工作模式.工作于这种模式下的bootloader通常都会向它的中断用户提供一个简单的命令行接口.
    3.3 Bootloader的概念扩展
    Bootloader最主要的功能是引导加载内核镜像.但是随着嵌入式系统的发展,bootloader已经逐渐在基本功能的基础上,进行了扩展,bootloader可以更多地增加对具体系统的板级支持,即增加一些硬件模块功能上的使用支持,以方便开发人员进行开发和调试.从这个层面上看,功能扩展后的bootloader可以虚拟地看成是一个微小的系统级的代码包.
    3.4 ARM Bootloader的共性
    从上面bootloader的基本概念可以看出,bootloader的设计与实现是与具体的CPU以及具体的硬件系统紧密相关的,从上一章的实现就可以看出,要实现一个通用的ARM bootloader,即要适合所有的ARM处理器以及硬件系统,是不太可能的事情.另外,不同的操作系统,可能对具体的bootloader还会有另外额外的要求.但是,我们还是可以根据ARM的体系结构,从理论上总结出一些ARM系统bootloader实现的共性,而这仅仅局限于理论上.至于涉及到具体的设计与实现,可以参看第四章的具体内容,并根据自己的硬件系统进行移植,也不是一件困难的事情.
    对于ARM bootloader的一些共同特性,理论上只局限于bootloader的基本功能,因为扩展功能众多,可以有串口,USB,以太网接口,IDE,CF等,无法进行归纳与总结.
    对于一个ARM系统来说,本质上,bootloader作为引导与加载内核镜像的"工具",在实现上,必须提供以下几个功能,更确切地说,必须做到以下几点[ 1]:
    初始化RAM(必需):bootloader必须能够初始化RAM,因为将来系统要通过它保存一些Volatile数据,但具体地实现要依赖与具体的CPU以及硬件系统.
    初始化串口(可选,推荐):bootloader应该要初始化以及使能至少一个串口,通过它与控制台联系进行一些debug的工作;甚至与PC通信.
    创建内核参数列表(针对linux操作系统,推荐).
    启动内核镜像(必需):根据内核镜像保存的存储介质不同,可以有两种启动方式:FALSH启动以及RAM启动;但是无论是哪种启动方式,下面的系统状态必须得到满足:
    CPU寄存器的设置: R0=0;
    R1=机器类型;
    R2=启动参数标记列表在RAM中的起始地址;
    这三个寄存器的设置是在最后启动内核时通过启动参数来传递完成的.
    CPU模式: 关闭中断;
    属于SVC模式;
    Bootloader中没有必要支持中断的实现,这属于内核机制以及设备驱动管理的管理范畴;SVC模式是系统的一种保护模式,这样就可以进行一些只能在SVC模式下的操作,例如一些特定寄存器访问操作.
    Cache和MMU的设置: MMU必须关闭;
    数据cache必须关闭;
    指令cache可以关闭也可以开启;
    Bootloader中所有对地址的操作都是使用物理地址,是实在的实地址,不存在虚拟地址,因此MMU必须关闭.Bootloader主要是装载内核镜像,镜像数据必须真实写回SDRAM中,所以数据cache必须关闭;而对于指令cache,不存在强制性的规定,但是一般情况下,推荐关闭指令cache.
    Bootloader启动内核镜像的方法是通过跳转语句直接跳转至内核镜像的第一句指令语句.
    第四章 Bootloader的设计与实现
    本章开始叙述bootloader的设计与实现,主要涉及代码的实现以及系统存储空间的具体分配,基本上为软件的设计与实现.
    4.1 课题研究的平台环境
    在本章开始,首先介绍本文研究的硬件平台和软件开发平台.
    4.1.1 硬件平台
    Booltoader是与硬件环境息息相关的,特别是与具体的处理器与具体硬件系统.对于本本文,其具体的实现与验证都需要具体的硬件环境.本课题的硬件平台是围绕PXA255(前面章节已有介绍)搭建起来的.整个系统的硬件框架如图所示.
    图4.1
    选择以Intel Xscale系列的PXA255为核心的硬件系统,是因为该系统既与相关的项目重叠,而且Xscale核本身是StrongArm的一个增强集,它与ARM V5TE构架兼容,另外PXA系列目前广泛应用于各类嵌入式系统中,是市场主流的嵌入式微处理器系列.对其研究,既有一定的典型性,又有一定的前瞻性.
    对于图4.1描述的硬件环境中,本课题需关注的是处理器,存储器以及在bootloader中需要支持的一些外部设备(如串口,USB,LCD)等.
    4.1.2 软件环境以及软件开发工具
    4.1.2.1 操作系统
    本文操作系统使用的是开放源码的Linux操作系统,因此最终bootloader的实现是针对Linux操作系统的,加载的kernel镜像也是Linux kernel生成.
    4.1.2.2 编程语言
    由于本文与芯片底层关系密切,甚至还涉及到相关协处理器,因此在与硬件芯片底层相关的部分使用汇编,这样可以大大提高性能以及移植性;而在很多系统板级的支持上,则使用C语言,以提高代码的可读性以及层次性.
    4.1.2.3 编译环境
    本文所使用的编译工具均为GNU的编译工具.其主要包括汇编器as,C编译器gcc,C++编译器g++,连接器ld和相关的二进制转换工具,而基于ARM平台的工具分别为arm-linux-as,arm-linux-gcc,arm-linux-g++,arm-linux-ld和,这些也是平常所说的交叉编译工具.GNU的编译器功能非常强大,共有上百个操作选项,不过在本课题实际开发中只需要用到有限的几个,大部分采用缺省选项.本课题使用的版本为2.95.3.GNU的所有开发工具都可以从www.gnu.org上下载,基于ARM的工具可以从www.uclinux.org获得.
    一般地,GNU工具地开发流程如下:(1)编写C,C++或者汇编源程序(2)用相关编译器生成目标文件(3)编写连接脚本(4)用连接器生成最终文件(elf格式)(5)用二进制转换工具生成可下载的二进制代码.
    在以后篇幅介绍具体bootloader的实现时,基本就根据以上流程进行开发.
    4.2 Bootloader的总体设计
    4.2.1 阶段设计
    在前面的章节中,已经介绍过bootloader的启动可以是分阶段的.在设计时,我们将bootloader分为两个阶段:阶段1和阶段2.分为两个阶段的原因是因为:(1)基于编程语言的考虑.阶段1用主要用汇编语言,它主要进行与CPU核以及存储设备密切相关的处理工作,进行一些必要的初始化工作,是一些依赖于CPU体系结构的代码,为了增加效率以及因为涉及到协处理器的设置,只能用汇编编写,这部分直接在FLASH中执行;阶段2用一般的C语言,来实现一般的流程以及对板级的一些驱动支持,这部分会被拷贝到RAM中执行.(2)代码具有更好的可读性与移植性:若对于相同的CPU以及存储设备,要增加外设支持,阶段1的代码可以维护不变,只对阶段2的代码进行修改;若要支持不同的CPU,则基础代码只需在阶段1中修改.
    4.2.2 地址规划设计
    当bootloader阶段设计好之后,需要考虑的是镜像存储的地址分配:总镜像保存在什么地方,阶段2对应的镜像会被拷贝到什么地方;内核镜像原先存放在什么地方,bootloader会把它又重新加载到什么地方;如何进行准确的地址规划以保证没有相互冲突等等,这些都是本节需要考虑的范畴.
    PXA255的地址空间是统一寻址的,对于本课题的硬件系统,外接32M的FLASH,由片选信号CS0选择,因此映射到物理地址的0x00000000处;对于64M的SDRAM,则对应PXA255的SDRAM BANK0,映射到0xa0000000处.
    PXA255系统复位后,从物理地址的0x00000000开始执行第一段代码,这个地址是由 CPU制造商预先安排的.而我们基于PXA255构建的系统将固态存储设备FLASH映射到这个地址上.当Bootloader放到FLASH的起始处后,系统加电或者复位后,CPU将首先执行bootloader程序.
    本文所使用的内核镜像以及根文件系统镜像都被加载到SDRAM中运行,这样做是因为基于运行速度的考虑,尽管在嵌入式系统中内核镜像与根文件系统镜像也可以直接在ROM或FLASH这样的固态存储设备中直接运行.所以bootloader在启动时以及加载内核时通常要考虑这一点.下图为具体的存储布局图:
    以上为SDRAM地址空间 内核镜像拷贝处
    0xa0300000
    镜像2被拷贝处
    0xa0000000

    内核镜像存放处
    FLASH地址空间 0x000c0000

    (镜像2存放处) 0x00040000
    镜像1存放处
    0x00000000
    图4.2
    虽然bootloader最终生成一个可执行镜像,但是为了更能清楚的解释其实现流程,在此虚拟地将其与启动阶段相对应起来,分成两个镜像:镜像1和镜像2(事实上,在编译过程中是会形成这两个镜像,除了一个总的镜像镜像1,还有被拷贝至SDRAM中的镜像2).在本课题中,将物理地址的0x00000000-0x00040000存放bootloader的镜像,内核镜像放在物理地址开始0x000c0000之后的1M空间内(内核镜像一般都小于1M大小);在前面的阶段设计中已经谈及镜像2在SDRAM中运行,这样bootloader的启动速度会大大加快,因此本课题将镜像2放在SDRAM的起始地址0xa0000000处运行;而内核镜像则规划至物理地址的0xa0300000处执行(放在这边是基于这样的考虑:linux内核会在SDRAM开始处存放一些全局数据结构,比如启动参数和内核页表等,所以预留一段空间).
    4.2.3 模式设计
    对于普通用户来说只需要bootloader的启动加载模式,但是对于开发者来说,则需要下载模式,因为他们需要时时刻刻地进行一些镜像的更新.为了在两者之间做到兼顾,本课题既支持启动加载模式,也支持下载模式,具体思路为:在bootloader做完一些硬件初始化工作后,而在加载内核镜像之前,先在一定的时间内等待有没有用户有键盘输入,如果没有,则为启动加载模式,直接加载内核镜像进行启动;如果有,则进入命令行格式,这时开发者就可以根据自己的需要以及bootloader的支持情况,做一些其他的工作.模式的转换设计主要在阶段2中实现.
    综合起来,整个bootloader的实现流程可以如下图所示:
    基本硬件初始化
    阶段1
    拷贝阶段2镜像至RAM
    进入阶段2开始执行
    扩展功能所需硬件初始化

    拷贝内核镜像至RAM中
    阶段2
    等待50ms
    看是否串口有
    输入
    是 否
    进入下载模式 跳转至内核镜像
    接收命令
    4.3 Bootloader的具体实现
    从本节开始介绍本课题的bootloader的具体代码实现.
    4.3.1 阶段1的代码实现
    阶段1通常包括以下步骤(以执行的先后顺序):
    一些基本硬件的初始化工作
    为加载镜像2准备RAM空间(RAM足够的情况下可以省略)
    把镜像2拷贝到RAM空间
    跳转到镜像2的入口点(一般是C入口点)
    下面结合相关具体代码描述相关的实现,其中涉及到的汇编代码可以参考ARM指令
    集,涉及到的对PXA255协处理器的操作可以参考参考文献中相关的介绍.
    本课题将阶段1中的这段汇编文件命名为rom_reset.s.
    和一般的汇编一样,首先要做的是一些伪代码:
    (1)定义ARM各模式的栈大小.
    .equ MonStackSz, 4096
    .equ FiqStackSz, 4096
    .equ IrqStackSz, 4096
    .equ AbtStackSz, 4096
    .equ UndStackSz, 4096
    .equ SysStackSz, 4096
    (2)申明各模式的栈.
    .global MonStack
    .global FiqStack
    .global IrqStack
    .global AbtStack
    .global UndStack
    .global SysStack
    (3)将各模式的栈与栈大小结合起来,即为各栈分配栈大小.
    .comm MonStack, MonStackSz
    .comm FiqStack, FiqStackSz
    .comm IrqStack, IrqStackSz
    .comm AbtStack, AbtStackSz
    .comm UndStack, UndStackSz
    .comm SysStack, SysStackSz
    (4)接着就是申明一些标号量(代码略).
    这些基本工作做完之后,开始进入真正的初始化工作,标识为正文段(.text段,见后
    面连接章节).
    以reset标号标识,一开始处设置异常中断向量表,当是冷启动时,直接跳转至对应处进行启动:
    reset:
    b coldstart
    b undefined_instruction
    b software_interrupt
    b abort_prefetch
    b abort_data
    b not_used
    b interrupt_request
    b fast_interrupt_request
    系统正常启动,都属于冷启动,程序直接跳转至coldstart标号处执行.接着完成的功能
    包括:
    使能各协处理器,PXA255中很多特殊功能都需要借助于协处理器,比如存储管理.
    ldr r0, =0x2001
    mcr p15,0,r0,c15,c1,0
    关闭MMU,内部指令/数据cache以及写缓冲区,ARM体系bootloader中都无需MMU的功能,所有的地址都直接使用物理地址;cache也都关闭,原因可参看上一章相关内容 .
    ldr r0, =0x00000078
    mcr p15,0,r0,c1,c0,0
    清空TLB,Caches以及写缓冲区,当系统冷启动时所有的保留数据都以无效处理,因此都要清空,况且cache都已经关闭.
    ldr r0, =0x00000000
    mcr p15,0,r0,c8,c7,0
    mcr p15,0,r0,c7,c7,0
    mcr p15,0,r0,c7,c10,4
    开系统各存储空间域的访问权限;ARM体系中系统的存储空间分为最多的16个域,每个域对应一定的内存区域,该区域具有相同的访问控制属性.MMU中寄存器C3用于控制与域相关属性的配置.
    mvn r0, #0
    mcr p15,0,r0,c3,c0,0
    以上涉及的是相关存储方面通用的处理,接着就要开始具体的硬件初始化的工作了,与
    具体的CPU以及具体的存储芯片大小有关.
    屏蔽所有的中断:为中断提供服务通常是操作系统设备驱动程序的责任,因此在bootloader的执行全过程中可以不必相应任何中断,中断屏蔽是通过写CPU提供的中断屏蔽寄存器来完成的.
    ldr r1, =(INT_BASE | INT_ICMR) /* 通过中断控制器基地址与相关偏移量获得中断屏蔽寄存器的地址*/
    ldr r2, =0x0
    str r2, [r1]
    设置CPU的速度与时钟频率:系统复位后对于CPU的速度与时钟频率都有一个初始的默认值,可以直接使用该值,到内核启动初始化的时候再设置;也可以在此处直接设置.
    ldr r1, =CLK_BASE
    ldr r2, =0x00000141
    str r2, [r1, #CLK_CCCR]
    3. SDRAM 的初始化以及使能,这个设置与具体的RAM大小有关,包括正确地设置系统的内存控制器以及各内存库控制寄存器.具体代码稍长,在这里相关代码略去.
    相关基本硬件初始化之后,开始设置模式.
    为ARM的各个操作模式设置称栈指针:在前面的伪代码中已经为各个操作模式分配相应的栈区,而且ARM各个模式下都有自己通用的栈顶指针SP,现在要做的是在不同模式下将该模式下的SP指向栈顶.每个模式的处理都类似,都是通过设置状态寄存器CPSR(可以参看前面章节的介绍)来完成,并且每个模式都要完成.以IRQ模式为例:
    mrs r0, cpsr /* 保存CPSR的值 */
    bic r0, r0, #0x1f /* 清楚所有的模式位*/
    orr r0, r0, #0x12 /* 将系统设置为IRQ模式*/
    msr CPSR_c, r0 /*将改变后的状态值发会给CPSR*/
    ldr sp, =(IrqStack + IrqStackSz - 4) /* 设置SP*/
    将系统模式设置成监管模式(SVC),并设置SP.
    mrs r0, cpsr
    bic r0, r0, #0x1f
    orr r0, r0, #0x13
    msr CPSR_c, r0
    ldr sp, =(MonStack + MonStackSz - 4)
    到此时,用汇编编写的硬件基本初始化代码完成,对于阶段1来说,剩下的工作就是将镜像2拷贝到SDRAM的指定处,这个工作我们通过一小段C代码来完成,文件名为cstart.c,完成拷贝功能的函数设为cstart.则在汇编末尾可以通过跳转指令进入阶段1的这一小段C代码:
    bl cstart
    在编译时,本课题将镜像2存放在一个二进制数组umon[ ]之中,具体的编译实现在后面相关篇幅中会有介绍.这样的话,将镜像2拷贝到SDRAM中的实现就比较简单,通过以下代码代码就可以实现:
    memcpy((char *)UMON_RAMBASE,(char *)umon,(int)sizeof(umon));
    其中,UMON_RAMBASE即为0xa0000000.接着通过以下代码就可以将执行流程跳转至阶段2开始执行:
    void (*entry)();
    entry = (void(*)())UMON_RAMBASE;
    entry();
    4.3.2 阶段2的代码实现
    本文在阶段2的处理,更多的是对bootloader的功能性的扩展,具体集中在对本文的硬件系统的支持,并在此基础上实现bootloader的两种操作模式.
    4.3.2.1 基本功能的实现
    基本功能的实现包括加载内核镜像并转让控制权给内核,具体的实现步骤包括:
    前面的存储布局中已经提过,本课题的内核镜像存放在地址0x000c0000处.根据linux内核压缩镜像的构成,离镜像起始的0x24处存放的是一个32位数来标识其身份,因此首先判断(0x000c0000+0x24)处的一个32位数是否等于0x016F2818(标识符).
    若检查一致,则将位于FLASH中的kernel镜像直接拷入SDRAM中,地址设为0xa0300000,如下:
    memcpy(ram_start, (uchar *)KERNEL_FLASH_START, KERNEL_MAX_SIZE)
    其中,KERNEL_MAX_SIZE设为1M,ram_start为0xa0300000,KERNEL_FLASH_START为0x000c0000.
    设置启动参数,现今的Linux内核都以链表(tagged list)的形式来传递参数,启动参数以标记ATAG_CORE开始,ATAG_NONE结束.任何bootloader想要传递给kernel的参数都可以添加在在这两个表头与表尾之间.相关的代码如下:
    struct tag *params = (struct tag *)BOOT_PARAMS;
    params->hdr.size = tag_size(tag_core);
    params->hdr.tag = ATAG_CORE;
    params->u.core.flags = 0;
    params->u.core.pagesize = 0;
    params->u.core.rootdev = 0;
    params = tag_next(params);
    //如果有参数传递,在此添加
    params->hdr.size = 0;
    params->hdr.tag = ATAG_NONE;
    其中,相关数据结构如下:
    struct tag_header {
    ulong size;
    ulong tag;
    };
    struct tag {
    struct tag_header hdr;
    union {
    struct tag_core core;
    struct tag_mem32 mem;
    struct tag_videotext videotext;
    struct tag_ramdisk ramdisk;
    struct tag_initrd initrd;
    struct tag_serialnr serialnr;
    struct tag_revision revision;
    struct tag_videolfb videolfb;
    struct tag_cmdline cmdline;
    struct tag_acorn acorn;
    struct tag_memclk memclk;
    } u;
    };
    直接跳转至RAM中的内核镜像的第一条指令执行:
    linux_kernel = (void (*)(int, int, struct tag *))ram_start;
    linux_kernel(LINUX_ARG0, LINUX_ARG1, tagparams);
    其中,三个参数的函数如下:
    LINUX_ARG0固定为0;
    LINUX_ARG1为表征系统机器类型,要与内核中的一致;
    Tagparams为系统传给内核的参数列表的首物理地址.
    以汇编角度看,最终这三个参数会对应系统寄存器R0,R1和R2.
    4.3.2.2 扩展功能的实现
    本文对bootloader的设计决定了实现的bootloader不仅仅起到加载内核镜像这一基本功能,而是把bootloader看作是一个虚拟的小系统,让其对硬件板级系统有更多的支持以为系统开发者提供方便.
    串口的支持
    本文将串口的支持放在扩展功能的范畴内,事实上现在一般都已经存在一种共识:bootloader应该串口功能的支持,能够从串口终端正确地收到打印信息.
    串口打印信息支持
    向串口终端打印信息是一个非常重要而且有效地调试手段.
    基于以上的原因,本课题没有通过外界的触发,而是直接在bootloader的阶段2代码中,初始化串口,串口初始化时将其配置成8位数据位,1停止位,无奇偶校验,波特率为115200.此串口使用PXA255内置的FF UART(全功能串口).
    另外在串口的支持代码中,增加相关接收与发送数据应用接口getchar( void )以及putchar(char c).当要接收数据时,可以通过调用接收数据函数接收数据;对于发送数据,主要将数据转发给屏幕显示函数,使其能够在屏幕上显示相应的字符,以达到打印信息的功能.
    下载模式的实现
    有了串口的支持,就可以实现下载模式的实现.在基本功能实现的第四个步骤(也就是启动内核)之前,首先使得代码流程延迟一段时间,接着调用串口的接收函数看是否有输入(开发者对应的是键盘输入),如果有数据,则bootloader不加载运行内核镜像,转入等待命令状态;如果没有,则加载运行内核镜像.
    udelay(50000); /* 等待用户输入*/
    if(getachar())
    return; /* 返回之后进入命令行等待状态 */
    else{ /* 否则运行内核*/
    linux_kernel = (void (*)(int, int, struct tag *))ram_start;

    linux_kernel(LINUX_ARG0, LINUX_ARG1, tagparams);
    }
    那么在等待命令状态后,bootloader又如何进行工作呢
    在本文中,定义了一个数据结构monCommand(如下),它主要用来描述一个命令,结构体中,name项指向具体的命令,第二项指向该命令对应的执行函数,最后一项则指向一些帮助文档说明,例如命令的用法等等.
    struct monCommand {
    char *name;
    int (*func)();
    char **helptxt;
    };
    本文中静态地维护一个命令数组,数组成员都是monCommand结构的,将要支持的命令以及相关处理保存在该数组中.Bootloader进入命令行状态后,一直等待串口的输入(即命令),当终端有输入时,读取数据,然后上面提过的数组中查找看是否有注册过,如果有立即执行对应的执行函数,如果没有,则继续等待.
    这样,以用户通过敲击键盘作为数据输入,bootloader通过串口接收到数据为触发,bootloader就进入等待命令状态,也同时进入下载模式;紧接着通过用户的命令,bootloader的执行完成相关的下载操作或者其它的处理.
    通过串口使用XMODEM协议进行下载内核镜像
    通过串口的接收功能可以进行内核镜像的下载,由于硬件串口以及相应软件上的支持使
    得硬件以及链路层可以完全支持,关键的问题在于上层的通信协议以及要顾及到PC端的实现(下载内核镜像一般放在PC端).由于bootloader中调试信息也是通过串口输出,如果上层协议处理不当,两者会有冲突.考虑到PC端都使用WINDOWS操作系统,它自带的超级终端(可以使用多种传输协议)非常方便,因此本课题在bootloader中使用XMODEM协议来作为串口传输的上一层协议,PC端就直接使用超级终端.
    下面简单介绍一下XMODEM协议:
    XMODEM协议是一种使用拨号调制解调器的个人计算机通信中广泛使用的异步文件运输协议.这种协议以128字节块的形式传输数据,并且每个块都使用一个校验和过程来进行错误检测.如果接收方关于一个块的校验和与它在发送方的校验和相同时,接收方就向发送方发送一个认可字节.然而,这种对每个块都进行认可的策略将导致低性能,特别是具有很长传播延迟的卫星连接的情况时,问题更加严重.因此延伸出与XMODEM想似的协议.使用循环冗余校验的与XMODEM相应的一种协议称为XMODEM-CRC.还有一种是XMODEM-1K,它以1024字节一块来传输数据[14].
    具体的数据包格式如表4.1[14]所示(按低字节开始):
    表示为标准xmodem协议,每次传送128字节
    或者
    表示为1k xmodem协议,每次传送1024个字节
    占1个字节
    数据块号,第一个数据块为01h
    占1个字节
    数据块反号
    占1个字节
    数据块
    占128或者1024个字节
    校验位.使用求和和CRC校验
    占1个或者2个字节
    表4.1
    定义: 01H, 02H, 04H , 06H , 15H , 18H
    具体的数据流如下:
    发送端 接受端
    <---
    01 FE -data- --->
    <---
    02 FD -data- xx ---> (未成功)
    <---
    02 FD -data- xx --->
    <---
    03 FC -data- xx --->
    <---
    --->
    <---
    图4.3
    实现上,首先在命令数组注册命令"xmodem",以及相应的执行函数Xmdoem,帮助函数xmodemhelp.当bootloader进入等待命令状态后一直等待接收串口数据,用户端键入"xmodem –d",则串口接收到数据后,在表中查找到命令"xmodem",立刻执行函数int Xmodem(int argc,char *argv[]),然后确认参数为"-d",说明是下载功能,代码就利用xmodem协议通过串口与PC端利用超级终端通过串口进行数据传输.
    为了防止冲突,最终传输的内核镜像仍然存放在物理地址的0xa0300000处,与设计中的一致.
    通过USB接口进行镜像下载
    通过串口进行内核镜像的下载,受到速度的限制,当开发者以后使用ramdisk(linux中的概念)时,通常要下载若干兆的数据,这时串口的速度劣势更加明显地表现出来了.本课题使用的硬件环境中,CPU PXA255芯片内置了一个USB 1.1设备端的控制模块,因此可以在bootloader中增加对USB传输的支持.
    通过USB进行传输,硬件底层以及链路层都通过USB1.1协议,但是传输是双方面的,双方如何进行通信,如何进行同步,则成为关键问题.由于bootloader中增加该功能扩展,有着很强的目的性,就是为了下载内核镜像,因此在本课题中自己定义了一套简单的传输协议,包括通信流程以及具体的数据包格式.在本功能中,主机对应PC,从机对应本文的硬件系统.
    本文中通信双方数据传输都使用数据包(packet)格式,具体格式如下:
    数据包格式:
    数据包头 Header
    数据块 block
    每个packet都有一个packet header,其定义如下:
    typedef struct tagUSB_PACKET_HEADER
    {
    UCHAR headerSig[HEADER_SIG_BYTES];
    UCHAR pktType;
    UCHAR Reserved;
    USHORT payloadSize;
    } USB_PACKET_HEADER, *PUSB_PACKET_HEADER;
    其中headerSig为头标志,本课题设为"kITL",payloadSize为后面数据块Block Data的长度,以字节为单位.
    pktType为数据包类型,表明Header后面所跟的Block Data的类型.我们定义了三种类型:
    Boot Request:此时pktType=0xBB,后面数据块Block Data为USB_BOOT_REQUEST类型,它是由从机向主机发送,请求开始传输数据,定义为:
    typedef struct tagUSB_BOOT_REQUEST
    {
    char PlatformId[KITL_MAX_DEV_NAMELEN+1];
    char DeviceName[KITL_MAX_DEV_NAMELEN+1];
    short reserved;
    } USB_BOOT_REQUEST, *PUSB_BOOT_REQUEST;
    Boot ACK:此时pktType=0xDD,后面的数据块Block Data为USB_BOOT_ACK类型.它是主机接收到Boot Request请求后向从机发送的,表明主机已准备好向从机发送数据,定义为:
    typedef struct tagUSB_BOOT_ACK
    {
    DWORD fJumping;
    } USB_BOOT_ACK, *PUSB_BOOT_ACK;
    Data:此时pktType= 0xCC,后面的数据块Block Data为传输数据块,它是由主机发送给从机的.它的前四个字节为USB_BLOCK_HEADER,后面为真正的传输数据.
    Block Data:
    USB_BLOCK_HEADER
    DATA
    其中USB_BLOCK_HEADER定义如下:
    typedef struct tagUSB_BLOCK_HEADER
    {
    DWORD uBlockNum; /*本数据块编号.第一个数据块为1*/
    } USB_BLOCK_HEADER, *PUSB_BLOCK_HEADER;
    具体的通信过程为:
    客户端(即从机)调用USBSendBootRequest()函数向主机端发送Boot请求;
    主机端程序USBBoot一旦收到Boot请求,立即向客户端发送Boot ACK应答,表示主机端已准备好;
    客户端接受到Boot ACK后,进入数据传输阶段;
    客户端向主机端发送数据请求包(Data request),在包中指明所要数据包号(第一个数据包号为0);
    主机端程序接收到数据请求包(Data request),则向客户端发送数据包;重复过程4,5,直到数据传送结束.
    在bootloader中增加USB传输支持,与串口有一点不同的是:串口的独特作用被默认为bootloader的基本功能,因此串口直接进行初始化,无需任何的触发;而为了bootloader最终生成的镜像文件不至于太大,只有在bootloader进入等待命令行状态后,用户敲入"USB"命令后,才会初始化USB硬件控制模块,当用户无需USB功能时,只要不给系统发送USB的命令,该模块就不会被使能,也就不会进行工作.
    对FLASH擦写操作的支持
    前面已经提及,通过串口或者USB更新内核镜像,仅仅做的是将内核镜像存放在SDRAM的具体地址处.SDRAM的一个特点是断电内容会消失,这样的话,如果将内核镜像下载完之后,系统重新启动,则系统使用的还是原先的镜像,更新镜像没有达到最终的目的.为了解决这个问题 ,本文在bootloader的实现过程中,增加了对FLASH的擦写操作支持.
    在我们的硬件系统中,FLASH使用的是Intel的28f128系列.在对该芯片的支持中,必须在底层完成对FLASH的擦除以及写的处理函数供上层调用,具体实现与存储芯片的一套机制相关,与本课题相去甚远,在这不再赘述.在bootloader的阶段2中,先初始化该芯片.然后与串口处理一样,在命令数组中注册命令"flash",处理函数"falshcmd";当bootloadr进入等待命令状态后,用户键入以"flash"开始的一串命令(具体命令见后面章节),进入flashcmd函数,该函数会通过后面的参数,来判断开发者的意图,紧接着调用芯片底层的处理函数来完成具体的操作.
    宏的应用控制
    在前面的总体设计中,已经为bootloader生成的镜像分配了256K的存储空间.随着它扩展功能的增加,各个外设的支持,会使得最终生成的镜像文件越来越大,很可能随着以后实现的深入造成存储空间不够.更为重要的是,对于普通用户来说,这些扩展功能都是多余的,如果系统作为产品发布,根本不需要这些功能,用户也看不到这些功能.因此,需要提供一种方案,只有当开发者需要特定的功能时,该模块才特定的支持.
    控制宏的加入就是为了解决这个问题.本文中,添加一个配置的控制文件config.h,在其中针对每个扩展功能,都有一个宏来控制,"1"表示该宏打开,需要对应的功能;"0"表示该宏关闭,不需要对应的功能.以USB为例,定义:
    #define USB_SUPPORT 1
    并且用它来控制软件上的USB模块的初始化以及所有有关USB操作的代码.当开发者不需要该功能时,将宏改为0,这样直接导致该模块不被初始化,相当于bootloader中未增加该扩展功能.其他模块都一样,除了串口,因为本课题中串口默认为必要的功能.
    这样做的另一个好处是在没有必要的时候不启动模块,既能节省功耗,又不至于与以后内核初始化该模块造成冲突.
    另外,对于本文所涉及到的地址,也全部使用宏来表示,这样做是为了更方便地进行移植.
    (7) 一些转换工具的加入
    在本文的bootloader的实现过程中,除了基本的代码之外,还另外需要一些转换工具,使得在链接生成镜像的时候,能完成本文所需要的一些特定功能.
    幸运的是,互联网技术的发展,能够将一些重要有用的资源进行网上共享,本课题中所涉及到的一些工具均为网上资源下载所得,并且能够非常干脆地完成其功能.这些工具包括:
    可执行的"elf",用来从一个elf文件中获得相关信息或者压缩生成elf文件的各个段,具体的功能按其参数选择为准:
    -B:将elf文件转换为bin文件
    -c:将big-endian控制结构转换为little-endian控制结构
    -f:显示elf文件的头
    -M:显示elf映射信息(考虑文件的偏移)
    -m:显示elf映射信息(不考虑文件的偏移)
    -s:显示elf各段的头
    可执行的"bin2array",用来将一个bin文件转化成一个二进制的数组.
    在实现中,专门创建一个tools的目录,来存放这些可执行的转化工具.
    4.3.3 代码的编译
    本文所用的编译器是GNU的gcc编译器,由于在本文实现的过程中编译工作都在PC端(X86平台),而最终编译的程序要在Xscale的平台上运行,为了解决这个问题,GNU有专门的针对ARM体系结构的交叉编译器arm-linux-gcc.
    对于一个C文件,编译它(例如a.c),只要
    a.o: a.c a.h
    $(CC) $(CFLAGS) a.c
    宏CC即为arm-linux-gcc,宏CFLAGS表示一些编译选项,这些选项都是根据本课题的实际情况进行配置的.这些选项为:
    CFLAGS = -c -Wno-format -O -fno-for-scope -mcpu=strongarm \
    -msoft-float -nostdinc -fno-builtin -g \
    -I. -I$(OBJ) -I$(COMMON) -I$(COMCSB) -I$(FLASHDIR) -o $@
    其中:
    -c表示只建立obj档,留到后面才进行链接;
    -O:优化,会根据CPU的构架进行一些优化;
    -mcpu=strongarm:表明cpu类型为strong arm;
    -msoft-float:表明支持软件模拟的浮点格式;
    -nostdinc:表明对于与一些标准头文件不在标准库目录下寻找,本课题中都有自己定义的头文件以及与一些库函数同名的函数;
    -fno-builtin:对于一些编译器自带的一些内嵌函数,如fabs,labs,memcmp,memcpy, sin,sqrt,strcmp,strcpy,strlen等等,除非在程序里前面以双下划线表识,否则编译器是不认的,这样做是因为本课题中的这些函数都重新进行了定义.
    -g:建立一些除错资讯,这样一些debug工具才能除错;
    -I:指定头文件.h的搜寻目录,后面即所跟这些目录.
    对于一个汇编文件(.S),它的编译命令为:
    obj/rom_reset.o: rom_reset.s config.h
    $(CC) $(ASMFLAGS) rom_reset.s
    ASMFLAGS为汇编编译选项,具体为:
    ASMFLAGS = -xassembler-with-cpp -mcpu=strongarm -c -g -o $@ \
    -D ASSEMBLY_ONLY -I. -I$(COMMON) -I$(COMCSB)
    -x:表示所编译文件的语言,-xassembler-with-cpp则就表示所编译的是汇编语言编写的程序.
    当然,为了实现简单,我们只需使用一个Makefile文件,在文件里面,将所有需要编译的文件,其依赖文件,编译选项以及编译环境都设定好.Makefile文件是用于自动编译和链接的,一个工程有很多文件组成,每个文件的改变都会导致工程的重新链接,但不是所有的文件都需要重新编译.Makefile中记录有文件的信息,在make的时候决定在链接的时候需要重新编译哪些文件.Makefile的宗旨就是:让编译器知道要编译一个文件需要依赖其他的哪些文件.
    4.3.4 目标文件的链接与转换
    各种源文件(包括汇编程序,C语言程序以及C++语言程序)经过编译器编译后生成ELF格式的目标文件,这些目标文件吓相应的C/C++运行时库经过ARM连接器处理后,生成ELF(Executable and Linkable Format)格式的镜像文件.ELF时目前最常用的一种,它定义了一些变量以及信息使得动态连接更有弹性,现在常用的ELF格式的文件包括:
    relocatable:这就是我们编译时产生的.o文件;
    executable:这就是一般最后生成的可执行文件;
    shared obj:这就是通常在/lib 或/usr/lib下那些可以动态连接的函数库;
    core:这就是core dump时产生的文件;
    但是需要注意的是,这里所讲的ELF格式档是广义的二进制文件,而不是仅仅单指可执行文件.
    4.3.4.1 镜像文件的组成
    ARM的这种ELF镜像文件是一个层次性结构的文件,其中包含了域(region),输出段(output section)和输入段(input section).各部分的关系如下[6]:
    一个镜像文件由一个或多个域组成.
    每个域包含一个或多个输出段.
    每个输出段包含一个或多个输入段.
    各输入段包含了目标文件中的代码和数据.
    下图4.4[6]描述了相关构成:
    输入段1.1.1
    输出段1.1
    输入段1.1.2
    输出段1.2
    域1 输入段1.2.1
    输出段1.3
    输入段1.3.1

    输入段1.3.2
    域2 输入段2.1.1
    输出段2.1
    输入段2.1.2
    输入段2.1.3
    内存 输出段 输入段
    图4.4 ARM镜像文件的组成
    下面具体介绍各组成部分.
    输入段中包含了4类内容:代码,已经初始化的数据,为经过初始化的存储区域,内容初始化为零的存储区域.每个输入段有相应的属性,一般的,可以分为只读的(RO),可读写的(RW)以及初始化为0的(ZI).连接器根据各输入段的属性将这些输入段分组,再组成不同的输出段以及域.
    一个输出段中包含了一系列具有相同的RO,RW,ZI属性的输入段.输出段的属性与其中包含的输入段的属性相同.在一个输出段内部,各输入段是按照一定的规则排序的.
    一个域中包含1~3个输出段,其中各输出段的属性各不相同.各输出段的排列顺序是由其属性决定的,其中,RO属性的输出段排在最前面,其次是RW属性的输出段,最后是ZI属性的输出段.一个域通常映射到一个物理存储器上,如ROM和RAM.
    镜像文件的各组成部分在存储系统中的地址有两种:一种是镜像文件位于存储器中时(也是该镜像文件开始运行之前)的地址,称为加载地址;一种是镜像文件运行时的地址,称为运行地址.之所以有这两种地址,是因为镜像文件在运行时,其中的有些域是可以移动到新的存储区域.比如:已经初始化的RW属性的数据所在的段在运行前可能保存在系统中的ROM中,在运行时,它被移动到RAM中.
    我们可以举个例子,如图5.5中,RW段的加载时地址为0x6000(指该段所占的存储区域的起始地址),该地址位于ROM中;RW段的运行段的运行地址为0x8000(指该段所占的存储区域的起始地址),该地址位于RAM中
    0xffff

    RAM 0xa000 ZI段

    0x8000 RW段
    RW段
    0x6000
    ROM
    RO段 RO段
    0x0000
    加载时地址映射关系 运行时地址映射关系
    图4.5
    通常一个镜像文件中包含若干的域,各域又可包含若干的输出段.因此,连接器就需要知道如下的信息,以决定如何生成相应的镜像文件.
    分组信息:决定如何将各输入段组这称相应的输出段和域.
    定位信息:决定各域在存储空间中的起始地址.
    ARM镜像文件的入口点有两种类型:一种是镜像文件运行时的入口点,称为初始入口点,另一种是普通的入口点[6].
    初始入口点是镜像文件运行时的入口点,每个镜像文件中只有一个唯一的初始入口点,它一般保存在ELF头文件中.
    普通入口点是在汇编程序中用ENTRY伪操作定义的,它通常用于标识该段代码是通过异常中断处理程序进入的.一个映象文件中可以定义多个普通入口点.初始入口点可以是普通入口点,但也可以不是普通入口点.
    如何让连接器知道相关的信息呢 这就要涉及到接下来的一节,配置文件的说明.
    4.3.4.2 连接脚本文件的编写
    在上一节中已经说过,GNU编译器生成地目标文件缺省为ELF格式.ELF文件由若干段(section)组成,如果不特别指明,由C源程序(本课题所用的基本语言)生成的目标代码中包含如下段:.text(正文段),包含程序的指令代码;.data(数据段),包含固定的数据,如常量,字符串;.bss(未初始化数据段)包含未初始化的变量,数组等.如果更加细致一点,还可以细分出.rodata(只读数据段),包含只读性质的数据;.got(偏移段),包含全局的偏移量表;以及一些遗留下来的.sdata段与.sbss等.连接器的任务就是将多个目标文件的.text,.data和.bss等段连接在一起,而连接脚本文件是告诉连接器从什么地方开始放置这些段.
    Gcc等编译器内置有缺省的连接脚本.如果采用缺省脚本,则生成的目标代码需要操作系统才能加载运行.本课题中,此时还没有操作系统的概念,为了能使代码能在本课题的硬件系统上直接运行,需要编写自己连接脚本文件.一般地,具体的连接文件的格式与具体的连接器有关,并不存在通用的格式.具体格式要能够让具体的连接器所识别解析.
    在前面的章节中提过,本课题的镜像可以包括镜像1(总镜像)和镜像2,镜像1中的阶段1部分在FLASH中直接运行,镜像2在SDRAM 中运行.
    本文中镜像1的连接脚本文件(设为rom.lnk)如下:
    MEMORY
    {
    rom : org = 0x00000000, len = 0x100000
    dram : org = 0xa0100000, len = 0x100000
    }
    SECTIONS
    {
    .text :
    {
    boot_base = .;
    obj/rom_reset.o(.text)
    *(.glue_7t)
    *(.glue_7)
    } >rom
    .data :
    {
    *(.data)
    } >rom
    .rodata :
    {
    *(.rodata)
    } >rom
    .got :
    {
    *(.got)
    } >rom
    .bss :
    {
    bss_start = .;
    *(.bss) *(COMMON)
    } >dram
    .sbss :
    {
    *(.sbss)
    bss_end = .;
    } >dram
    }
    在MEMORY标识的括号里说明的是各存储空间:包括rom(FALSH),偏移首地址为0x00000000,大小为1M;ram,偏移首地址为0xa0100000,大小也为1M.SECTIONS标识的括号里说明的是各个段的放置空间:.text放在当前的偏移首地址,只存放rom_reset.o中.text段,">rom"标识是存放在rom中;.text段之后的是.data段,也放在rom中;接着是只读数据段.rodata;本课题还留有.got段,尽管没有使用到;未初始化数据段.bss 段放在ram的偏移首地址处,">dram"标识是存放在ram中.
    最终,本文中该镜像的具体的地址映射为:
    .text: 0x00000000……0x000003eb (1004 bytes) allocated text
    .data: 0x000003ec.. …..0x00028993 (165288 bytes) allocated writeable
    .bss: 0xa0100000…….0xa0105fff (24576 bytes) allocated writeable
    .sbss: 0xa0106000…….0xa0106000 (0 bytes) writeable
    镜像2的连接脚本为:
    MEMORY
    {
    ram : org = 0xa0000000, len = 0x100000
    }
    SECTIONS
    {
    .text :
    {
    boot_base = .;
    obj/ram_reset.o(.text)
    *(.glue_7t)
    *(.glue_7)
    } >ram
    .data :
    {
    *(.data)
    } >ram
    .rodata :
    {
    *(.rodata)
    } >ram
    .got :
    {
    *(.got)
    } >ram
    .bss :
    {
    bss_start = .;
    *(.bss) *(COMMON)
    } >ram
    .sbss :
    {
    *(.sbss)
    bss_end = .;
    } >ram
    }
    其具体的含义与上一个脚本没有什么差别,唯一不同的是所有的存储空间为ram,而且其偏移初始地址也不一样.
    镜像2在该连接文本的设置下的具体映射为:
    .text: 0xa0000000……0xa00109b3 (68020 bytes) allocated text
    .data: 0xa00109b4……0xa0024a07 (82004 bytes) allocated writeable
    .rodata: 0xa0024a08……0xa00285a7 (15264 bytes) allocated
    .bss: 0xa00285b0……0xa0042453 (106148 bytes) allocated writeable
    .sbss: 0xa0042454……0xa0042454 (0 bytes) writeable
    4.3.4.3 代码的连接以及转换
    在这一节中,我们就可以回答前面所提过的问题:怎么阶段2的镜像会存放在数组umon[ ]中,使得阶段1可以将其拷贝到SDRAM中,以及本课题中是任何进行连接的.
    首先看阶段2的部分,它最终生成.bin文件,连接语句如下:
    $(AOUTRAM).bin: info $(OBJS2) $(OBJ)/libz.a makefile ram.lnk
    $(LD) -e coldstart -o $(AOUTRAM) -T ram.lnk $(OBJS2) $(LIBS)
    $(WIN_TOOLS)/elf -cm $(AOUTRAM)
    $(WIN_TOOLS)/elf -cB $(AOUTRAM).bin $(AOUTRAM)
    $(WIN_TOOLS)/bin2array -D "UMON_RAMBASE 0xa0000000" \
    -D "UMON_START 0xa0000000" -o $(OBJ)/umon.c -a umon $(AOUTRAM).bin
    很明显地,第一行指明一些必须依赖的文件,其中$(OBJS2)是阶段2中所有源程序经编译后生成的目标文件(.o)的集合.第二行中,$(LD)就是本课题所使用的GNU连接工具arm-linux-ld,-e指明该镜像文件的入口,-o指明生成的文件名,-T指明了连接脚本.正常地,前面的两行已经基本可以了,但是由于本课题的特殊性,还有很多后续工作要做.第三行,开始使用前面提过的可执行的转换工具elf,经生成的文件中的地址映射打印出来,并且使用little-endian控制构架;第四行完成的是将生成文件转换为可下载的.bin文件.最后执行的是通过可执行的转换工具bin2array,将刚刚转换的.bin文件再转换成二进制的数组,数组中则保存着镜像的二进制内容,数组名为umon,并保存在临时生成的文件umon.c中,(这就是本节一开始提出的问题的答案所在),在第一阶段只需将该数组里的内容拷贝至SDRAM中即可;-D则定义了一些本文需要使用到的宏.
    至于本文最终生成的rom.bin,其连接执行语句如下:
    $(AOUTROM).bin: $(AOUTRAM).bin $(OBJS1) rom.lnk
    $(LD) -e reset -o $(AOUTROM) -T rom.lnk $(OBJS1) $(LIBS)
    $(WIN_TOOLS)/elf -cm $(AOUTROM)
    $(WIN_TOOLS)/elf -cB $(AOUTROM).bin $(AOUTROM)
    rom.bin的生成依赖于阶段2中生成的ram.bin($(AOUTRAM).bin),$(OBJS1)(阶段1中的汇编目标文件以及一个C文件编译后的目标文件).其他的,与前面刚刚提过的一样.
    4.4 代码组织结构
    具体代码实现介绍完之后,本节简单介绍一下bootloader具体的代码组织构架.
    为了实现理论上的通用性,本文对整个代码的组织结构进行了统一安排.可以为不同的ARM硬件系统单独创建文件夹,在该目录下均包含只与本硬件系统相关的代码,例如:编写其对应的Makefile,在Makefile中针对不同的硬件系统写对应的依赖关系,编译环境(包括编译工具,参数等)不需要改变;如有必要,编写其对应的连接脚本.而对于其他一些任何系统都需要使用的代码或者工具,则也专门创建文件夹.这样当要使用某个特定的系统,只需在本课题代码的基础上进行少量移植,并将代码保存在对应的目录下,编译时只需在该特定目录下通过Makefile进行编译即可.
    具体的代码结构如下(例如存在SA1100处理器及其系统和Xscale处理器及其系统):

    图4.6
    在common目录下为一些基本公用的代码,是与硬件系统没有关系的,比如:各种FLASH的支持代码,一些网络协议的支持,一些重新定义的函数等.targets目录下为各个硬件系统,如上图,包括Xscale处理器的硬件系统以及Sa1100处理器的硬件系统,各自目录下包括各自的处理代码,这些代码是与具体的CPU以及硬件系统密切相关的,当然还包括Makefile以及连接脚本;如要增加其他的ARM硬件系统,只需在targets目录下再添加对应的目录即可.Tools目录下包含本课题所使用的各种转换工具.
    4.5 使用操作
    通过硬件复位后,系统正常启动,bootloader完成其基本的功能之后加载内核镜像,然后将控制权交给内核.如果要使用bootloader的扩展功能,当硬件复位后,敲击键盘任意键,bootloader接收到后,进入命令行状态,具体命令包括:
    xmodem –d:通过XMODEM协议下载数据并显示数据大小;
    usb –d:通过USB下载数据并显示数据大小;
    通过擦写FLASH更新镜像:
    更新bootloader自身镜像,镜像放在0x00000000开始的FLASH的第一个扇区内.命令如下:
    flash opw
    flash erase 0
    flash opw
    flash ewrite 0x00000000 0xa0300000 166272
    解释如下:
    ――flash opw
    由于bootloader所在的零扇区是写保护的,这个命令将写保护暂时打开.打开的时间到下一条命令运行完为止.
    ――flash erase 0
    擦除零扇区.
    ――flash opw
    再一次打开写保护.
    ――flash ewrite 0x00000000 0xa0300000 16672
    将sdram中从0xa0300000地址开始的数据写到flash从0x00000000开始的空间中,数据量为166272,即用串口或者USB传输时显示的数字.
    更新内核镜像,命令如下:
    flash erase 3
    flash erase 4
    flash erase 5
    flash erase 6
    flash write 0x000c0000 0xa0300000 878976
    解释如下:
    ――flash erase 3(4,5,6)
    擦除内核镜像所在的3,4,5,6扇区(内核镜像存放在0x000c0000开始的1M存储区域内,该区域正好在这些扇区之内).
    ――flash write 0x000c0000 0xa0300000 878976
    将SDRAM中从0xa0300000地址开始的数据写到FALSH从0x000c0000开始的空间中,数据量为878976,即用串口或者USB传输时显示的数字. 实验结果与测评
    上一章bootloader的设计与实现,硬件系统中CPU采用了Intel Xscale系列的PXA255,前面已经提及,Xscale核心与ARMV5TE构架相兼容,所以以PXA255作为CPU来搭建的硬件系统是一个典型ARM系统.但是PXA255还是有其自身的一些特点,而这些特点是其他一些ARM处理器所没有的.所以,要如本文题目所讲,实现一个通用的ARM bootloader,即要适合所有的ARM处理器以及硬件系统,是不太可能的事情.本文真正所做的,就是基于PXA255搭建的这个ARM硬件系统,来设计和完成bootloader.特别的,本文在设计时,已经考虑了移植其他ARM 系统,因此将本文涉及的bootloader进行移植,并不是件困难的事.
    本章内容主要是功能上的一些验证结果,并对该结果进行简单的说明,由于本课题性质上的限定,没有太多的数据.
    5.1 实验结果
    具体的实验结果包括bootloader的基本功能的实现以及扩展功能的实现,在这里以实际使用时的一些使用画面来作为功能实现的证据.
    5.1.1 基本功能的实现结果
    本文中的操作系统使用的Linux操作系统,内核编译完成后生成的是一个压缩的镜像文件,当系统控制权交给操作系统后,也就是说当开始执行内核代码时,它首先做的是自解压的过程,并且如果用串口打印消息,可以看到"uncompressing linux ……………….done, booting the kernel……",说明已经开始启动内核.对于bootloader来说,出现这样的消息,说明已经成功地加载和引导内核了,它的基本功能也就实现了,而且不存在各类镜像存放加载地址之间的冲突.对于本文的系统,当通过硬件复位重启后,可以看到此消息,验证了基本功能的实现,并且本文实现的bootloader已经在具体的项目中进行使用,没有出现差错.
    下图为内核的启动信息:

    图6.1 内核启动信息
    5.1.2 扩展功能的实现结果
    本文bootloader扩展功能主要是实现了通过串口和USB进行数据传输,以及对FLASH的擦写操作.对串口读写数据的支持,包含了使用XOMDEM以及1K-XMODEM协议,而且只支持下载功能(即PC端到硬件设备端);类似的,使用USB进行数据传输,也是只支持下载功能;而对FLASH的擦写操作,支持了对单个BLOCK擦写或者多个BLOCK的连续擦写.
    以下是一些使用时的界面.

    图6.2 使用XMODEM传输数据界面

    图6. 3 使用USB传输数据时PC上的传输界面
    5.2 程序性能
    衡量程序的性能,通常考虑稳定性,执行速度以及它的移植性.由于本文的bootloader并非是大容量的代码,因此,其执行速度是基本可以忽略不计的,另外稳定性的问题,通过平常大量的使用,功能都能正常稳定的实现.因此程序的性能问题,主要考虑扩展功能的一些相关数据以及程序的移植性问题.
    5.2.1 扩展功能的功能性测试数据
    以下是使用本课题中bootloader扩展功能传输数据时的代表性能的一些相关数据(其中,串口波特率设置为115200):
    功能
    平均传输速度
    稳定性
    使用XMODEM协议,串口下载数据
    1—2k cps
    较好
    使用1K-XMODEM协议,串口下载数据
    10k cps
    较好
    使用USB下载数据
    200k cps
    较好

    表6.1
    5.2.2 程序的可移植性
    本文中,对bootloader的设计时,已经考虑到针对其它ARM系统bootloader的移植,特别是设计的bootloader采用了双阶段,更加有利于移植工作.
    对于不同的CPU以及硬件系统进行移植,需要做的是:
    在阶段1的汇编代码rom_reset.s中,根据自身CPU的一些指令(特别是一些独有的指令)以及硬件系统的具体配置(体现在SDRAM的配置上),针对上一章实现中的描述进行修改即可.
    根据自身硬件系统存储空间的映射,进行地址规划,修改相关的地址.
    这样在关闭配置文件中所有对扩展的支持后,就可以完成该系统bootloader的基本功能.如果还要支持扩展功能,则就要看具体的硬件电路以及CPU本身支持的内置硬件控制模块了,可能要重新进行代码的编写.
    第六章 总结与展望
    本文主要介绍的是基于ARM嵌入式系统通用bootloader的设计与实现.由于bootloader是与具体的硬件系统紧密相关的,所以在具体的实现上,主要以Intel Xscale 核心的PXA255为处理器构建的硬件系统为硬件平台,以Linux为操作系统来阐明一个bootloader的设计过程.而对于ARM系统的通用bootloader,本文则从理论上来阐述对于一个ARM系统,bootloader所要实现的功能以及在实现bootloader时的一些软硬件上的规定,并且结合实现的bootloder,进一步的说明如何通过已实现的bootloader来进行简单的移植以适合其它的ARM系统.
    Bootloader是因嵌入式系统的蓬勃发展而应运而生的.一个嵌入式系统赋予bootloader的职能是引导与加载内核镜像,但是为了给更多的开发者提供便利的开发手段,bootloader越来越不局限于其基本功能,它不断地增加对硬件电路板具体功能模块的支持,甚至是支持一些简单的网络协议.现在的bootloader,更多的像一个小系统.
    实现bootloader,就基于以上原因,在扩展功能上进行了更多的支持.除了bootloader基本的加载内核镜像的功能,还增加了对串口(使用XMODEM协议)的支持,USB1.1的支持以及对FLASH擦写的支持.
    但是,一个真正的通用bootloader,应该有更多的扩展功能来适应更多的应用.由于时间的限制,实现的bootloader,扩展功能仅局限于上面所提及的.以后,应该能够有更多的支持,例如:简单的tftp协议;支持一些扩展存储卡(例如MMC卡,CF卡)的支持;以及对应的一些文件系统的支持(TFS,FAT等).
    www.3722.cn大量管理资料下载


    摘要

    目录
    目录
    第 页 共 55 页
    绪言


    第 页 共 55 页
    ARM体系结构介绍
    Bootloader的概念
    Bootloader的设计与实现
    实验结果与测评

    指令译码及控制
    地址寄存器
    64MB
    SDRAM
    32MB NOR
    FLASH
    USB OTG
    Controller
    Async
    Buffer
    Compact
    FLASH
    I2C
    D/A
    JTAG
    UART
    RS232
    Intel Xscale
    PXA255
    Proccessor
    AC97
    Touch pan
    3.5''TFT
    LCD
    I2C
    RTC
    IrDA
    SIR/FIR
    LCD
    Supply
    3.3VCPU
    Supply
    5V
    DC-DC
    Battery
    Charger
    Ext Power
    Supply In
    Battery
  • 下载地址 (推荐使用迅雷下载地址,速度快,支持断点续传)
  • 免费下载 DOC格式下载
  • 您可能感兴趣的
  • 单机游戏下载  电影下载  手机电影下载  flashplayer下载  xp系统下载  360安全卫士下载  qq下载  单机游戏下载大全  电脑主题下载  下载软件