UBOOT中start.S中源码的指令级的详尽解析
转自:http://www.crifan.com/files/doc/docbook/uboot_starts_analysis/release/htmls/index.html
在看的Uboot的start.S中文件时候,发现其最开始初始化系统,做的第一件事情,就是将CPU设置为SVC模式,但是S3C2440的CPU的核心是ARM920T,其有7种模式,为何非要设置为SVC模式,而不是设置为其他模式呢对此,经过一些求证,得出如下原因?
首先,先要了解ARM的CPU的7种模式是哪些:
http://www.docin.com/p-73665362.html
表3.1。ARM CPU中的模式
处理器模式 | 说明 | 备注 |
---|---|---|
用户(USR) | 正常程序工作模式 | 此模式下程序不能够访问一些受操作系统保护的系统资源,应用程序也不能直接进行处理器模式的切换。 |
系统(SYS) | 用于支持操作系统的特权任务等 | 与用户模式类似,但具有可以直接切换到其它模式等特权 |
快中断(FIQ) | 支持高速数据传输及通道处理 | FIQ异常响应时进入此模式 |
中断(IRQ) | 用于通用中断处理 | IRQ异常响应时进入此模式 |
管理(SVC) | 操作系统保护代码 | 系统复位和软件中断响应时进入此模式 |
中止(ABT) | 用于支持虚拟内存和/或存储器保护 | 在ARM7TDMI没有大用处 |
未定义(UND) | 支持硬件协处理器的软件仿真 | 未定义指令异常响应时进入此模式 |
另外,7种模式中,除用户USR模式外,其它模式均为特权模式。
对于为何此处是SVC的模式,而不是其他某种格式,其原因,可以从两方面来看:
-
我们先简单的来分析一下那7种模式:
- 中止生根粉和未定义UND模式
首先可以排除的是,中止ABT和未定义UND模式,那都是不太正常的模式,此处程序是正常运行的,所以不应该设置CPU为其中任何一种模式,所以可以排除。
- 快中断FIQ和中断IRQ模式
其次,对于快中断FIQ和中断IRQ来说,此处的uboot初始化的时候,也还没啥中断要处理和能够处理,而且即使是注册了终端服务程序后,能够处理中断,那么这两种模式,也是自动切换过去的,所以,此处也不应该设置为其中任何一种模式。
- 用户USR模式
虽然从理论上来说,可以设置CPU为用户USR模式,但是由于此模式无法直接访问很多的硬件资源,而UBOOT初始化,就必须要去访问这类资源,所以此处可以排除,不能设置为用户USR模式。
- 系统sys模式vs管理svc模式
首先,SYS模式和USR模式相比,所用的寄存器组,都是一样的,但是增加了一些访问一些在USR模式下不能访问的资源。
而SVC模式本身就属于特权模式,本身就可以访问那些受控资源,而且,比SYS模式还多了些自己模式下的影子寄存器,所以,相对SYS模式来说,可以访问资源的能力相同,但是拥有更多的硬件资源。
所以,从理论上来说,虽然可以设置为SYS和SVC模式的任一种,但是从UBOOT方面考虑,其要做的事情是初始化系统相关硬件资源,需要获取尽量多的权限,以方便操作硬件,初始化硬件。
从UBOOT的目的是初始化硬件的角度来说,设置为SVC模式,更有利于其工作。
因此,此处将CPU设置为SVC模式。
- 中止生根粉和未定义UND模式
-
UBOOT作为一个引导程序来说,最终目的是为了启动的Linux内核的,在做好准备工作(即初始化硬件,准备好内核和根文件系统等)跳转到内核之前,本身就要满足一些条件,其中一个条件,就是要求CPU处于SVC模式的。
所以,UBOOT在最初的初始化阶段,就将CPU设置为SVC模式,也是最合适的。
提示 关于满足哪些条件,详情请参考
或者Linux的内核文档:
kernel_source_root\documentation\arm\booting
中也是同样的解释:
CPU必须处于SVC模式
所以,UBOOT在最初的初始化阶段,就将CPU设置为SVC模式,也是最合适的。
综上所述,UBOOT在初始化阶段,就应该将CPU设置为SVC模式。
第1章开始。详解
下面将详细解释的uboot中的start.S中中的每一行代码。详细到,每个指令的语法和含义,都进行详细讲解,使得此文读者可以真正搞懂具体的含义,即什么。
以及对于一些相关的问题,深入探究为何要这么做,即为什么。
对于UBOOT的start.S中,主要做的事情就是系统的各个方面的初始化。
从大的方面分,可以分成这几个部分:
- 设置CPU模式
- 关闭看门狗
- 关闭中断
- 设置堆栈SP指针
- 清除BSS段
- 异常中断处理
下面来对start.S中进行详细分析,看看每一个部分,是如何实现的。
/ * * armboot - ARM920 CPU内核的启动代码 * *版权所有(c)2001 Marius Gr鰃er <[email protected]> *版权所有(c)2002 Alex Z黳ke <[email protected]> *版权所有(c)2002 Gary Jennejohn <[email protected]> * *参见文件信用证作为此的人员列表 *项目。 * *这个程序是免费的软件; 你可以重新分配它和/或 *根据GNU通用公共许可证的条款进行修改 *由自由软件基金会发布; 版本2的 许可证,或(在您的选择)任何更新的版本。 * *这个程序是分发的,希望它是有用的, *但没有任何保证; 甚至没有隐含的保证 *适销性或适用于特定目的。见 * GNU通用公共许可证,以获得更多细节。 * *您应该收到GNU通用公共许可证的副本 *与这个程序一起; 如果没有,请写入自由软件 * Foundation,Inc.,59 Temple Place,Suite 330,Boston, * MA 02111-1307 USA * / #include <config.h> #include <version.h> / * ************************************************** *********************** * *跳转矢量表如表3.1中[1] * ************************************************** *********************** * / .globl _start
globl是个关键字,对应含义为: http://re-eject.gbadev.org/files/GasARMRef.pdf表1.1。全球性的语法
所以,意思很简单,就是相当于Ç语言中的EXTERN,声明此变量,并且告诉链接器此变量是全局的,外部可以访问 所以,你可以看到
中,有用到此变量: ENTRY(_start) 即指定入口为_start,而由下面的_start的含义可以得知,_start就是整个start.S中的最开始,即整个的uboot的代码的开始。 |
_start :b复位
_start后面加上一个冒号 ':',表示其是一个标号标签,类似于Ç语言转到后面的标号。 而同时,_start的值,也就是这个代码的位置了,此处即为代码的最开始,相对的0的位置。 而此处最开始的相对的0位置,在程序开始运行的时候,如果是从NORFLASH启动,那么其地址是0, _stat = 0 如果是重新移居代码之后,就是我们定义的值了,即,在
中的: TEXT_BASE = 0x33D00000 表示是代码段的基地址,即 _start = TEXT_BASE = 0x33D00000 关于标号的语法解释:
而_start标号后面的: b复位 就是跳转到对应的标号为重的位置。 |
ldr pc,_undefined_instruction ldr pc,_software_interrupt ldr pc,_prefetch_abort ldr pc,_data_abort ldr pc,_not_used ldr pc,_irq ldr pc,_fiq
LDR命令的语法为:
上面那些LDR的作用,以第一个_undefined_instruction为例,就是将地址为_undefined_instruction中的一个字的值,赋值给PC。 |
_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
http://re-eject.gbadev.org/files/GasARMRef.pdf 所以上面的含义,以_undefined_instruction为例,就是,此处分配了一个字的32位= 4 =字节的地址空间,里面存放的值是undefined_instruction。 而此处_undefined_instruction也就是该地址空间的地址了用Ç语言来表达就是: _undefined_instruction =&undefined_instruction 或 * _undefined_instruction = undefined_instruction 在后面的代码,我们可以看到,undefined_instruction也是一个标号,即一个地址值,对应着就是在发生“未定义指令”的时候,系统所要去执行的代码。 (其他几个对应的“软件中断”,“预取指错误”,“数据错误”,“未定义”,“(普通)中断”,“快速中断”,也是同样的做法,跳转到对应的位置执行对应的代码。) 所以: ldr pc,标号1 ...... 标号1:.word标号2 ...... 标号2: ......(具体要执行的代码) 的意思就是,将地址为标号1中内容载入到个人计算机,而地址为标号1中的内容,正好装的是标号2。 用Ç语言表达其实很简单: PC = *(标号1)=标号2 对PC赋值,即是实现代码跳转,所以整个这段汇编代码的意思就是: 跳转到标号2的位置,执行对应的代码。 |
.balignl 16,0xdeadbeef
balignl这个标号的语法及含义:
所以意思就是,接下来的代码,都要16字节对齐,不足之处,用0xdeadbeef填充。 其中关于所要填充的内容0xdeadbeef,刚开始没看懂是啥意思,后来终于搞懂了。 经过(等)多位网友提示和纠正,觉得这样解释会更加合理些: 此处0xdeadbeef本身没有真正的意义,但是很明显,字面上的意思是,(坏)死的牛肉。 虽然其本身没有实际意义,但是其是在十六进制下,能表示出来的,为数不多的,可读的单词之一了。 另外一个相对常见的是:0xbadc0de,意思是坏代码,坏的代码,注意其中的o是0,因为十六进制中是没有o的。 这些“单词”,相对的作用是,使得读代码的人,以及在查看程序运行结果时,容易看懂,便于引起注意。 而关于自己之前,随意杜撰出来的,希望起到搞笑作用,表示好牛肉(好牛牛)的0xgoodbeef,实际上,在十六进制下,会出错的,因为十六进制下没有o和克这两个字母。 |
/ * ************************************************** *********************** * *启动代码(复位向量) * *只有当我们不从内存开始时,才能做重要的init 将armboot移动到ram *设置堆栈 *跳到第二阶段 * ************************************************** *********************** * / _TEXT_BASE : .word TEXT_BASE .globl _armboot_start _armboot_start: .word _start
/ * *这些在板特定的链接描述文件中定义。 * / .globl _bss_start _bss_start: .word __bss_start .globl _bss_end _bss_end: 剑_end
关于_bss_start和_bss_end都只是两个标号,对应着此处的地址。 而两个地址里面分别存放的值是__bss_start和_end,这两个的值,根据注释所说,是定义在开发板相关的链接脚本里面的,我们此处的开发板相关的链接脚本是:
其中可以找到__bss_start和_end的定义: __bss_start =。 .bss:{*(。bss)} _end =。 而关于_bss_start和_bss_end定义为.glogl即全局变量,是因为UBOOT的其他源码中要用到这两个变量,详情请自己去搜索源码。 |
.globl FREE_RAM_END FREE_RAM_END: .word 0x0badc0de .globl FREE_RAM_SIZE FREE_RAM_SIZE: .word 0x0badc0de
关于FREE_RAM_END和FREE_RAM_SIZE,这里只是两个标号,之所以也是声明为全局变量,是因为UBOOT的源码中会用到这两个变量。 但是这里有点特别的是,这两个变量,将在本源码start.S中中的后面要用到,而在后面用到这两个变量之前,UBOOT的Ç源码中,会先去修改这两个值,具体的逻辑是: 本文件start.S中中,后面有这两句: ldr pc,_start_armboot _start_armboot:.word start_armboot 意思很明显,就是去调用函数的start_armboot。 而的start_armboot函数是在:
中: init_fnc_t * init_sequence [] = { cpu_init,/ *基本的cpu依赖设置* / ...... 空值, }; void start_armboot(void) { init_fnc_t ** init_fnc_ptr; ...... for(init_fnc_ptr = init_sequence; * init_fnc_ptr; ++ init_fnc_ptr){ if((* init_fnc_ptr)()!= 0){ hang(); } } ...... } 即在的start_armboot去调用了cpu_init。 cpu_init函数是在:
中: int cpu_init(void) { / * *必要时建立堆栈 * / #ifdef CONFIG_USE_IRQ IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4; FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ; FREE_RAM_END = FIQ_STACK_START - CONFIG_STACKSIZE_FIQ - CONFIG_STACKSIZE; FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1; #其他 FREE_RAM_END = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4 - CONFIG_STACKSIZE; FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1; #万一 返回0; } 在cpu_init中,根据我们的一些定义,比如堆栈大小等等,去修改了IRQ_STACK_START,FIQ_STACK_START,FREE_RAM_END和FREE_RAM_SIZE的值。 至于为何这么修改,后面遇到的时候会具体再解释。 |
/ * *实际复位代码 * / 重启: / * *将cpu设置为SVC32模式 * / mrs r0,cpsr
CPSR是当前的程序状态寄存器(当前程序状态寄存器), 而SPSR是保存的程序状态寄存器(保存的程序状态寄存器)。 具体细节,可参考: |
|
MRS - 从状态寄存器移动 MRS指令的语法为:
所以,上述汇编代码含义为,将CPSR的值赋给R0寄存器。 |
msr cpsr,r0
MSR - 移至状态寄存器 MSR的指令格式是:
此行汇编代码含义为,将R0的值赋给CPSR。 |
所以,上面四行汇编代码的含义就很清楚了。
先是把CPSR的值放到R0寄存器中,然后清除位[4:0],然后再或上
0xd3 = 11 0 10111b
表1.5。CPSR = 0xD3的位域及含义
CPSR位域 | 7 | 6 | 五 | 4 | 3 | 2 | 1 | 0 |
位域含义 | 一世 | F | M4 | M3 | M2 | M1 | M0 | |
0xD3 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |
对应含义 | 关闭中断IRQ | 关闭快速中断FIQ | 设置CPU为SVC模式,这和上面代码注释中的“将cpu设置为SVC32模式”,也是一致的。 |
关于为何设置CPU为SVC模式,而不是设置为其他模式,请参阅本文档后面的章节(开始的链接)。