Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2149379
  • 博文数量: 361
  • 博客积分: 10828
  • 博客等级: 上将
  • 技术积分: 4161
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-20 14:34
文章分类

全部博文(361)

文章存档

2011年(132)

2010年(229)

分类: LINUX

2011-02-28 20:50:18

在了解了ARM相关的汇编指令后,同时结合网上各位大虾的提点开始阅读u-boot的启动代码,现将分析过程记录如下



可执行文件及内存映射
我们可以把可执行文件分为2种情况:存放态和运行态
1.存放态:可执行文件经过烧到存储介质上(flash或磁盘)的分布,此时可执行文件通常有2部分组成,代码段和数据段,代码段又分为可执行代码段 (.text)和只读数据段(.rodata),数据段可以分为初始化数据段(.data)和未初始化代码段(.bss),如下:

+-------------+-----------
| .bss        |   (ZI)
+-------------+-- 数据段
| .data       |   (RW)
+-------------+-----------
| .rodata     |
|_____________| 代码段(RO)
| .text       |
+-------------+-----------

2.运行态:可执行文件经过装载后就变成为运行态,
当可执行文件装载后, 在RAM中的分布如下:
| ...         |
+-------------+-- ZI段结束地址
| ZI 段       |
+-------------+-- ZI段起始地址
| 保留区2     |
+-------------+-- RW段结束地址
| RW 段       |
+-------------+-- RW段起始地址
| 保留区1     |
+-------------+-- RO段结束地址
| RO 段       |
+-------------+-- RO段起始地址

所以装载过程必须完成把可执行文件的各个段搬移到RAM的指定位置,这个装载过程则是由启动程序来完成的。而可执行代码在RAM中的地址则是由链接脚本来指定的。



一个可执行的image必须有一个入口点,并且只能有一个全局入口点,所以要通知编译器这个入口在哪里。这个是有链接脚本来实现的,由此我们可以找到程序 的入口点是在 /board/lpc2210/u-boot.lds中指定的,其中ENTRY(_start)说明程序从_start开始运行,而他指向的是cpu /arm7tdmi/start.o文件。因为我们用的是ARM7TDMI的cpu架构,在复位后从地址0x00000000取它的第一条指令,所以我们 将Flash映射到这个地址上,这样在系统加电后,cpu将首先执行u-boot程序。


ARM在CPU加电复位后是从0x0000地址开始取指,因此在零地址需要放置第一条启动代码。默认情况下,程序的链接器是把0x8000作为映像的入口 点(取指的第一条指令的位置),因此 需要对映像链接定位,即重定位映像段的存放,包括代码段、数据段、零区等,对整个系统的代码做正确的定位,这些规则通常写成链接脚本。链接脚本就是提供了 一种把代码段和数据段放在不同存储器定位。

我们的只读代码和数据是固化在ROM中(通常在0x0000),但是在执行的时候想在RAM区运行(优化系统,使性能发挥最大),就需要链接定位。链接器 告诉了随机存储器从哪里开始。


Load View:代码编译链接的一个组织情况
Execute View:代码正确执行的空间组织


启动过程的C部分
1. 初始化MMU
2.初始化外部端口
3. 中断处理程序表初始化
4. 串口初始化
5. 其它部分初始化(可选)
6. 主程序循环

于是我们可以在链接脚本中找到映像的加载地址,也即程序的入口点。/board/s3c2410/U-boot.lds

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
    . = 0x00000000; /*映像的入口点,通常链接器将此地址定位到ROM的0x0地址,必须使编译器知道这
                                    个地址*/
    . = ALIGN(4);
    .text      :
    {
    cpu/arm920t/start.o    (.text)
    *(.text)
    }

    . = ALIGN(4);
    .rodata : { *(.rodata) }

    . = ALIGN(4);
    .data : { *(.data) }

    . = ALIGN(4);
    .got : { *(.got) }

    . = .;
    __u_boot_cmd_start = .;
    .u_boot_cmd : { *(.u_boot_cmd) }
    __u_boot_cmd_end = .;

    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) }
    _end = .;
}
从上面可以看出,链接脚本指定了代码段从0x00000000开始,而代码段最开始链接的就是cpu/arm920t/start.o。于是可以知道在 CPU加电复位后程序首先是从cpu/arm920t/start.S开始的。

1.Stage1:cpu/arm920t/start.S
当系统启动时, ARM CPU会跳到0x00000000去执行。一般BootLoader都包括如下几个部分:
1. 建立中断向量异常表
2. 显示的切换到SVC且32指令模式
3. 关闭S3C2410的内部看门狗
4. 禁止所有的中断
5. 配置系统时钟频率和总线频率
6. 设置内存区的控制寄存器
7. 初始化中断
8. 安装中断向表量
9. 把可执行文件的各个段搬到运行态的各个位置
10. 跳到C代码部分执行

具体分析如下:


/*复位时0地址是ROM区,从0x0到0x20分配了ARM的中断向量表*/
.globl _start
_start:    b       reset /*0x0,正常情况下,系统reset后进入的入口,驻留于0x0地址,机器码为EA0000XX*/
    ldr    pc, _undefined_instruction /*0x4,未定义指令,系统出错处理的入口*/
    ldr    pc, _software_interrupt /*0x8,软中断,monitor程序的入口*/
    ldr    pc, _prefetch_abort   /*0x0c,预取失败错误*/
    ldr    pc, _data_abort       /*0x10,取数据失败错误(通常是保护现场,然后do nothing)*/
    ldr    pc, _not_used        /*0x14保留*/
    ldr    pc, _irq             /*0x18,快速中断请求 */
    ldr    pc, _fiq             /*0x1c,处理原理与irq相同,所有的硬件中断源共用一个通道来进行IRQ或FIQ */

_undefined_instruction:    .word undefined_instruction
_software_interrupt:    .word software_interrupt
_prefetch_abort:    .word prefetch_abort
_data_abort:        .word data_abort
_not_used:        .word not_used
_irq:            .word irq
_fiq:            .word fiq

    .balignl 16,0xdeadbeef
/*.将地址对其到16的倍数,如果PC跳过4字节才是16的倍数,则用0xdeadbeef填充,如果只跳过了1,2,3个字节则填充不确定,如果PC 是16的倍数,则什么也不做*/

***************************************************************
* 当一个异常出现以后,ARM会自动执行以下几个步骤:
* (1) 把下一条指令的地址放到连接寄存器LR(通常是R14),这样就能够在处理异常返回时从正确的位置继续执行。
* (2) 将相应的CPSR(当前程序状态寄存器)复制到SPSR(备份的程序状态寄存器)中。从异常退出的时候,就可以由SPSR来恢复CPSR。
* (3) 根据异常类型,强制设置CPSR的运行模式位。
* (4) PC(程序计数器)被强制成相关异常向量处理函数地址,从而跳转到相应的异常处理程序中。
*
* 当异常处理完毕后,ARM会执行以下几步操作从异常返回:
* (1) 将连接寄存器LR的值减去相应的偏移量后送到PC中
* (2) 将SPSR复制回CPSR中
* (3) 若在进入异常处理时设置了中断禁止位,要在此清除
上述代码即碰到异常时,PC会被强制设置为对应的异常向量,从而跳转到
相应的处理程序,然后再返回到主程序继续执行。
******************************************************************

/*
*************************************************************************
*
* Startup Code (reset vector)
*
* do important init only if we don't start from memory!
* relocate armboot to ram
* setup stack
* jump to second stage
*
*************************************************************************
*/
/*保存变量的数据区*/
_TEXT_BASE:
    .word    TEXT_BASE

.globl _armboot_start
_armboot_start:
    .word _start

/*
* These are defined in the board-specific linker script.
*/
.globl _bss_start
_bss_start:
    .word __bss_start

.globl _bss_end
_bss_end:
    .word _end

#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
    .word    0x0badc0de

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
    .word 0x0badc0de
#endif
/*****************************************************/
上述代码主要是用于保存一些全局变量,用于启动程序将代码从flash
拷贝到RAM或其他使用。有一些变量的值是通过链接脚本得到的,如
TEXT_BASE位于/u-boot-1.1.6/board/xxx(开发板目录名称)/config.mk
* 文件里。__bss_start、_end位于/u-boot-1.1.6/board/xxx(开发板目录名称)
/u-boot.lds文件里,具体值是由编译器算出来的。

/********************************************************/

/*
* the actual reset code
*/

reset:
    /*
    * set the cpu to SVC32 mode ,在进入时将CPSR设置为监控模式,退出后改为用户模式
      * 运行模式位为:10011(svc mode)
    */
    mrs    r0,cpsr
    bic    r0,r0,#0x1f //r0=r0 AND (!0x1f),屏蔽所有中断,为中断提供服务通常是OS的设备驱动的责任,在bootloader执行中不需要中断
    orr    r0,r0,#0xd3 //逻辑或
    msr    cpsr,r0     //svc mode
阅读(1156) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~