分享

ARM论坛 - ARM技术论

 kuainiao 2011-04-28

 ADS下C语言的入口方式和ROM镜像文件的生成

        这部分介绍下ADS下如何生成可以运行的ROM镜像文件,我们知道当程序下载到flash中运行的时候,对于RW、ZI数据就存在着两个环境,一个load环境,一个是exec环境,有时候由于速度的需要RO数据也要重新加载,那么对RO数据也是有两个环境。编译器产生ROM镜像文件时候,这三块数据的存放依次为RO、RW、ZI,并且地址空间时连续的。但是到了运行的时候,RW数据必须被拷贝到SDRAM(SRAM)中以支持读写,这就是我们所谓的运行环境。那么就要有一段代码去完成这个任务,在本章中我们介绍如何生成这段代码。

        玩过2410的朋友都知道2410初始化代码中有一段搬运RW和ZI初始化的代码,没错,它确实能够在一定程度上完成上面所说的任务,只要我们在生成二进制可执行代码的时候在编译器链接项的地方填写正确的RO&RW地址,(比如RO = 0, RW = 0x30000000), 那么将程序下到 NOR flash的零地址并从nor flash启动,启动代码会将RW&ZI数据弄到0x30000000,程序就能跑起来了。

        但是各位有没有想过,怎么把RO代码弄到SDRAM中(有时候这是必须的,比方后面我将提到用nor flash的bootloader烧写nor flash)?如果直接设RO=0x30000000,那么这段代码下载到0地址肯定跑不起来,除非是ROPI,这个要求就高了。这里我们有必要从介绍ADS中规定的C语言入口开始,ADS中从初始化汇编代码跳到main函数有两种方式,main和__main:

        1,在__main入口的模式下,汇编代码的指令为 b  __main, 编译器在跳转到main之前还要作一系列的工作,这其中就包括对运行环境的初始化,在中提到: copies nonroot(RO&RW) execution regions from load addr to exec addr, and Zeros ZI region. 借助编译器,我们就可以定义更为复杂的运行环境,这里要用到scatter文件(.scf),比如我们要的目标运行环境是:将启动代码以外的所有代码都 拷贝到SDRAM的初始地址中运行,比且把RW段设在0x30800000,那么对应的scf文件如下:
   
   FLASH 0x0  0x200000
   {
    EXEC1 0x0 0x200000
    {
     2410init.o(Init, +First)  
     __main.o(+RO)   ; copy code      
     * (Region$$Table)       ; RO/RW addresses to copy
     * (ZISection$$Table)    ; ZI addresses to zero
    }
    EXEC2 0x30000000 0x00800000
    {
     *(+RO)
    }
    SDRAM 0x30800000 0x00800000
    {
     *(+RW,+ZI)
    }
   }
  ;Sections named Region$$Table and ZISection$$Table which contain the addresses of the code/data to be copied.

        当然,在这种模式下,有些入口函数必须自己重定义,比如__user_initial_stackheap,具体参见ADS文档。

        2, main入口模式即简单的跳转,这里j即使不用“main”这个名字也无所谓。那么编译器不会作任何的初始化,所有运行环境的建立都要靠我们自己,这就是大家看到的那段搬运代码存在的理由。但是它实现一些简单的运行环境是可取的,如果用scf定义的复杂环境,虽然我相信是可以做到的,但是可能会比较麻烦。我还没深究。

        另外,这里提一下semihost,因为我们在看ADS的东西的时候经常出现这个词,我也一直受其困扰。这里我简单说一下自己的见解,semihost 仅仅是一种调试手段,它的机理就是利用MULTI_IDE等工具捕捉目标环境运行过程中产生的值为0x123456的SWI中断,然后向上位机的ADS 软件发送对应的调试信息。对于我们最后的应用代码来说,都是nonsemihost类型的。如果我们在调试中使用semihost,那么只要在最后重定义ADS中的一些使用到的库函数(比如fputc),代码就可以从semihost向nonsemihost的类型转变。不过到目前为止,我还没体会到semihost的威力。

2410启动代码分析

        这一章主要对目前广泛流行的2410启动代码进行分析:S3C2410的初始化代码主要涉及到对系统主要模块的配置、运行环境的建立、系统时钟、MMU等模块的配置,下面按执行顺序依次都各个部分进行分析:

程序入口:(ResetHandler)
        在程序一开始,首先进行的一些操作主要保证初始化程序能够顺利的运行, 因此主要包括关闭WDT、中断,配置锁相环等。

配置memory接口
        memory接口是数据访问正确的基本保障,此处主要配置SFR寄存器中0x48000000开始的memory接口寄存器组,  确保每个bank的位宽、访问类型(waitable)以及时序参数正确。如果没有特别的要求,一般来说时序参数使用默认值即可。

初始化堆栈
        ARM有6种运行模式,必须为每一种模式提供独立的堆栈空间,在堆栈设置之前是不能进行C函数的调用的。ARM的堆栈模式是从高地址递减的,我的所有代码统一将堆栈的首地址设在0x33ff8000处,往低依次为FIQ、IRQ、Abort、Undef、SVC,其中SVC和User模式不予区分。堆栈大小一般可在头文件或者当前文件中修改。

运行空间的初始化
        这段代码主要完成两个功能,一是将RW数据搬运到RW空间(我们生成ROM镜像时,RW数据是跟在RO数据之后的),二是初始化ZI数据段。当然,这段代码存在的前提是代码的运行环境只是标准的两段式:一段RO空间和一段RW空间;并且在C程序入口时没有调用编译器的链接库(__main)。后者已经提供相应的功能,并且支持更加复杂的运行环境定义(使用SCF文件)。

__rt_lib_init
        在ADS1.2的环境中,如果在C入口没有调用编译器的链接库(__main),那么在C程序一开始要调用该函数以初始化运行时的函数库,以保证对ADS提供的某些库函数能够正常调用。从这个函数开始,我们已经在C语言环境下了。

MMU初始化
        2410的MMU支持1级&2级地址映射,在我们目前大部分应用中均采用1级section模式的地址映射,一个section的大小为1M,也就是说从逻辑地址到物理地址的转变是这样的一个过程:
         一个32位的地址,高12位决定了该地址在页表中的index,这个index的内容决定了该逻辑section对应的物理section;  低20位决定了该地址在section中的偏移(index)。
        因此从0x0~0xffffffff的地址空间总共可以分成0x1000(4K)个section,页表中每项的大小为32个bit,因此页表的大小为0x4000(16K)。在我的代码中所有程序的页表统一存放在地址0x33ff8000。
        每个页表项的内容如下:
  
          bit: 31                              20 19     12 11  10 9 8          5  4  3  2  1  0
          content:  Section对应的物理地址     NULL     AP   0  Domain   1  C B  1  0
   
        最低两位(10)是section分页的标识。
        AP:Access Permission,区分只读、读写、SVC&其它模式。
       Domain:每个section都属于某个Domain,一共有16个Domain,每个Domain的属性由CP15的R3寄存器控制。 在我得所有程序中,都只包含两个Domain,一个是SFR地址以下(包括SFR)的空间,可访问;  另一个是SFR以上的空间,不可访问。
        C、B:这两位决定了该section的cache&write buffer属性,这与该段的用途(RO or RW)有密切关系。不同的用途要做不同的设置。

        C     B                         具体含义
        0    0   无cache,无写缓冲,任何对memory的读写都反映到ASB总线上。 对 memory 的操作过程中CPU需要等待。
        0    1   无cache,有写缓冲,读操作直接反映到ASB总线上。写操作CPU将数据写入到写缓冲后继续运行,由写缓冲进行ASB操作。
        1    0    有cache,写通模式(write-through (WT)),读操作首先考虑cache hit;写操作时直接将数据写入写缓冲,如果同时出现cache hit,那么也更新cache。
        1    1    有cache,写回模式(write-back (WB)),读操作首先考虑cache hit;写操作也首先考虑cache,如果hit,则只修改cache,并将cache对应半行的dirty比特置位;如果miss,则写入写缓冲,触发ASB总线操作。
               
        在我的程序中内存空间的分配统一采用了文末的MEMORY图。虽然MMU只是使用了逻辑地址到物理地址的linear transfer(值不改变),但是由于MMU能够引入cache&write buffer,因此系统性能有很大的提高!

配置时钟比、重新设置PLL
        2410内部有三个时钟:FCLK、HCLK、PCLK,分别供CPU、AHB总线和APB总线使用,为了降低功耗,一般都选择周期比为1:2:4的合理配置。 同时将PLL配置为运行环境时钟,一般都达到最高202M。
      (注:FCLK is used by ARM920T.
                HCLK is used for AHB bus, which is used by the ARM920T, the memory controller, the interrupt controller, the LCD controller, the DMA and USB host block.
                PCLK is used for APB bus, which is used by the peripherals such as WDT, IIS, I2C, PWM timer, MMC interface,
ADC, UART, GPIO, RTC and SPI)

IO初始化
        将IO口配置为对应的功能选项,同时一般会点亮相应的LED灯。
   
中断初始化
        2410的内存空间没有remap的机制,应该中断入口时钟位于零地址。因此中断服务机制可以描述如下:
        首先,不管使用那种启动方式,必须确保一下代码段位于内存的0x0地址:
    b ResetHandler  
     b HandlerUndef ;handler for Undefined mode
     b HandlerSWI ;handler for SWI interrupt
     b HandlerPabort ;handler for PAbort
     b HandlerDabort ;handler for DAbort
     b .   ;reserved
     b HandlerIRQ ;handler for IRQ interrupt
     b HandlerFIQ ;handler for FIQ interrupt
        除ResetHandler外,其余各项都是由如下的宏定义的一段代码:
    HandlerFIQ   HANDLER  HandleFIQ
     MACRO
     $HandlerLabel  HANDLER  $HandleLabel
     $HandlerLabel
      sub sp,sp,#4            ; decrement sp(to store jump address)
      stmfd sp!,{r0}          ; PUSH the work register to stack
      ldr     r0,=$HandleLabel  ; load the address of HandleXXX to r0
      ldr     r0,[r0]            ; load the contents
      str     r0,[sp,#4]       ; store the contents(ISR) of HandleXXX to stack
      ldmfd   sp!,{r0,pc}     ; POP the work register and pc(jump to ISR)
      MEND
        这段代码的含义是通过堆栈将中断向量表中的内容赋给PC指针(如HandleFIQ是存放着FIQ服务程序入口地址的地址),自然程序就跳到相应的入口地址。
        可见,中断向量表存放的是各个中断服务程序的入口地址,它是用来被加载的,而并不是可执行代码。为了统一,所有示例程序都将中断向量表放在0x33ffff00开始的地址,并根据入口地址依次排列。
        需要注意的是如果各种模式的服务程序用C语言定义,那么类型必须用__irq定义,以保证能够正确返回。
   
初始化串口
        串口统一选用UART0,模式采用115200、1bit STOP、No Parity。
  
最后跳转到我们自己的应用程序!

附:我得程序所使用的地址空间结构以及MMU中C、B的设置:

    Blank Area: RW_FAULT     0x5b000000 ~ 0xffffffff
      
      Sram & SFR: NCNB           0x40000000 ~ 0x4affffff
      
      Blank Area: RW_FAULT    0x34000000 ~ 0x3fffffff

      Int_Vec, Stack, MTT: CNB  0x33f00000 ~ 0x33ffffff
      
      SDRAM Download: NCNB   0x31000000 ~ 0x33efffff
      
      SDRAM Exec RW: CB         0x30800000 ~ 0x30ffffff
      
      SDRAM Exec R CNB        0x30000000 ~ 0x307fffff
      
      Bank5, FPGA: NCNB          0x28000000 ~ 0x2fffffff
      
      Bank4, FPGA: NCNB            0x20000000 ~ 0x27ffffff
      
      Bank3, Bottom NIC: NCNB   0x18000000 ~ 0x1fffffff
      
      Bank2, Bottom Flash: CNB  0x10000000 ~ 0x17ffffff
      
      Bank1, Bottom Sram: CNB   0x08000000  ~ 0x0fffffff
      
      Bank0, Flash or Sram: CNB  0x00000000 ~ 0x07ffffff


Nor Flash Bootloader
   
        这是我着手写的第一个程序,我的想*是让这个程序同时支持通过串口对Nand 和 Nor FLASH的烧写,如果不进行任何烧写,那么就跳到Nor Flash的第二个section启动应用程序,这样一来,即使脱离JTGA,我也可以使用串口进行盲调。

        由于有现成的初始化文件和flash烧写的示例程序,开发起来还比较快。当然也遇到了一些问题,一开始连flash的device ID都读不出来,后来发现我指针没有定义成volatile类型,flash的操作时序被编译器优化了;再者,在对Nor Flash进行操作时,bank0在MMU中的类型一定要设为NCNB,这样比较保险。

        遇到最大的问题就是下面的了,一开始我用jtag把程序下载到0x30000000的地方运行,对Nor Flash的烧写完全正常,但是当把程序下载到Nor Flash中启动运行后,再对Nor Flash的section 2进行烧写时,就出现了问题。所幸没多久我就意识到了问题,将程序放在Nor Flash中运行,同时有对同一片flash进行操作,那么操作时序势必会被CPU的指令读取时序所破坏,因此程序必须搬运到SDRAM中运行。

        但是启动地址有必须是零地址,所以我采用了前文提到的scatter文件的方法,将非必要的代码全部搬到sdram中运行,scf文件格式就是前文中的那个。当然采用了__main的入口,调用了ADS的链接库,让它帮忙建立程序的运行环境。

        至此,Nor Flash Bootloader可以顺畅无忧的实现其功能了。

Nand Flash Bootloader
   
        因为Nor flash bootloader已经实现了对Nand Flash的烧写,因此在Nand Flash Bootloader中实现flash烧写并不是我的目的,况且,S3C2410运行在NAND BOOT模式下的时候,4K的SRAM位于0地址,上电时刻Nand Flash中block 0的前8个page的数据自动加载到SRAM后开始运行,Nor flash这个时候是不可见的。 因此,我做Nand Flash Bootloader的目的简单而又直接,就是把block1开始的若干个block数据加载到sdram首地址,然后PC跳到那里运行应用程序就可以了。比方说我把编译好的ucos-ii代码放在block1,那么ucos-ii就可以跑起来了。

        因此制作Nand Flash一个最重要的问题就是真个程序必须小于4K。应用程序应该是一个完整的应用代码,只是在编译时RO的起始地址应该定位成0x30000000,如果直接用JTAG将其下载到对应地址,程序照样能够跑起来(当然零地址要有中断向量入口程序)。这里我偷懒了一下,将应用程序的中断向量表地址和Nand Flash Bootloader设得完全相同,那样应用程序就可以借用bootloader的中断跳转程序以实现中断的正确跳转,当然应用程序也有自己相应的跳转代码,但是这段代码位于SDRAM起始地址,是不会被执行的;至于堆栈,应用程序在自己得初始化代码中可以重新设置堆栈。

        在Nand Flash的硬件方面,我开发板使用的是K9F5608(32M),相对于K9S208(64M),后者的地址需要写四次才能全部送出,而前者只要三次就够了,2410的引脚中专门有nCON控制地址送出的次数。因此当硬件在这两者之间变化时,既要注意外部电路图的接法,又要注意软件代码的正确性。

RTL8019调试心得

        一开始接触8019真的是让我头晕,首先我没有一点网络基础,另外,8019的datasheet称不上最烂也算是极品了。当初作PCB的时候选用8019主要是因为价格便宜以及lbbbb做过,能够提供源代码&技术支持。最后能搞定,我觉得还是很有成就感的。

        8109AS的运行模式包括跳线模式、非跳线模式和PnP模式,PnP模式是在电脑上使用的即插即用模式,因此这里我们可以不予考虑。8109AS的IO寄存器符合NE2000标准,分为4个page,其中page3是8109AS自己定义的寄存器。所谓跳线模式,是指8019AS I/O寄存器page3中的大部分配置寄存器(CONFIGn)的值是在上电复位时刻确定的,来源是在RESET上升沿时捕捉到的一些外部引脚的电平值。在非跳线模式下,这些寄存器值的配置由外部EEPROM 93C46完成。配置寄存器在运行过程中大部分值时不能改变的。

        目前驱动程序目前只实现了最基本的收发功能。片内16K的SRAM划分如下:40~46:发送缓冲区1;46~4c:发送缓冲区2;4c~80:接收缓冲区。

        另外我在调试中发现片内的SRAM是不可按地址读的,虽然我在原理图上也象CS8900A那样连了mem_wr&mem_rd,但是似乎不能访问,希望哪位高人能够给我一个明确的回答。

        起初作硬件了时候我加了93C46,想使用非跳线模式,JP脚就悬空在那里。后来93C46买不到,就一直空着,虽然8019的初始化没有出问题,但是对这种不洋不土的模式,我还是心有余悸,因此将JP脚接到了5V电源,板上唯一的飞线就是这么来
        现在还有一个郁闷的问题就是linux 2.4.18是不支持8019的。天下的2410开发板都采用8900a也就是这个道理。所以,我还要完成驱动!!!

        作者:lionwq

2009-04-20 04:07:23
资料 邮件   举报
地平线
【1楼】


宣传分:36
发帖数:176
积分: 752


楼主真是倾囊相授呀!赞一个!

2009-04-22 08:07:44
资料 邮件   举报
sjhdu
【2楼】

宣传分:0
发帖数:18
积分: 56


谢啦!

2009-04-25 06:03:02
资料 邮件   举报
似水年华_arm
【3楼】

宣传分:0
发帖数:12
积分: 24


好,谢谢了。

2009-07-16 17:30:16
资料 邮件   举报
sandy242
【4楼】

宣传分:0
发帖数:1
积分: 2


感谢楼主这么热心这么用心的编辑帖子

2009-08-10 19:15:24
资料 邮件   举报
js001sdx
【5楼】

宣传分:0
发帖数:5
积分: 12


UP!UP!UP!UP!谢谢!

2009-08-10 21:52:00
资料 邮件   举报
laiguo
【6楼】

宣传分:0
发帖数:4
积分: 12


佩服,楼主分析的很透彻!

2009-08-11 21:49:25
资料 邮件   举报
alang2009
【7楼】

宣传分:0
发帖数:8
积分: 20


楼主厉害,以前都是只会用 没有分析个所以然来,今天受教了

2009-08-21 18:19:59
资料 邮件   举报
boylove1
【8楼】

宣传分:0
发帖数:14
积分: 39


谢谢分享

2009-08-25 07:49:45
资料 邮件   举报
孤行者
【9楼】

宣传分:0
发帖数:22
积分: 64


请问楼主:上文提到“我们知道当程序下载到flash中运行的时候,对于RW、ZI数据就存在着两个环境”其中的ZI是什么意思??

2009-10-05 04:24:52
资料 邮件   举报
summer
【10楼】

宣传分:37
发帖数:106
积分: 453


ZI:ZeroInit就是RW段中要被初始化为零的变量被称为ZI段.

可以简单的这样理解:
RO是程序中的指令和常量等
RW是程序中的已初始化变量等
ZI是程序中的未初始化的变量和堆栈等

       烧录到ROM中的image文件与实际运行时的ARM程序之间并不是完全一样的。因此就有必要了解ARM程序是如何从ROM中的image到达实际运行状态的。
实际上,RO中的指令至少应该有这样的功能:
1. 将RW从ROM中搬到RAM中,因为RW是变量,变量不能存在ROM中。
2. 将ZI所在的RAM区域全部清零,因为ZI区域并不在Image中,所以需要程序根据编译器给出的ZI地址及大小来将相应得RAM区域清零。ZI中也是变量,同理:变量不能存在ROM中
在程序运行的最初阶段,RO中的指令完成了这两项工作后C程序才能正常访问变量。否则只能运行不含变量的代码。
说了上面的可能还是有些迷糊,RO,RW和ZI到底是什么,下面我将给出几个例子,最直观的来说明RO,RW,ZI在C中是什么意思。
1; RO
看下面两段程序,他们之间差了一条语句,这条语句就是声明一个字符常量。因此按照我们之前说的,他们之间应该只会在RO数据中相差一个字节(字符常量为1字节)。
Prog1:
#include
void main(void)
{
;
}
Prog2:
#include
const char a = 5;
void main(void)
{
;
}
Prog1编译出来后的信息如下:
================================================================================
Code RO Data RW Data ZI Data Debug
948 60 0 96 0 Grand Totals
================================================================================
Total RO Size(Code + RO Data) 1008 ( 0.98kB)
Total RW Size(RW Data + ZI Data) 96 ( 0.09kB)
Total ROM Size(Code + RO Data + RW Data) 1008 ( 0.98kB)
================================================================================
Prog2编译出来后的信息如下:
================================================================================
Code RO Data RW Data ZI Data Debug
948 61 0 96 0 Grand Totals
================================================================================
Total RO Size(Code + RO Data) 1009 ( 0.99kB)
Total RW Size(RW Data + ZI Data) 96 ( 0.09kB)
Total ROM Size(Code + RO Data + RW Data) 1009 ( 0.99kB)
================================================================================
以上两个程序编译出来后的信息可以看出:
Prog1和Prog2的RO包含了Code和RO Data两类数据。他们的唯一区别就是Prog2的RO Data比Prog1多了1个字节。这正和之前的推测一致。
如果增加的是一条指令而不是一个常量,则结果应该是Code数据大小有差别。
2; RW
同样再看两个程序,他们之间只相差一个“已初始化的变量”,按照之前所讲的,已初始化的变量应该是算在RW中的,所以两个程序之间应该是RW大小有区别。
Prog3:
#include
void main(void)
{
;
}
Prog4:
#include
char a = 5;
void main(void)
{
;
}
Prog3编译出来后的信息如下:
================================================================================
Code RO Data RW Data ZI Data Debug
948 60 0 96 0 Grand Totals
================================================================================
Total RO Size(Code + RO Data) 1008 ( 0.98kB)
Total RW Size(RW Data + ZI Data) 96 ( 0.09kB)
Total ROM Size(Code + RO Data + RW Data) 1008 ( 0.98kB)
================================================================================
Prog4编译出来后的信息如下:
================================================================================
Code RO Data RW Data ZI Data Debug
948 60 1 96 0 Grand Totals
================================================================================
Total RO Size(Code + RO Data) 1008 ( 0.98kB)
Total RW Size(RW Data + ZI Data) 97 ( 0.09kB)
Total ROM Size(Code + RO Data + RW Data) 1009 ( 0.99kB)
================================================================================
可以看出Prog3和Prog4之间确实只有RW Data之间相差了1个字节,这个字节正是被初始化过的一个字符型变量“a”所引起的。
3; ZI
再看两个程序,他们之间的差别是一个未初始化的变量“a”,从之前的了解中,应该可以推测,这两个程序之间应该只有ZI大小有差别。
Prog3:
#include
void main(void)
{
;
}
Prog4:
#include
char a;
void main(void)
{
;
}
Prog3编译出来后的信息如下:
================================================================================
Code RO Data RW Data ZI Data Debug
948 60 0 96 0 Grand Totals
================================================================================
Total RO Size(Code + RO Data) 1008 ( 0.98kB)
Total RW Size(RW Data + ZI Data) 96 ( 0.09kB)
Total ROM Size(Code + RO Data + RW Data) 1008 ( 0.98kB)
================================================================================
Prog4编译出来后的信息如下:
================================================================================
Code RO Data RW Data ZI Data Debug
948 60 0 97 0 Grand Totals
================================================================================
Total RO Size(Code + RO Data) 1008 ( 0.98kB)
Total RW Size(RW Data + ZI Data) 97 ( 0.09kB)
Total ROM Size(Code + RO Data + RW Data) 1008 ( 0.98kB)
================================================================================
编译的结果完全符合推测,只有ZI数据相差了1个字节。这个字节正是未初始化的一个字符型变量“a”所引起的。
注意:如果一个变量被初始化为0,则该变量的处理方法与未初始化华变量一样放在ZI区域。
即:ARM C程序中,所有的未初始化变量都会被自动初始化为0。
总结:
1; C中的指令以及常量被编译后是RO类型数据。
2; C中的未被初始化或初始化为0的变量编译后是ZI类型数据。
3; C中的已被初始化成非0值的变量编译后市RW类型数据。
附:
程序的编译命令(假定C程序名为tst.c):
armcc -c -o tst.o tst.c
armlink -noremove -elf -nodebug -info totals -info sizes -map -list aa.map -o tst.elf tst.o
编译后的信息就在aa.map文件中。
ROM主要指:NAND Flash,Nor Flash
RAM主要指:PSRAM,SDRAM,SRAM,DDRAM

其它讲解:

       一般而言,一个程序包括只读的代码段和可读写的数据段。在ARM的集成开发环境中,只读的代码段和常量被称作RO段(ReadOnly);可读写的全局变量和静态变量被称作RW段(ReadWrite);RW段中要被初始化为零的变量被称为ZI段(ZeroInit)。对于嵌入式系统而言,程序映象都是存储在Flash存储器等一些非易失性器件中的,而在运行时,程序中的RW段必须重新装载到可读写的RAM中。这就涉及到程序的加载时域和运行时域。简单来说,程序的加载时域就是指程序烧入Flash中的状态,运行时域是指程序执行时的状态。对于比较简单的情况,可以在ADS集成开发环境的ARM LINKER选项中指定RO BASE和RW BASE,告知连接器RO和RW的连接基地址。对于复杂情况,如RO段被分成几部分并映射到存储空间的多个地方时,需要创建一个称为“分布装载描述文件”的文本文件,通知连接器把程序的某一部分连接在存储器的某个地址空间。需要指出的是,分布装载描述文件中的定义要按照系统重定向后的存储器分布情况进行。在引导程序完成初始化的任务后,应该把主程序转移到RAM中去运行,以加快系统的运行速度。

       什么是arm的映像文件,arm映像文件其实就是可执行文件,包括bin或hex两种格式,可以直接烧到rom里执行。在axd调试过程中,我们调试的是axf文件,其实这也是一种映像文件,它只是在bin文件中加了一个文件头和一些调试信息。映像文件一般由域组成,域最多由三个输出段组成(RO,RW,ZI)组成,输出段又由输入段组成。一般的Image文件包含了RO和RW数据,而没有ZI数据。之所以Image文件不包含ZI数据,是因为ZI数据都是0,没必要包含,只要程序运行之前将ZI数据所在的区域一律清零即可。包含进去反而浪费存储空间。
       所谓域,指的就是整个bin映像文件所处在的区域,它又分为加载域和运行域。加载域就是映像文件被静态存放的工作区域,一般来说flash里的 整个bin文件所在的地址空间就是加载域,当然在程序一般都不会放在 flash里执行,一般都会搬到sdram里运行工作,它们在被搬到sdram里工作所处的地址空间就是运行域。我们输入的代码,一般有代码部分和数据部分,这就是所谓的输入段,经过编译后就变成了bin文件中ro段和rw段,还有所谓的zi段,这就是输出段。对于加载域中的输出段,一般来说ro段后面紧跟着rw段,rw段后面紧跟着zi段。在运行域中这些输出段并不连续,但rw和zi一定是连着的。zi段和rw段中的数据其实可以是rw属性。

       | Image$$RO$$Base| |Image$$RO$$Limit| |Image$$RW$$Base| |Image$$ZI$$Base| |Image$$ZI$$Limit|这几个变量是编译器通知的,我们在 makefile文件中可以看到它们的值。它们指示了在运行域中各个输出段所处的地址空间,| Image$$RO$$Base| 就是ro段在运行域中的起始地址,|Image$$RO$$Limit| 是ro段在运行域中的截止地址,其它依次类推。我们可以在linker的output中指定,在 simple模式中,ro base对应的就是| Image$$RO$$Base|,rw base 对应的是|Image$$RW$$Base|,由于rw和zi相连,|Image$$ZI$$Base| 就等于|Image$$RW$$limit|。其它的值都是编译器自动计算出来的。

   下面是2410启动代码的搬运部分,我给出注释:

BaseOfROM DCD |Image$$RO$$Base|

TopOfROM DCD |Image$$RO$$Limit|

BaseOfBSS DCD |Image$$RW$$Base|

BaseOfZero DCD |Image$$ZI$$Base|

EndOfBSS DCD |Image$$ZI$$Limit|

adr r0, ResetEntry; ResetEntry是复位运行时域的起始地址,在boot nand中一般是0

ldr r2, BaseOfROM;

cmp r0, r2

ldreq r0, TopOfROM;TopOfROM=0x30001de0,代码段地址的结束

beq InitRam

ldr r3, TopOfROM

;part 1,通过比较,将ro搬到sdram里,搬到的目的地址从 | Image$$RO$$Base| 开始,到|Image$$RO$$Limit|结束

0

ldmia r0!, {r4-r7} ;将r0值作为地址处(ResetEntry)连续的4个32位数依次转入r4,r5,r6,r7;同时r0增加。

stmia r2!, {r4-r7};将r4,r5,r6,r7的值依次存入|Image$$RO$$Base|地址处;同时r2增加。

cmp r2, r3

bcc %B0;

;part 2,搬rw段到sdram,目的地址从|Image$$RW$$Base| 开始,到|Image$$ZI$$Base|结束

sub r2, r2, r3;r2=0 ;上面拷贝时每次拷贝4个双字(32位)大小,但是RO段大小不一定是4的整数倍,所以可能多拷贝了几个双字大小,r2-r3得到多拷贝的个数

sub r0, r0, r2 ;r0-(r2-r3)可以使r0指向在boot nand中RO的结束地址

InitRam ;carry rw to baseofBSS

ldr r2, BaseOfBSS ;TopOfROM=0x30001de0,baseofrw

ldr r3, BaseOfZero ;BaseOfZero=0x30001de0

0

cmp r2, r3

ldrcc r1, [r0], #4

strcc r1, [r2], #4

bcc %B0

;part 3,将sdram zi初始化为0,地址从|Image$$ZI$$Base|到|Image$$ZI$$Limit|

mov r0, #0;init 0

ldr r3, EndOfBSS;EndOfBSS=30001e40

1

cmp r2, r3

strcc r0, [r2], #4

bcc %B1

至此三个输出段组成(RO,RW,ZI)拷贝和初始化结束。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多