Uboot中start.S源码的指令级的详尽解析
第 3 章相关知识点详解
⽬录
摘要
3.1. 如何查看C或汇编的源代码所对应的真正的汇编代码
⾸先解释⼀下,由于汇编代码中会存在⼀些伪指令等内容,所以,写出来的汇编代码,并不⼀定是真正可以执⾏的代码,这些类似于伪指令的汇编代码,经过汇编器,转换或翻译成真正的可以执⾏的汇编指令。所以,上⾯才会有将“汇编源代码”转换为“真正的汇编代码”这⼀说。
然后,此处对于有些⼈不是很熟悉的,如何查看源代码真正对应的汇编代码。
此处,对于汇编代码,有两种:
1. ⼀种是只是进过编译阶段,⽣成了对应的汇编代码
2. 另外⼀种是,编译后的汇编代码,经过链接器链接后,对应的汇编代码。
总的来说,两者区别很⼩,后者主要是更新了外部函数的地址等等,对于汇编代码本⾝,⾄少对于我们⼀般所去查看源代码所对应的汇编来说,两者可以视为没区别。
在查看源代码所对应的真正的汇编代码之前,先要做⼀些相关准备⼯作:
1. 编译uboot
在Linux下,⼀般编译uboot的⽅法是:
1. make distclean
去清除之前配置,编译等⽣成的⼀些⽂件。
2. make EmbedSky_config
去配置我们的uboot
3. make
去执⾏编译
2. 查看源码所对应的汇编代码
源代码电影讲解
对于我们此处的uboot的start.S来说:
1. 对于编译所⽣成的汇编的查看⽅式是
⽤交叉编译器的dump⼯具去将汇编代码都导出来:
arm-linux-objdump –d cpu/arm920t/start.o > uboot_start.o_
这样就把start.o中的汇编代码导出到uboot_start.o_中了。
然后查看uboot_start.o_,即可到对应的汇编代码。
举例来说,对于start.S中的汇编代码:
/* Set up the stack          */
stack_setup:
ldr r0, _TEXT_BASE  /* upper 128 KiB: relocated uboot  */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area                      */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12  /* leave 3 words for abort-stack    */
bl clock_init
去uboot_start.o_中,搜索stack_setup,即可到对应部分的汇编代码:
00000090 <stack_setup>:
90: e51f0058  ldr r0, [pc, #-88] ; 40 <_TEXT_BASE>
94: e2400701  sub r0, r0, #262144 ; 0x40000
98: e2400080  sub r0, r0, #128 ; 0x80
9c: e240d00c  sub sp, r0, #12 ; 0xc
a0: ebfffffe  bl 0 <clock_init>
2. 对于链接所⽣成的汇编的查看⽅式是
和上⾯⽅法⼀样,即:
arm-linux-objdump –d u-boot > whole_uboot_
然后打开该txt,到stack_setup部分的代码:
33d00090 <stack_setup>:
33d00090: e51f0058  ldr r0, [pc, #-88] ; 33d00040 <_TEXT_BASE>
33d00094: e2400701  sub r0, r0, #262144 ; 0x40000
33d00098: e2400080  sub r0, r0, #128 ; 0x80
33d0009c: e240d00c  sub sp, r0, #12 ; 0xc
33d000a0: eb000242  bl 33d009b0 <clock_init>
两者不⼀样地⽅在于,我们uboot设置了text_base,即代码段的基地址,上⾯编译后的汇编代码,经过链接后,更新了对应的基地址,所以看起来,所以代码对应的地址,都变了,但是具体地址中的汇编代码,除了个别调⽤函数的地址和跳转到某个标号的地址之外,其他都还是⼀样的。
对于C语⾔的源码,也是同样的⽅法,⽤对应的dump⼯具,去从该C语⾔的.o⽂件中,dump出来汇编代码。
3.2. uboot初始化中,为何要设置CPU为SVC模式⽽不是设置为其他模式
在看Uboot的start.S⽂件时候,发现其最开始初始化系统,做的第⼀件事情,就是将CPU设置为SVC模式,但是S3C2440的CPU的core是ARM920T,其有7种模式,为何⾮要设置为SVC模式,⽽不是设置为其他模式呢?对此,经过⼀些求证,得出如下原因:
⾸先,先要了解ARM的CPU的7种模式是哪些:
表 3.1. ARM中CPU的模式
处理器模式说明备注
⽤户(usr)正常程序⼯作模式此模式下程序不能够访问⼀些受操作系统保护的系统资源,应⽤程序也不能直接进⾏处理器模式的切换。
系统(sys)⽤于⽀持操作系统的特权任务等与⽤户模式类似,但具有可以直接切换到其它模式等特权
快中断(fiq)⽀持⾼速数据传输及通道处理FIQ异常响应时进⼊此模式
中断(irq)⽤于通⽤中断处理IRQ异常响应时进⼊此模式
管理(svc)操作系统保护代码系统复位和软件中断响应时进⼊此模式
中⽌(abt)⽤于⽀持虚拟内存和/或存储器保护在ARM7TDMI没有⼤⽤处
未定义(und)⽀持硬件协处理器的软件仿真未定义指令异常响应时进⼊此模式
另外,7种模式中,除⽤户usr模式外,其它模式均为特权模式。
对于为何此处是svc模式,⽽不是其他某种格式,其原因,可以从两⽅⾯来看:
1. 我们先简单的来分析⼀下那7种模式:
1. 中⽌abt和未定义und模式
⾸先可以排除的是,中⽌abt和未定义und模式,那都是不太正常的模式,此处程序是正常运⾏的,所以不应该设置CPU为其中任何⼀种模式,所以可以排除。
2. 快中断fiq和中断irq模式
其次,对于快中断fiq和中断irq来说,此处uboot初始化的时候,也还没啥中断要处理和能够处理,⽽且即使是注册了终端服务程序后,能够处理中断,那么这两种模式,也是⾃动切换过去的,所以,此处也不应该设置为其中任何⼀种模式。
3. ⽤户usr模式
虽然从理论上来说,可以设置CPU为⽤户usr模式,但是由于此模式⽆法直接访问很多的硬件资源,⽽uboot初始化,就必须要去访问这类资源,所以此处可以排除,不能设置为⽤户usr模式。
4. 系统sys模式 vs 管理svc模式
⾸先,sys模式和usr模式相⽐,所⽤的寄存器组,都是⼀样的,但是增加了⼀些访问⼀些在usr模式下不能访问的资源。
⽽svc模式本⾝就属于特权模式,本⾝就可以访问那些受控资源,⽽且,⽐sys模式还多了些⾃⼰模式下的影⼦寄存器,所以,相对sys模式来说,可以访问资源的能⼒相同,但是拥有更多的硬件资源。
所以,从理论上来说,虽然可以设置为sys和svc模式的任⼀种,但是从uboot⽅⾯考虑,其要做的事情是初始化系统相关硬件资源,需要获取尽量多的权限,以⽅便操作硬件,初始化硬件。
从uboot的⽬的是初始化硬件的⾓度来说,设置为svc模式,更有利于其⼯作。
因此,此处将CPU设置为SVC模式。
2. uboot作为⼀个bootloader来说,最终⽬的是为了启动Linux的kernel,在做好准备⼯作(即初始化硬件,准备好kernel和rootfs等)跳转到kernel之前,本⾝就要满
⾜⼀些条件,其中⼀个条件,就是要求CPU处于SVC模式的。
所以,uboot在最初的初始化阶段,就将CPU设置为SVC模式,也是最合适的。
所以,uboot在最初的初始化阶段,就将CPU设置为SVC模式,也是最合适的。
综上所述,uboot在初始化阶段,就应该将CPU设置为SVC模式。
3.3. 什么是watchdog + 为何在要系统初始化的时候关闭watchdog
关于Uboot初始化阶段,在start.S中,为何要去关闭watchdog,下⾯解释具体的原因:
3.3.1. 什么是watchdog
参考
简要摘录如下:
watchdog⼀般是⼀个硬件模块,其作⽤是,在嵌⼊式操作系统中,很多应⽤情况是系统长期运⾏且⽆⼈看守,所以难免或者怕万⼀出现系统死机,那就杯具了,这时,watchdog就会⾃动帮你重启系统。
那么其是如何实现此功能的呢?那么就要简单解释⼀下其实现原理了。
watchdog硬件的逻辑就是,其硬件上有个记录超时功能,然后要求⽤户需要每隔⼀段时间(此时间可以根据⾃⼰需求⽽配置)去对其进⾏⼀定操作,⽐如往⾥⾯写⼀些固定的值,俗称“喂狗”,那么我发现超时了,即过了这么长时间你还不给偶喂⾷,那么偶就认为你系统是死机了,出问题了,偶就帮你重启系统。
说⽩了就是弄个看家狗dog,你要定期给其喂⾷,如果超时不喂⾷,那么狗就认为你,他的主⼈,你的系统,死机了,就帮你reset重启系统。
3.3.2. 为何在要系统初始化的时候关闭watchdog
了解了watchdog的原理后,此问题就很容易理解了。
如果不禁⽤watchdog,那么就要单独写程序去定期“喂狗”,那多⿇烦,多⽆聊啊。
毕竟咱此处只是去⽤uboot初始化必要的硬件资源和系统资源⽽已,完全⽤不到这个watchdog的机制。
需要⽤到,那也是你linux内核跑起来了,是你系统关⼼的事情,和我uboot没啥关系的,所以肯定此处要去关闭watchdog(的reset功能)了。
3.4. 为何ARM7中PC=PC+8
此处解释为何ARM7中,CPU地址,即PC,为何有PC=PC+8这⼀说法:
众所周知,AMR7,是三级流⽔线,其细节见图:
图 3.1. AMR7三级流⽔线
⾸先,对于ARM7对应的流⽔线的执⾏情况,如下⾯这个图所⽰:
图 3.2. ARM7三级流⽔线状态
然后对于三级流⽔线举例如下:
图 3.3. ARM7三级流⽔线⽰例
从上图,其实很容易看出,第⼀条指令:
add r0, r1,$5
执⾏的时候,此时PC已经指向第三条指令:
cmp r2,#3
的地址了,所以,是PC=PC+8.
3.4.1. 为何ARM9和ARM7⼀样,也是PC=PC+8
ARM7的三条流⽔线,PC=PC+8,很好理解,但是AMR9中,是五级流⽔线,为何还是PC=PC+8,⽽不是PC
=PC+(5-1)*4
=PC + 16,
呢?
下⾯就需要好好解释⼀番了。
具体解释之前,先贴上ARM7和ARM9的流⽔线的区别和联系:
图 3.4. ARM7三级流⽔线 vs ARM9五级流⽔线
图 3.5. ARM7三级流⽔线到ARM9五级流⽔线的映射
下⾯开始对为何ARM9也是PC=PC+8进⾏解释。
先列出ARM9的五级流⽔线的⽰例:
图 3.6. ARM9的五级流⽔线⽰例
举例分析为何PC=PC+8
然后我们以下⾯uboot中的start.S的最开始的汇编代码为例来进⾏解释:
00000000 <_start>:
0: ea000014  b 58 <reset>
4: e59ff014  ldr pc, [pc, #20] ; 20 <_undefined_instruction>
8: e59ff014  ldr pc, [pc, #20] ; 24 <_software_interrupt>
c: e59ff014  ldr pc, [pc, #20] ; 28 <_prefetch_abort>
10: e59ff014  ldr pc, [pc, #20] ; 2c <_data_abort>
14: e59ff014  ldr pc, [pc, #20] ; 30 <_not_used>
18: e59ff014  ldr pc, [pc, #20] ; 34 <_irq>
1c: e59ff014  ldr pc, [pc, #20] ; 38 <_fiq>
00000020 <_undefined_instruction>:
20: 00000120  .word 0x00000120
下⾯对每⼀个指令周期,CPU做了哪些事情,分别详细进⾏阐述:
在看下⾯具体解释之前,有⼀句话要牢记,那就是:
PC不是指向你正在运⾏的指令,⽽是
PC始终指向你要取的指令的地址
认识清楚了这个前提,后⾯的举例讲解,就容易懂了。
1. 指令周期Cycle1
1. 取指
PC总是指向将要读取的指令的地址(即我们常说的,指向下⼀条指令的地址),⽽当前PC=4,
所以去取物理地址为4对对应的指令
ldr pc, [pc, #20]
其对应⼆进制代码为e59ff014。
此处取指完之后,⾃动更新PC的值,即PC=PC+4(单个指令占4字节,所以加4)=4+4=8
2. 指令周期Cycle2
1. 译指
翻译指令e59ff014
2. 同时再去取指
PC总是指向将要读取的指令的地址(即我们常说的,指向下⼀条指令的地址),⽽当前PC=8,
所以去物理地址为8所对应的指令“ldr pc, [pc, #20]” 其对应⼆进制代码为e59ff014。
此处取指完之后,⾃动更新PC的值,即PC=PC+4=8+4=12=0xc
3. 指令周期Cycle3
1. 执⾏(指令)
执⾏“e59ff014”,即
ldr pc, [pc, #20]
所对表达的含义,即PC
= PC + 20
= 12 + 20
= 32
= 0x20
此处,只是计算出待会要赋值给PC的值是0x20,这个0x20还只是放在执⾏单元中内部的缓冲中。
2. 译指
翻译e59ff014
3. 取指
此步骤由于是和上⾯(1)中的执⾏同步做的,所以,未受到影响,继续取指,⽽取指的那⼀时刻,PC为上⼀Cycle更新后的值,即PC=0xc,所以是去取物理地址为0xc所对应的指令
ldr pc, [pc, #20]
对应⼆进制为e59ff014
其实,分析到这⾥,⼤家就可以看出:
在Cycle3的时候,PC的值,刚好已经在Cycle1和Cycle2,分别加了4,所以Cycle3的时候,PC=PC+8,⽽同样道理,对于任何⼀条指令的,都是在Cycle3,指令的Execute执⾏阶段,如果⽤到PC的值,那么PC那⼀时刻,就是PC=PC+8。
所以,此处虽然是五级流⽔线,但是却不是PC=PC+16,⽽是PC=PC+8。
进⼀步地,我们发现,其实PC=PC+N的N,是和指令的执⾏阶段所处于流⽔线的深度有关,即此处指令的执⾏Execute阶段,是五级流⽔线中的第三个,⽽这个第三阶段的Execute和指令的第⼀个阶段的Fetch取指,相差的值是 3 -1 =2,即两个CPU的Cycle,⽽每个Cycle都会导致PC=+PC+4,所以,指令到了Execute阶段,才会发现,此时PC已经变成PC=PC+8了。
回过头来反观ARM7的三级流⽔线,也是同样的道理,指令的Execute执⾏阶段,是处于指令的第三个阶段,同理,在指令计算数据的时候,如果⽤到PC,就会发现此时PC=PC+8。
同理,假如ARM9的五级流⽔线,把指令的Execute执⾏阶段,设计在了第四个阶段,那么就是PC=PC+(第4阶段-1)*4个字节 = PC= PC+12了。
⽤图来说明PC=PC+8个过程

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。