ARM linux的启动部分源代码简略分析.docx
《ARM linux的启动部分源代码简略分析.docx》由会员分享,可在线阅读,更多相关《ARM linux的启动部分源代码简略分析.docx(37页珍藏版)》请在冰点文库上搜索。
ARMlinux的启动部分源代码简略分析
ARMlinux的启动部分源代码简略分析
ARMlinux的启动部分源代码简略分析
以友善之臂的mini2440开发板为平台,以较新的内核linux-2.6.32.7版本为例,仅作说明之用。
当内核映像被加载到RAM之后,Bootloader的控制权被释放。
内核映像并不是可直接运行的目标代码,而是一个压缩过的zImage(小内核)。
但是,也并非是zImage映像中的一切均被压缩了,映像中包含未被压缩的部分,这部分中包含解压缩程序,解压缩程序会解压缩映像中被压缩的部分。
zImage使用gzip压缩的,它不仅仅是一个压缩文件,而且在这个文件的开头部分内嵌有gzip解压缩代码。
当zImage被调用时它从arch/arm/boot/compressed/head.S的start汇编例程开始执行。
这个例程进行一些基本的硬件设置,并调用arch/arm/boot/compressed/misc.c中的decompress_kernel()解压缩内核。
arch/arm/kernel/head.S文件是内核真正的启动入口点,一般是由解压缩内核的程序来调用的。
首先先看下对于运行这个文件的要求:
MMU=off;D-cache=off;I-cache=无所谓,开也可以,关也可以;r0=0;r1=机器号;r2=atags指针。
这段代码是位置无关的,所以,如果以地址0xC0008000来链接内核,那么就可以直接用__pa(0xc0008000)地址来调用这里的代码。
其实,在这个(Linux内核中总共有多达几十个的以head.S命名的文件)head.S文件中的一项重要工作就是设置内核的临时页表,不然mmu开起来也玩不转,但是内核怎么知道如何映射内存呢?
linux的内核将映射到虚地址0xCxxx xxxx处,但他怎么知道在4GB的地址空间中有哪一片ram是可用的,从而可以映射过去呢?
因为不同的系统有不通的内存映像,所以,LINUX约定,要调用内核代码,一定要满足上面的调用要求,以为最初的内核代码提供一些最重要的关于机器的信息。
内核代码开始的时候,R1存放的是系统目标平台的代号,对于一些常见的,标准的平台,内核已经提供了支持,只要在编译的时候选中就行了,例如对X86平台,内核是从物理地址1M开始映射的。
好了好了,看下面的代码。
arch/arm/kernel/head.S
ENTRY(stext)是这个文件的入口点。
最初的几行是这样的:
setmode PSR_F_BIT|PSR_I_BIT|SVC_MODE,r9
@ensuresvcmode
@andirqsdisabled
//设置为SVC模式,关闭中断和快速中断
//此处设定系统的工作状态为SVC,arm有7种状态每种状态
//都有自己的堆栈,SVC为管理模式,具有完全的权限,可以执行任意指令
//访问任意地址的内存
//setmode是一个宏,其定义为:
//.macrosetmode,mode,reg
//msr cpsr_c,#\mode
//.endm
mrc p15,0,r9,c0,c0 @getprocessorid
bl__lookup_processor_type @r5=procinfor9=cpuid
movs r10,r5 @invalidprocessor(r5=0)?
beq __error_p @yes,error'p'
这几行是查询处理器的类型的,我们知道arm系列有很多型号,arm7、arm9、arm11、Cortex核等等类型,这么多型号要如何区分呢?
其实,在arm的15号协处理器(其实ARM暂时也就这么一个协处理器)中有一个只读寄存器,存放与处理器相关信息。
__lookup_processor_type是arch/arm/kernel/head-common.S文件中定义的一个例程,这个head-common.S用include命令被包含在head.S文件中。
其定义为:
__lookup_processor_type:
adr r3,3f
ldmiar3,{r5-r7}
add r3,r3,#8
sub r3,r3,r7 @getoffsetbetweenvirt&phys
add r5,r5,r3 @convertvirtaddressesto
add r6,r6,r3 @physicaladdressspace
1:
ldmiar5,{r3,r4} @value,mask
and r4,r4,r9 @maskwantedbits
teq r3,r4
beq 2f
add r5,r5,#PROC_INFO_SZ @sizeof(proc_info_list)
cmp r5,r6
blo 1b
mov r5,#0 @unknownprocessor
2:
mov pc,lr
ENDPROC(__lookup_processor_type)
这个例程接受处理器ID(保存在寄存器r9中)为参数,查找链接器建立的支持的处理器表。
此时此刻还不能使用__proc_info表的绝对地址,因为这时候MMU还没有开启,所以此时运行的程序没有在正确的地址空间中。
所以不得不计算偏移量。
若没有找到processorID对应的处理器,则在r5寄存器中返回返回0,否则返回一个proc_info_list结构体的指针(在物理地址空间)。
proc_info_list结构体在文件中定义:
structproc_info_list{
unsignedint cpu_val;
unsignedint cpu_mask;
unsignedlong __cpu_mm_mmu_flags; /*usedbyhead.S*/
unsignedlong __cpu_io_mmu_flags; /*usedbyhead.S*/
unsignedlong __cpu_flush; /*usedbyhead.S*/
constchar *arch_name;
constchar *elf_name;
unsignedint elf_hwcap;
constchar *cpu_name;
structprocessor *proc;
structcpu_tlb_fns*tlb;
structcpu_user_fns *user;
structcpu_cache_fns *cache;
};
第一项是CPU id,将与协处理器中读出的id作比较,其余的字段也都是与处理器相关的信息,到下面初始化的过程中自然会用到。
另外,这个例程加载符地址的代码也是挺值得我辈学习的:
adr r3,3f
加载一个符号的地址,这个符号在加载语句前面(下面)定义,forward嘛,这个符号为3,离这条语句最近的那个。
在那个符号为3的位置我们看到这样的代码:
.align2
3:
.long__proc_info_begin
.long__proc_info_end
4:
.long.
.long__arch_info_begin
.long__arch_info_end
搜索这两个符号的值,在文件arch/arm/kernel/vmlinux.lds.S中:
__proc_info_begin=.;
*(.proc.info.init)
__proc_info_end=.;
这两个符号分别是一种初始化的段的结束开始地址和结束地址。
为了了解由structproc_info_list结构体组成的段的实际构成,我们还是得要了解一下在系统中到底都有哪些变量是声明了要被放到这个段的。
用关键字.proc.info.init来搜,全部都是arch/arm/mm/proc-*.S文件,这些都是特定于处理器的汇编语言文件,对于我们的mini2440,自然是要看proc-arm920.S文件的,在其中可以看到这些内容:
.section".proc.info.init",#alloc,#execinstr
.type__arm920_proc_info,#object
__arm920_proc_info:
.long0x41009200
.long0xff00fff0
.long PMD_TYPE_SECT|\
PMD_SECT_BUFFERABLE|\
PMD_SECT_CACHEABLE|\
PMD_BIT4|\
PMD_SECT_AP_WRITE|\
PMD_SECT_AP_READ
.long PMD_TYPE_SECT|\
PMD_BIT4|\
PMD_SECT_AP_WRITE|\
PMD_SECT_AP_READ
b __arm920_setup
.longcpu_arch_name
.longcpu_elf_name
.longHWCAP_SWP|HWCAP_HALF|HWCAP_THUMB
.longcpu_arm920_name
.longarm920_processor_functions
.longv4wbi_tlb_fns
.longv4wb_user_fns
#ifndefCONFIG_CPU_DCACHE_WRITETHROUGH
.longarm920_cache_fns
#else
.longv4wt_cache_fns
#endif
.size__arm920_proc_info,.-__arm920_proc_info
看到这儿我们再回国头去看__lookup_processor_type的代码:
ldmiar3,{r5-r7}
add r3,r3,#8
sub r3,r3,r7
尽管符号3处只有两个有效值,但它加载了三个数,而第三个数,我们看到是这样定义的:
.long.
__lookup_processor_type中,给r3加上8,也就是让r3指向“.”的地址,然后用r3减r7来获取虚拟地址与物理地址的差,这样看来,“.”就应该是虚拟空间(编译地址)里那个数据的地址。
之后的代码获得__proc_info_begin和__arch_info_end这两个符号在物理空间中的地址:
addr5,r5,r3 @convertvirtaddressesto
addr6,r6,r3
然后便是在那个段中逐个的检查structproc_info_list结构体,以找到与我们的CPU相匹配的:
1:
ldmiar5,{r3,r4} @value,mask
and r4,r4,r9 @maskwantedbits
teq r3,r4
beq 2f
add r5,r5,#PROC_INFO_SZ @sizeof(proc_info_list)
cmp r5,r6
blo 1b
mov r5,#0 @unknownprocessor
2:
mov pc,lr
__lookup_processor_type例程会返回在文件arch/arm/mm/proc-arm920.S中定义的一个保存有与我们的处理器相关的信息的structproc_info_list结构体的地址。
接下来我们继续看stext的代码:
bl__lookup_machine_type @r5=machinfo
movs r8,r5 @invalidmachine(r5=0)?
beq __error_a @yes,error'a'
在获得了处理器信息之后,则调用__lookup_machine_type来查找机器信息。
这个例程同样也在arch/arm/kernel/head-common.S文件中定义。
这个例程的定义如下:
__lookup_machine_type:
adr r3,4b
ldmiar3,{r4,r5,r6}
sub r3,r3,r4 @getoffsetbetweenvirt&phys
add r5,r5,r3 @convertvirtaddressesto
add r6,r6,r3 @physicaladdressspace
1:
ldr r3,[r5,#MACHINFO_TYPE]@getmachinetype
teq r3,r1 @matchesloadernumber?
beq 2f @found
add r5,r5,#SIZEOF_MACHINE_DESC @nextmachine_desc
cmp r5,r6
blo 1b
mov r5,#0 @unknownmachine
2:
mov pc,lr
ENDPROC(__lookup_machine_type)
处理的过程和上面的__lookup_processor_type还是挺相似的。
这个例程接收r1中传进来的机器号作为参数,然后,在一个由structmachine_desc结构体组成的段中查找和我们的机器号匹配的structmachine_desc结构体,这个结构体在arch/arm/include/asm/mach/arch.h文件中定义,用于保存机器的信息:
structmachine_desc{
/*
*Note!
Thefirstfourelementsareused
*byassemblercodeinhead.S,head-common.S
*/
unsignedint nr; /*architecturenumber */
unsignedint phys_io;/*startofphysicalio */
unsignedint io_pg_offst;/*byteoffsetforio
*pagetabeentry*/
constchar *name; /*architecturename */
unsignedlong boot_params;/*taggedlist */
unsignedint video_start;/*startofvideoRAM*/
unsignedint video_end; /*endofvideoRAM */
unsignedint reserve_lp0:
1;/*neverhaslp0 */
unsignedint reserve_lp1:
1;/*neverhaslp1 */
unsignedint reserve_lp2:
1;/*neverhaslp2 */
unsignedint soft_reboot:
1;/*softreboot */
void (*fixup)(structmachine_desc*,
structtag*,char**,
structmeminfo*);
void (*map_io)(void);/*IOmappingfunction */
void (*init_irq)(void);
structsys_timer *timer; /*systemticktimer */
void (*init_machine)(void);
};
同样这个例程也用到了同上面很相似的方式来获得符号的地址:
adr r3,4b
b代表back,即向后,这个符号为4,紧接着我们前面看到的那个为3的标号:
4:
.long.
.long__arch_info_begin
.long__arch_info_end
在文件arch/arm/kernel/vmlinux.lds.S中我们可以看到段的定义:
__arch_info_begin=.;
*(.arch.info.init)
__arch_info_end=.;
这两个符号也是分别表示某种初始化的段的开始地址和结束地址。
为了找到段的填充内容,还是得要了解一下到底都有哪些structmachine_desc结构体类型变量声明了要被放到这个段的。
用关键字.arch.info.init来搜索所有的内核源文件。
在arch/arm/include/asm/mach/arch.h文件中我们看到:
#defineMACHINE_START(_type,_name) \
staticconststructmachine_desc__mach_desc_##_type\
__used \
__attribute__((__section__(".arch.info.init")))={\
.nr =MACH_TYPE_##_type, \
.name =_name,
#defineMACHINE_END \
};
定义机器结构体,也就是.arch.info.init段中的内容,都是要通过两个宏MACHINE_START和MACHINE_END来完成的啊,MACHINE_START宏定义一个tructmachine_desc结构体,并初始化它的机器号字段和机器名字段,可以在arch/arm/tools/mach-types文件中看到各种平台的机器号的定义。
那接着我们来搜MACHINE_START吧,这是一个用于定义机器结构体的宏,所以可以看到这个符号好像都是在arch/arm/mach-*/mach-*.c这样的文件中出现的,我们感兴趣的应该是arch/arm/mach-s3c2440/mach-mini2440.c文件中的这个符号:
MACHINE_START(MINI2440,"MINI2440")
/*Maintainer:
MichelPollet*/
.phys_io=S3C2410_PA_UART,
.io_pg_offst=(((u32)S3C24XX_VA_UART)>>18)&0xfffc,
.boot_params=S3C2410_SDRAM_PA+0x100,
.map_io =mini2440_map_io,
.init_machine =mini2440_init,
.init_irq=s3c24xx_init_irq,
.timer =&s3c24xx_timer,
MACHINE_END
OK,__lookup_machine_type这个例程的我们也搞明白了。
回忆一下,启动代码现在已经完成的工作,R10寄存器中为指向proc_info_list结构体的指针(物理地址空间),这个结构体包含有关于我们的处理器的一些重要信息。
R8寄存器中为指向一个与我们的平台相匹配的machine_desc结构体的指针,这个结构体中保存有一些关于我们的平台的重要信息。
回来接着看arch/arm/kernel/head.S文件中的stext:
bl__vet_atags
这个例程同样同样也是在arch/arm/kernel/head-common.S文件中定义:
__vet_atags:
tst r2,#0x3 @aligned?
bne 1f
ldr r5,[r2,#0] @isfirsttagATAG_CORE?
cmp r5,#ATAG_CORE_SIZE
cmpner5,#ATAG_CORE_SIZE_EMPTY
bne 1f
ldr r5,[r2,#4]
ldr r6,=ATAG_CORE
cmp r5,r6
bne 1f
mov pc,lr @atagpointerisok
1:
mov r2,#0
mov pc,lr
ENDPROC(__vet_atags)
这个例程接收机器信息(R8寄存器)为参数,并检测r2中传入的ATAGS指针的合法性。
内核使用tag来作为bootloader传递内核参数的方式。
系统要求r2中传进来的ATAGS指针式4字节对齐的,同时要求ATAGS列表的第一个tag是一个ATAG_CORE类型的。
此时R10寄存器中保存有指向CPU信息结构体的指针,R8寄存器中保存有指向机器结构体的指针,R2寄存器中保存有指向tag表的指针,R9中还保存有CPUID信息。
回到arch/arm/kerne