设置堆栈 sp 指针
上面代码中,第一行的意思很简单,就是把地址为_TEXT_BASE的内存中的内容给r0,而查看前面的相关部分的代码,即:
_TEXT_BASE: .word TEXT_BASE
得知,地址为_TEXT_BASE的内存中的内容,就是
r0 = TEXT_BASE = 0x33D00000
sub指令:
所以对应含义为:
r0 = r0 - #CFG_MALLOC_LEN r0 = r0 - #CFG_GBL_DATA_SIZE
其中,对应的两个宏的值是:
u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\include\configs\EmbedSky.h中:
所以,从源码中的宏定义中可以看出,
所以,此三行的含义就是算出r0的值:
如果定义了CONFIG_USE_IRQ,即如果使用中断的话,那么再把r0的值减去IRQ和FIQ的堆栈的值,
而对应的宏的值也是在u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\include\configs\EmbedSky.h中:
所以,此时r0的值就是:
最后,再减去终止异常所用到的堆栈大小,即12个字节。现在r0的值为:
然后将r0的值赋值给sp,即堆栈指针。
关于:
- sp代表stack pointer,堆栈指针;
和后面要提到的ip寄存器:
- ip代表instruction pointer,指令指针。
更多详情参见下面的解释。
ARM的寄存器的别名和相关的APCS
此处简单介绍一下,ARM 寄存器的别名,以及什么是 APCS。
1.ARM 中的寄存器的别名
默认的情况下,这些寄存器只是叫做r0,r1,…,r14等,而APCS 对其起了不同的别名。
使用汇编器预处理器的功能,你可以定义 R0 等名字,但在你修改其他人写的代码的时候,最好还是学习使用 APCS 名字。
一般编程过程中,最好按照其约定,使用对应的名字,这样使得程序可读性更好。
关于不同寄存器所对应的名字,见下表:
APCS寄存器别名定义:
更加详细一点,见下:
2.什么是 APCS
APCS,ARM 过程调用标准(ARM Procedure Call Standard),提供了紧凑的编写例程的一种机制,定义的例程可以与其他例程交织在一起。最显著的一点是对这些例程来自哪里没有明确的限制。它们可以编译自 C、 Pascal、也可以是用汇编语言写成的。
APCS 定义了:
- 对寄存器使用的限制。
- 使用栈的惯例。
- 在函数调用之间传递/返回参数。
- 可以被‗回溯‘的基于栈的结构的格式,用来提供从失败点到程序入口的函数(和给予的
参数)的列表。
算出堆栈位置
在上面,经过计算,算出了堆栈的地址,然后赋值给了sp,此处,接着才去调用函数clock_init去初始化时钟。
其中此函数是在C文件:
u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\boot_init.c
中:
void clock_init(void) { 。。。设置系统时钟clock的相关代码。。。 }
看到这里,让我想起,关于其他人的关于此start.S代码解释中说到的,此处是先去设置好堆栈,即初始化sp指针,然后才去调用C语言的函数clock_init的。
#ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit #endif
就不需要先设置好堆栈,再去进行函数调用。
其中cpu_init_crit对应的代码也在start.S中(详见后面对应部分的代码),是用汇编实现的。
adr指令
这个由于流水线导致的PC的值和当前指令地址不同的现象,就是我们常说的,ARM中,PC=PC+8。
总结
why
那对于C语言,为何就需要堆栈,而汇编却不需要堆栈的原因?
为何 C 语言(的函数调用)需要堆栈,而汇编语言却不需要堆栈
之前看了很多关于uboot的分析,其中就有说要为C语言的运行,准备好堆栈。
而自己在Uboot的start.S汇编代码中,关于系统初始化,也看到有堆栈指针初始化这个动作。
但是,从来只是看到有人说系统初始化要初始化堆栈,即正确给堆栈指针sp赋值,但是却从来没有看到有人解释,为何要初始化堆栈。所以,接下来的内容,就是经过一定的探究,试图来解释一下,为何要初始化堆栈,即:
为何C语言的函数调用要用到堆栈,而汇编却不需要初始化堆栈。
要明白这个问题,首先要了解堆栈的作用。
关于堆栈的作用,要详细讲解的话,要很长的篇幅,所以此处只是做简略介绍。
总的来说,堆栈的作用就是:保存现场/上下文,传递参数。
1.保存现场/上下文
现场,意思就相当于案发现场,总有一些现场的情况,要记录下来的,否则被别人破坏掉之后,你就无法恢复现场了。而此处说的现场,就是指CPU运行的时候,用到了一些寄存器,比如r0,r1等等,对于这些寄存器的值,如果你不保存而直接跳转到子函数中去执行,那么很可能就被其破坏了,因为其函数执行也要用到这些寄存器。
因此,在函数调用之前,应该将这些寄存器等现场,暂时保持起来,等调用函数执行完毕返回后,再恢复现场。这样CPU就可以正确的继续执行了。
在计算机中,你常可以看到上下文这个词,对应的英文是context。那么:
1. 什么叫做上下文 context
保存现场,也叫保存上下文。
上下文,英文叫做context,就是上面的文章,和下面的文章,即与你此刻,当前CPU运行有关系的内容,即那些你用到寄存器。所以,和上面的现场,是一个意思。
保存寄存器的值,一般用的是push指令,将对应的某些寄存器的值,一个个放到堆栈中,把对应的值压入到堆栈里面,即所谓的压栈。
然后待被调用的子函数执行完毕的时候,再调用pop,把堆栈中的一个个的值,赋值给对应的那些你刚开始压栈时用到的寄存器,把对应的值从堆栈中弹出去,即所谓的出栈。
其中保存的寄存器中,也包括lr的值(因为用bl指令进行跳转的话,那么之前的pc的值是存在lr中的),然后在子程序执行完毕的时候,再把堆栈中的lr的值pop出来,赋值给pc,这样就实现了子函数的正确的返回
2.传递参数
C语言进行函数调用的时候,常常会传递给被调用的函数一些参数,对于这些C语言级别的参数,被编译器翻译成汇编语言的时候,就要找个地方存放一下,并且让被调用的函数能够访问,否则就没发实现传递参数了。对于找个地方放一下,分两种情况。
一种情况是,本身传递的参数就很少,就可以通过寄存器传送参数。
因为在前面的保存现场的动作中,已经保存好了对应的寄存器的值,那么此时,这些寄存器就是空闲的,可以供我们使用的了,那就可以放参数,而参数少的情况下,就足够存放参数了,比如参数有2个,那么就用r0和r1存放即可。(关于参数1和参数2,具体哪个放在r0,哪个放在r1,就是和APCS中的“在函数调用之间传递/返回参数”相关了,APCS中会有详细的约定。感兴趣的自己去研究。)
但是如果参数太多,寄存器不够用,那么就得把多余的参数堆栈中了。即,可以用堆栈来传递所有的或寄存器放不下的那些多余的参数。
3.举例分析 C 语言函数调用是如何使用堆栈的
为何 ARM7 中 PC=PC+8
首先,对于 ARM7 对应的流水线的执行情况,如下面这个图所示:
ARM7
为何 ARM9 和 ARM7 一样,也是 PC=PC+8
关于为何不直接用 mov 指令,而非要用 adr 伪指令
mov 指令的操作数的取值范围到底是多少
http://blog.chinaunix.net/space.php?uid=20799298&do=blog&cuid=2055392
http://netwinder.osuosl.org/pub/netwinder/docs/arm/ARM7500FEvB_3.pdf
参考文献
1.2010年6月 最新TQ2440光盘下载 (Linux内核,WinCE的eboot,uboot均有更新)
http://bbs.embedsky.net/viewthread.php?tid=859
2. .globl,.word,.balignl的语法
http://re-eject.gbadev.org/files/GasARMRef.pdf
3.label 的解释
http://sourceware.org/binutils/docs-2.20/as/Labels.html#Labels
4. ldr的语法:
http://infocenter.arm.com/help/topic/com.arm.doc.dui0206hc/DUI0206HC_
rvct_linker_and_utilities_guide.pdf
5. ldr 指令
http://wenku.baidu.com/view/f7cc280102020740be1e9bea.html
6. ldr 指令
http://www.pczpg.com/a/2010/0607/11062.html
7. .word 的语法
http://blogold.chinaunix.net/u3/115924/showart_2280163.html
8. ARM7体系结构
http://www.docin.com/p-73665362.html
9.bootloader
http://www.csie.nctu.edu.tw/~wjtsai/EmbeddedSystemDesign/Ch2-bootloader.pdf
10. S3C2440相关的软硬件资料
http://just4you.springnote.com/pages/1052612
11. S3C2440的CPU的datasheet:s3c2440a_um_rev014_040712.pdf
http://just4you.springnote.com/pages/1052612/attachments/803220
12.伪指令ldr语法和含义
http://blog.csdn.net/lihaoweiV/archive/2010/11/24/6033003.aspx
13. ARM9 2410移植之ARM中断原理, 中断嵌套的误区,中断号的怎么来的
http://againinput4.blog.163.com/blog/static/17279949120113882341352/
14.adr指令的语法和含义
http://blog.mcuol.com/User/cdkfGao/article/8057_1.htm
15. ARM协处理器
http://apps.hi.baidu.com/share/detail/32319228
16. ARM920T
http://infocenter.arm.com/help/topic/com.arm.doc.ddi0151c/ARM920T_TRM1_S.pdf
17. CP15 的各个寄存器的含义解释
http://www.heyrick.co.uk/assembler/coprocmnd.html
18. Invalidate ICache and DCache
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0184b/Chdcfejb.html
19. Invalidate TLB(s)
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0184b/Chdifbjc.html
20. Control register
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0184b/Chdifbjc.html
21. Domain access control
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0151c/I273867.html
22. ARM920T 的 CPU 的 7 种模式
http://www.docin.com/p-73665362.html
23. ARM Linux Kernel Boot Requirements
http://www.arm.linux.org.uk/developer/booting.php
24. 嵌入式系统之 WATCHDOG(看门狗)概述
http://wenku.baidu.com/view/e5cd52ff04a1b0717fd5dd27.html
25. ARM 流水线和 program counter(PC)的增量
http://hi.baidu.com/istry/blog/item/f823e1438de0a71972f05d0f.html
26. Predeclared register names
http://www.keil.com/support/man/docs/armasm/armasm_ch03s03s01.htm
27. Predeclared extension register names
http://www.keil.com/support/man/docs/armasm/armasm_ch03s03s02.htm
28. Predeclared coprocessor names
http://www.keil.com/support/man/docs/armasm/armasm_ch03s03s03.htm
29. mov 的操作数的取指范围
http://blog.chinaunix.net/space.php?uid=20799298&do=blog&cuid=2055392
30. ARM Processor Instruction Set
http://netwinder.osuosl.org/pub/netwinder/docs/arm/ARM7500FEvB_3.pdf