在前面包含头文件,定义一些必要的宏之后,就到了实际运行代码的开始了,这里就是内核解压内核映象文件的开始位置了,也就是代码段的开始位置了。前面都没有看到实际的代码,因此会碰到很多新的知识点,新的拦路虎的,这更加需要花费时间和耐心了。现在就开始吧!
.section".start", #alloc, #execinstr
在这行代码里,先看来理解section关键字,这个关键字主要用来定义一个段的开始。在计算机组织可执行代码,主要通过分段的方式,比如一般的程序,都包括有几个段:代码段、数据段、堆栈段、全局数据段。这个关键字的格式如下:
.sectionsection_name [, "flags"[, %type[,flag_specific_arguments]]]
每一个段是以段名为开始,以下一个段为结束或者文件结束。在这个段里段名是.start,后面两个是参数,#alloc是表示本段包含有可分配数据,#execinstr是表示本段是可执行段。
/*
*sort out different calling conventions
*/
.align
start:
.type start,#function
.rept 8
mov r0,r0
.endr
b 1f
.word 0x016f2818 @Magic numbers to help the loader
.word start @absolute load/run zImage address
.word _edata @zImage end address
1: mov r7,r1 @ save architecture ID
mov r8,r2 @ save atags pointer
在定义段开始之后,就应进入实际的代码,一看之下,居然还不是可执行的代码,还是一个关键字的标号align,而这个关键字是做什么用呢?在开发X86的汇编代码看不到这个吧?其实是这个关键字跟ARM很有缘份的,因为ARM的处理器都需要指令按四字节对齐的方式来取指令的,这样才可以达到速度最快。因此,这里使用这个关键,就是让后面的指令码按ARM的要求进行指令对齐。
接着定义start标号代标实际代码开始位置,这样可以让连接脚本找到实际代码开始位置,当然它也是可以代码一个函数的名称的。关键字type用指明start标号是作为一个函数使用。
接着在后使用关键字rept重复8条移动指令,为什么需要这样呢?这样做不是在做无用功吗?其实开发过嵌入式系统的人,或者对硬件有了解的人,都知道CPU在启动时,各个电路还在电压上升阶段,需要稍微等一下,因此就需要先运行几个空指令作为延时作用。后面紧跟着调用跳转指令,跳转到标号1的位置来运行。再来仔细看一下这条跳转指令,它后面写着是1f,如果你把1f作为一个标号来查找,整个汇编代码都没有的。其实ARM的汇编里有一个标号定义的习惯,在标号可以定义为数字,从跳转指令的位置开始,在这条指令的前面数据字标号,使用b为后缀,而向前跳转的使用f,其实b就是back的缩写,f是front的缩写。这里的1f的意思就是向前跳转到标号1的位置继续运行。
跳转指令后面是定义了三个数据,这三个数据不是运行指令,因此要忽略它。定义数据0x016f2818是用来标识本压缩内核用的,当本文件运行时,就可以先检查这个ID是否正确,如果正确了才开始运行,只起到这个作用。.word start数据是用来保存zImage运行的起始位置,也就是前面定义start标号的地址。.word _edata是用来保存zImage的结束位置,后面可以根据这两个位置信息来移动数据,或者解压整个文件时用到。
接着看到标号1后面两行代码,第一行代码是从r1里保存CPU架构ID到r7寄存器里,第二代码是从r2里保存输入命令行参数指令到r8寄存器。这两个参数是从引导程序UBOOT里传送过来,CPU的架构ID用来标识CPU的系统结构,以便运行不同的指令代码和不同的内存配置,或者不同的特性,比如是否有MMU等特征。命令行参数主要用来给LINUX内核使用,让内核知道从那里启动,根文件系统类型、位置,以及最开始运行的程序的名称和位置。
//QQ:9073204 EMAIL:[email protected]
//蔡军生 2012-2-26