在上一节的u-boot.lds文件中有这样一句是:
cpu/arm_cortexa8/start.o (.text)这句话就是调用初始化代码stat.s的元老级功臣,这可和神话中的女娲、盘古之类的有得一拼的,只是那时代没有计算机,要不还真得一较高低才行。说远了,回归主题,话说从这里调用并执行start.s文件后,该文件又是如何执行的呢?
start.s是就是所谓启动的第一阶段,其主要功能如下:
(1)定义入口。由于一个可执行的image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在rom(Flash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。
(2)设置异常向量(exception vector)。
(3)设置CPU的速度、时钟频率及中断控制寄存器。
(4)初始化内存控制器 。
(5)将rom中的程序复制到ram中。
(6)初始化堆栈 。
(7)转到ram中执行,该工作可使用指令ldr pc来完成。
怎么不是相信春哥得永生呢?其实我也这么想的,关键是程序不认识春哥呀,这个傻不啦叽的家伙只认识表哥呀,当然这里可不是你的表哥哟,所谓的表即“异常向量表”,明白了吧,关键时候还得看表哥的。
系统上电后,pc指针从0x00000000地址开始执行,这个地址是处理器可以直接访问的,所以这个时候不要指望能运行到你的外存上,实际上现在还在内部flash中,就是所谓的nor flash。所以,我们就要在0x00000000位置放置有意义的东西,不然系统怎么启动呢?废话少说,直接看下面的代码再作解释:
.globl _start _start: b start_code 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
该部分为处理器的异常处理向量表。地址范围为0x0000 0000 ~ 0x0000 0020,刚好8条指令,在ARM的异常向量表(如下表所示):
中断向量地址 | 异常中断类型 | 异常中断模式 | 优先级(6最低) |
0x00 | 复位 | 特权模式 | 1 |
0x04 | 未定义的指令 | UND终止模式 | 6 |
0x08 | 软件中断 | 特权模式 | 6 |
0x0C | 指令预取终止 | 终止模式 | 5 |
0x10 | 数据访问终止 | 终止模式 | 2 |
0x14 | 保留 | 未使用 | 未使用 |
0x18 | 外部中断请求 | IRQ模式 | 4 |
0x1C | 快速中断请求 | FIQ模式 | 3 |
FIQ异常向量放在所有向量最后,目的是可以将FIQ异常处理程序直接放在向量的地址上. 这样在执行FIQ处理时,就可以不用进行跳转,快速响应中断.FIQ向量放在最后,允许FIQ异常处理程序直接放在地址0x0000001C或0xFFFF001C开始 的位置,而不需要由向量的分支指令。也就是说直接从0x0000 0001C开始执行,这样省去了一个跳转指令,如果FIQ不是在顶端,那么当然需要一次跳转.
这里千万别理解为_start就是存放在0x00开始的地方,而是发生中断跳转的位置。而翻译后_start的位置由是编译链接工具决定,可由编译后u-boot目录下生成的System.map文件可知,_start的位置竟然是0x33f80000,那这个地址是哪儿来的呢?相信不是天上掉下来的林妹妹,我也想天上掉个林妹妹给我呢,就我这种码农,掉下来林妹妹也不见得是我的,更何况是根本不会实现的,想也白想了。因此,这个地址得有一个地方指定才对吧!OK,见证奇迹的时候到了,我们可以通过根目录下的Makefile查看是u-boot是如何链接的,有如下一段代码为证:
GEN_UBOOT = \ UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \ sed -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\ cd $(LNDIR) && $(LD) $(LDFLAGS) $(LDFLAGS_$(@F)) $$UNDEF_SYM $(__OBJS) \ --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \ -Map u-boot.map -o u-boot $(obj)u-boot: depend \ $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds $(GEN_UBOOT)
可以看到LDFLAGS标志,而它又被定义了,所以得找到罪魁祸首才行,在根目录下的config.mk文件中,可以看到如下代码:
LDFLAGS += -Bstatic -T $(obj)u-boot.lds $(PLATFORM_LDFLAGS) ifneq ($(TEXT_BASE),) LDFLAGS += -Ttext $(TEXT_BASE) endif
LDFLAGS里面除了指定链接脚本,如果TEXT_BASE不等于空,还加上了-Ttext$(TEXT_BASE),TEXT_BASE的值是多少呢?我们可以在config.mk里面看到它的定义(系统中有多个文件,不同处理器对应的文件不同,如移植s3c2440的可以参见目录board/samsung/smdk2410/,当然也要防止U-Boot的结构调整,指不定这个目录又放到哪儿去了),亲,看到了吗?就只有这一行哟,它的值为0x33f80000。
TEXT_BASE = 0x33F80000
关于异常向量表就啰嗦说这么多了,不过还是没觉得轻松多少呢?因为真正的解释部分还没开始,接下来正文开始了,首先说明一下第一行,学习过汇编的童鞋都应该知道globl是声明全局变量的意思,所以这里把_start声明是全局的,至于全局变量和局部变量的区别,相信各位童鞋的能力,就此略去三千字吧。
程序段正文的第一行可是一个亮点哟,亲们,这里不能打瞌睡哟!
_start: b start_code
只用说明第一条是跳转执行的,那接下来的几句也不用多作说明了吧,分别在对应异常的发生时进行相应的跳转,跳转的位置可以通过接下来的几行代码来看:
_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例如,当发生fiq中断时,将进行跳转到对应的中断,而对应的fiq标签可以在文件后面找到相应的实现部分(其实这里还有一个分支,仅用一个作示例):
fiq: get_bad_stack bad_save_user_regs bl do_fiq再回到上面的中断发生处理中,在标签前面还有一个.word,在汇编里.word伪操作用于分配一段字内存单元(分配的单元都是字对齐的)
.balignl 16,0xdeadbeef首先来解释哈.balignl的应用形式:
.balign[wl] abs-expr, abs-expr, abs-expr增加位置计数器(在当前子段)使它指向规定的存储边界。第一个表达式参数(结果必须是纯粹的数字)是必需参数:边界基准,单位为字节。例如,‘.balign 8’向后移动位置计数器直至计数器的值等于8的倍数。如果位置计数器已经是8的倍数,则无需移动。第2个表达式参数(结果必须是纯粹的数字)给出填充字节 的值,用这个值填充位置计数器越过的地方。第2个参数(和逗点)可以省略。如果省略它,填充字节的值通常是0。但在某些系统上,如果本段标识为包含代码, 而填充值被省略,则使用no-op指令填充空白区。第3个参数的结果也必须是纯粹的数字,这个参数是可选的。如果存在第3个参数,它代表本对齐命令允许跳 过字节数的最大值。如果完成这个对齐需要跳过的字节数比规定的最大值还多,则根本无法完成对齐。您可以在边界基准参数后简单地使用两个逗号,以省略填充值 参数(第二参数);如果您在想在适当的时候,对齐操作自动使用no-op指令填充,本方法将非常奏效。.balignw和.balignl是.balign命令的变化形式。.balignw使用2个字节来填充空白区。.balignl使用4字节来填充。例如,.balignw 4,0x368d将地址对齐到4的倍数,如果它跳过2个字节,GAS将使用0x368d填充这2个字节(字节的确切存放位置视处理器的存储方式而定)。
ARM920T处理器核心,支持32位与16位两种指令长度,16位的指令叫thumb指令集,由于我使用的是32位指令集,所以一切都是以32位指令集进行说明。既然是32位指令集,所以一条指令就占32位,即4字节。
而在前面的两段代码中,可以通过计算,第一部分占了4x8=32字节内存;第二部分占了4x7=28字节内存。一共占了4x15=60个字节的内存,所以本代码的作者当时就简单的在15这个数上,加了个1,即16,把当前指针往后移到地址为64的位置,然后在前面插上了0xdeadbeef这个特殊的值。果说最小的值的话,那么也可以写成.balignl 8,0xdeadbeef,也可以达到同样的目的。因为60不是8的倍数,但是64是8的倍数,如果写8,也正好插到64前面,也即60这个内存起始地址。如果更大一点儿的呢,那么填32也可以达到同样的效果,即.balignl 32,0xdeadbeef,道理同上。当然,不能为4,因为pc值在任何时候,都是4的倍数,只要不为0就为4的倍数,呵呵,这个值不行,如果用了这个值,0xdeadbeef永远也插不进去。-----------------以上解释引自http://haoyeren.blog.sohu.com/84511571.html
这里再补充一点为什么用‘.balign 4’就不行,因为可以计算出当然PC的位置是60个字节,前面也有计算,所以4x15=60正好是4的倍数,无需移动,而指令的使用是用0xdeadbeef填充移动的空间,因为没有移动,所以中间没有空余的空间不能插呀!
前面对我们的表哥进行了算是详细的介绍,但正如前面所说的那样,系统上电后运行到第一句就跳走了,后面的是在异常发生时再进行跳转,所以真正引导系统启动的代码还没有开始哟!亲,辛苦了哟,现在就是开始的代码:
/* * the actual start code */ start_code: /* * set the cpu to SVC32 mode */ mrs r0, cpsr bic r0, r0, #0x1f orr r0, r0, #0xd3 msr cpsr, r0从以上的注释也可以看到,这里是将CPU的模式为设置为SVC模式,至于SVC是什么模式,这里得说明一下处理器硬件的东西了。ARM处理器共有37个寄存器,31个通用寄存器,分别为R0-R15,其中R8-R12为分组寄存器,R13可理解为堆栈指针(汇编里用的SP就是鼎鼎有名的R13),R14为链接寄存器(汇编里可也有这位大侠的句号哟,就是所用的LR链接寄存器),至于R15就更不用说了,就是传说中滴PC大神,程序计数器。除了31个通用寄存器还有6个状态寄存器,分为CPSR和SPSR,其中SPSR有五个分支,就是为处理器5种状态(fiq、irq、svc、abt、und,除此之外还有系统模式和用户模式。这里涉及到的模式就由此展开,不同的模式下可访问的资源权限是不同的,接下来通过一个表格详细介绍一下ARM中CPU的几种模式:
处理器模式 | 说明 | 主要功能 |
用户(usr) | 正常程序工作模式 | 此模式下程序不能够访问一些受操作系统保护的系统资源,应用程序也不能直接进行处理器模式的切换。 |
系统(sys) | 用于支持操作系统的特权任务等 | 与用户模式类似,但具有可以直接切换到其它模式等特权 |
快中断(fiq) | 支持高速数据传输及通道处理 | FIQ异常响应时进入此模式 |
中断(irq) | 用于通用中断处理 | IRQ异常响应时进入此模式 |
管理(svc) | 操作系统保护代码 | 系统复位和软件中断响应时进入此模式 |
中止(abt) | 用于支持虚拟内存和/或存储器保护 | 在ARM7TDMI没有大用处 |
未定义(und) | 支持硬件协处理器的软件仿真 | 未定义指令异常响应时进入此模式 |
sys模式和usr模式相比,所用的寄存器组,都是一样的,但是增加了一些访问一些在usr模式下不能访问的资源。而svc模式本身就属于特权模式,本身就可以访问那些受控资源,而且,比sys模式还多了些自己模式下的影子寄存器,所以,相对sys模式来说,可以访问资源的能力相同,但是拥有更多的硬件资源。所以,从理论上来说,虽然可以设置为sys和svc模式的任一种,但是从uboot方面考虑,其要做的事情是初始化系统相关硬件资源,需要获取尽量多的权限,以方便操作硬件,初始化硬件。
狗,很形象的东西,如果不喂狗就会乱咬人
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) /* turn off the watchdog */ # if defined(CONFIG_S3C2400) # define pWTCON 0x15300000 # define INTMSK 0x14400008 /* Interupt-Controller base addresses */ # define CLKDIVN 0x14800014 /* clock divisor register */ #else # define pWTCON 0x53000000 # define INTMSK 0x4A000008 /* Interupt-Controller base addresses */ # define INTSUBMSK 0x4A0000 # define CLKDIVN 0x4C000014 /* clock divisor register */ # endif ldr r0, =pWTCON mov r1, #0x0 str r1, [r0] /* * mask all IRQs by setting all bits in the INTMR - default */ mov r1, #0xffffffff ldr r0, =INTMSK str r1, [r0] # if defined(CONFIG_S3C2410) ldr r1, =0x3ff ldr r0, =INTSUBMSK str r1, [r0] # endif
如上代码中,前面的
#if defined....
.....
#else
....
#endif
是定义到寄存器,就是给寄存器取了一个别名,如同是我们的名字一样。如果在其它地方有定义,直接引入就可以了,不必重新定义,这里就不用多说了,此处略去一千字解释张三、李四名字的由来......
接下来通过ldr指向看门狗寄存器,下面就直接向狗写入0,这里就是一个关狗的过程。看门狗的原理就是:当看门狗开启后,如果一段时间不去喂狗,狗就会饿呀,狗饿了做干嘛呢,抢答题?你系统就会不断的一直重启,重启,还是重启。这里就是为什么在刚开始时会把狗关起来的原因,防止喂狗的时间到了,狗饿死了,然后你就挂了。
最后就是关中断,因为系统在初始化的时候不需要中断处理,如果打开的时候,你突然按一下按键,跳去处理按键,但系统还没初始化完呢?会出现什么情况呢,一样的结局,还没看到开机界面呢,挂了。