一.arch/arm/kernel/head-armv.S(我的好像就叫head.S)
内核最先执行的一个文件,包括内核入口ENTRY(stext)到start_kernel间初始代码
主要作用是检查CPU ID,
Architecture Type,初始化BSS等操作,并跳到start_kernel函数。在执行前,处理器应满足以下状态:
r0 - should be 0
r1 - unique architecture number
MMU - off
I-cache - on or off
D-cache – off
1.head.S分析
.section ".text.head", "ax"
.type stext, %function
ENTRY(stext) //内核入口点
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
//禁止FIQ IRQ,进入管理模式
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id //读取CPU ID存入R9
bl __lookup_processor_type @ r5=procinfo r9=cpuid
//内核是否支持该架构,输入参数r9=cpuid
,返回值r5=procinfo
movs r10, r5 @ invalid processor (r5=0)? //如果不支持当前CPU则R5返回值为0
beq __error_p @ yes, error 'p' //如果r5 = 0则打印错误
bl __lookup_machine_type @ r5=machinfo //内核是否支持该单板
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
bl __create_page_tables //建立一级页表
ldr r13, __switch_data @ address to jump to after //设置跳转地址,mmu被使能之后会跳到该地址出(现在还不会过去的)
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC
①在内核映像中,定义了若干proc_info_list表示它支持的CPU,对于ARM架构的CPU,这些结构的源码在arch/arm/mm/目录下。比如proc-v6.S中有如下代码,它表示的所有ARMv6架构的CPU(s3c6410是ARMv6架构的)的proc_info_list结构。
不同的proc_info_list结构被用来支持不同的CPU,它们都是定义在“.proc.info.init”段中,在连接内核时,这些结构被组织在一起。开始地址为__proc_info_begin,结束地址为__proc_info_end。在arm/kernel/vmlinux.lds.S中可以看到这样的代码:
__proc_info_begin = .; /* proc_info_list结构的开始地址*/
*(.proc.info.init)
__proc_info_end = .; /*proc_info_list结构的结束地址*/
__lookup_processor_type函数就是根据前面从协处理器CP15的寄存器C0中读取到CPU
ID(存入r9寄存器),从这些proc_info_list结构中找出匹配。
__lookup_processor_type函数首先将标号3的实际地址加载到r3,然后将编译时生成的
__proc_info_begin虚拟地址载入到r5,__proc_info_end虚拟地址载入到r6,标号3的虚拟地址载入到r7。由于r3和r7分别存储的是同一位置标号3的物理地址和虚拟地址,所以儿者相减即得到虚拟地址和物理地址之间的offset。利用此offset,将r5和r6中保存的虚拟地址转变为物理地址(此时MMU没有开启,无法用编译连接的虚拟地址)然后从proc_info中读出内核编译时写入的processor
ID和之前从cpsr中读到的processor ID对比,查看代码和CPU硬件是否匹配。如果编译了多种处理器支持则会循环每种type依次检验,如果硬件读出的ID在内核中找不到匹配,则r5置0返回。
②不同的machine_desc结构被用来支持不同的开发板,它们都是定义在“.arch.info.init”段中,在连接内核时,这些结构被组织在一起。开始地址为__arch_info_begin,结束地址为__arch_info_end
=。在arm/kernel/vmlinux.lds.S中可以看到这样的代码:
__arch_info_begin = .; /* machine_desc结构的开始地址*/
*(.arch.info.init)
__arch_info_end = .; /*machine_desc结构的结束地址*/
U-Boot调用内核时,会在r1寄存器中给出开发板的标记即机器类型ID。__lookup_machine_type函数将这个值与machine_desc结构的nr成员比较,如果两者相等则表示找到匹配的machine_desc结构,于是返回它的地址(存到r5中)。如果__arch_info_begin、__arch_info_end之间所有的machine_desc结构的nr成员都不等于r1寄存器中的值,则返回0,即r5中值为0
。编码方法与__lookup_processor_type函数完全一样,在此不再详述。
③bl __create_page_tables
__create_page_tables:函数在arch/arm/kernel/head.S中实现,它完成一级页表的创建。代码如下:
/*首先将内核起始地址-0x4000到内核起始地址之间的16K存储器清0*/
mov r0, r4
mov r3, #0
add r6, r0, #0x4000
1: str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r6
bne 1b
/*将proc_info中的mmu_flags加载到r7*/
ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
/*将PC指针右移20位,得到内核第一个1MB空间的段地址存入r6,接着根据此值存入映射标识*/
mov r6, pc, lsr #20 @ start of kernel section
orr r3, r7, r6, lsl #20 @ flags + kernel base
str r3, [r4, r6, lsl #2] @ identity mapping
④__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long __data_start @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
.long cr_alignment @ r7
.long init_thread_union + THREAD_START_SP @ sp
设置lr寄存器值为__enable_mmu,执行完函数__v6_setup,函数会跳转到__enable_mmu地址处。
⑤add pc, r10, #PROCINFO_INITFUNC
执行这条指令后,程序将会跳转到函数__v6_setup处执行。__v6_setup是在文件arch/arm/mm/proc-v6.S中实现的,它的初始化
TLB, Caches, 和 MMU状态,为下一步开启MMU作准备。然后将原来保存在lr中的地址载入pc(_v6_setup最后一句的作用),跳转到arch/arm/kerne/
head.S的__enable_mmu处执行,代码不再详述。
⑥在开启MMU之前还要做好最后的准备工作,比如无效Icaches、Dcaches,设置域访问控制器和页表指针等。这些设置是在arch/arm/kerne/
head.S文件中__enable_mmu函数中实现的.
__enable_mmu:
#ifdef CONFIG_ALIGNMENT_TRAP /*开启地址对齐检查*/
orr r0, r0, #CR_A
#else
bic r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE /*无效Dcaches*/
bic r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
bic r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE/*无效Icaches*/
bic r0, r0, #CR_I
#endif
mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) |
domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) |
domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) |
domain_val(DOMAIN_IO, DOMAIN_CLIENT))
mcr p15, 0, r5, c3, c0, 0 /*设置域访问控制器*/
mcr p15, 0, r4, c2, c0, 0 /*设置页表基址寄存器*/
b __turn_mmu_on
⑦当__enable_mmu执行完后,跳转到__turn_mmu_on,该函数会把MMU使能位写入MMU控制寄存器,使能MMU激活虚拟地址。然后将原来保存在sp中的地址载入pc,跳转到head-common.S的__mmap_switched,至此代码进入虚拟地址的世界。
.align 5
.type __turn_mmu_on, %function
__turn_mmu_on:
mov r0, r0
mcr p15, 0, r0, c1, c0, 0 /*使能MMU*/
mrc p15, 0, r3, c0, c0, 0
mov r3, r3
mov r3, r3
mov pc, r13 /*跳转到__mmap_switched*/
(跳到switch_data了)
⑧在跳转到第二阶段代码的C入口点之前,需要做一些准备工作。包括复制数据段、清除BSS段、保存一些重要的信息到寄存器和内存中。这些都在函数__mmap_switched中实现。这里只粘贴部分代码:
__mmap_switched:
.type __mmap_switched, %function
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 /*复制数据段*/
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 /*清除BSS段*/
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, r7, sp}
str r9, [r4] /* processor ID保存在r9*/
str r1, [r5] /* machine ID报存在r1*/
str r2, [r6] /* atags地址保存在r2*/
bic r4, r0, #CR_A /*Clear 'A' bit*/
stmia r7, {r0, r4} /*将控制寄存器保存到r7定义的内存地址*/
然后再接下来跳入/init/main.c的start_kernel函数,它是内核第二阶段代码的入口点。
bstart_kernel /*调用start_kernel函数*/