linux内核vmlinux启动过程中是如何调用到__arm920_setup函数的
看完韦老师第一期视频中的内核分析第一阶段后,感觉自己对内核不再那么恐惧了。韦老师在视频中对内核的分析,一句句将我们载入内核这个神秘而又美丽的世界。首先说一下内核的任务。
正如韦东山老师在所说的那样,我们辛辛苦苦移植U-BOOT,干了这么多工作,其实我们的主要目的就是能够运行内核。如果你们对UBOOT的启动过程还不清楚,那就强烈建议你们看看韦东山老师的第一期视频课程,里面对UBOOT所做的分析堪称完美中的经典之作。UBOOT移植好后,就要启动内核,别忘记UBOOT启动内核之前传给内核两个参数:1、机器ID;2、在UBOOT中专门跳出一块内存,在这块内存中设置了打算传递给内核的一些参数。UBOOT 将这块内存的地址传递给内核。
那么理所当然,内核就要处理UBOOT 传递过来的参数吧。大体就是这样子的,不明白的话,还是那句话转进去看看韦东山老师的视频教程,那里面有详细的分析。
下面就是我在学习韦东山老师教程中时遇到的一点迷惑,在此记录了下来!希望也能帮助你们解决疑惑!
二、linux内核vmlinux启动过程中是如何调用到__arm920_setup函数的(对应arch/arm/kernal/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
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
bl __create_page_tables
/*
* The following calls CPU specific code in a position independent
* manner. See arch/arm/mm/proc-*.S for details. r10 = base of
* xxx_proc_info structure selected by __lookup_machine_type
* above. On return, the CPU will be ready for the MMU to be
* turned on, and r0 will hold the CPU control register value.
*/
ldr r13, __switch_data @ address to jump to after 1
@ mmu has been enabled 2
adr lr, __enable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC 3
前面的调用过程暂且不分析,网上的资料有很多,从红色部分开始分析:
1中将__switcn_data标号地址放入到寄存器r13中(即SP中),2中将__enable_mmu标号地址放入lr中,重点是第3句,
首先得知道PROCINFO_INITFUNC它代表的是什么含义:在Source Insight中搜索PROCINFO_INITFUNC得到结果如下:
file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\ksohtml\wps163.tmp.png
进入第一个,发现有如下定义:
DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));
这句话可以理解为将PROCINFO_INITFUNC定义为结构体成员__cpu_flush在结构体proc_info_list中的偏移量。
接下来:先搞明白结构体都有哪些成员变量
点击proc_info_list进入结构体proc_info_list的C语言定义如下:
struct proc_info_list {
unsigned int cpu_val;
unsigned int cpu_mask;这两个不就是__lookup_processor_type函数中用到的VAL与mask么!
unsigned long __cpu_mm_mmu_flags; /* used by head.S */
unsigned long __cpu_io_mmu_flags; /* used by head.S */
unsigned long __cpu_flush; /* used by head.S */
const char *arch_name;
const char *elf_name;
unsigned int elf_hwcap;
const char *cpu_name;
struct processor *proc;
struct cpu_tlb_fns *tlb;
struct cpu_user_fns *user;
struct cpu_cache_fns *cache;
};
这时咱们得弄明白proc_info_list这个结构体中每个成员的含义了,其实这个结构体就是__lookup_processor_type函数中搜索的基本单位,每一种处理器都对应这么一个结构体,对应连接脚本中的
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
那么咱们得找到咱们自己板子对应处理器型号(我的是ARM920T)的这个结构体在哪里填充的呢?
在Source Insight中搜索.proc.info.init,进入下面这个条目:proc-arm920.S (arch\arm\mm):448: .section ".proc.info.init", #alloc, #execinstr(因为我的处理器是arm920的,如果你的是别的类的,找到对应的项进入即可),得到结构体proc_info_list汇编语言定义如下:
.section ".proc.info.init", #alloc, #execinstr
.type __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
.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
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
.long cpu_arm920_name
.long arm920_processor_functions
.long v4wbi_tlb_fns
.long v4wb_user_fns
#ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
.long arm920_cache_fns
#else
.long v4wt_cache_fns
#endif
.size __arm920_proc_info, . - __arm920_proc_info
可以理解为上述汇编代码对应的每一项在编译连接时就被填充到对应的C语言定义的结构体成员中,最后以这种结构体为单位把每一种型号所对应的结构体都放到连接脚本
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
所定义的地方。
好了,分析到这里想必大家都能明白过来PROCINFO_INITFUNC这个东西代表的什么含义了,它就是结构体proc_info_list 中第五个成员变量,对应汇编代码,那就是b __arm920_setup这句话。
第2句话add pc, r10, #PROCINFO_INITFUNC中r10中存放的是__lookup_processor_type函数找到对应的处理器型号后返回的在段
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
中的地址。其实返回的就是上述分析总舵处理器型号对应的结构体信息中的的一个结构体的地址。
对应JZ2440也就是我这个开发板的处理器型号arm920所对应的结构体在段中的位置。
add pc, r10,#PROCINFO_INITFUNC
进而这句话就是调用b __arm920_setup了。
进入到标号__arm920_setup处。
__arm920_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
#endif
adr r5, arm920_crval
ldmia r5, {r5, r6}
mrc p15, 0, r0, c1, c0 @ get control register v4
bic r0, r0, r5
orr r0, r0, r6
mov pc, lr
__arm920_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
#endif
adr r5, arm920_crval
ldmia r5, {r5, r6}
mrc p15, 0, r0, c1, c0 @ get control register v4
bic r0, r0, r5
orr r0, r0, r6
mov pc, lr
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
#endif
adr r5, arm920_crval
ldmia r5, {r5, r6}
mrc p15, 0, r0, c1, c0 @ get control register v4
bic r0, r0, r5
orr r0, r0, r6
mov pc, lr
最后的这句话(红色部分对应)就是返回到LR所指的地方继续运行,lr中方的是什么呢?
第3句adr lr, __enable_mmu @ return (PIC) address 3中不做好准备了么,此时程序就跳转到__enable_mmu处继续执行。
找到标号__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
bic r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
bic r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
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 @ load domain access register
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer
b __turn_mmu_on
/*
* Enable the MMU. This completely changes the structure of the visible
* memory space. You will not be able to trace execution through this.
* If you have an enquiry about this, *please* check the linux-arm-kernel
* mailing list archives BEFORE sending another post to the list.
*
* r0 = cp#15 control register
* r13 = *virtual* address to jump to upon completion
*
* other registers depend on the function called upon completion
*/
.align 5
.type __turn_mmu_on, %function
__turn_mmu_on:
mov r0, r0
mcr p15, 0, r0, c1, c0, 0 @ write control reg
mrc p15, 0, r3, c0, c0, 0 @ read id reg
mov r3, r3
mov r3, r3
mov pc, r13
红色部分的含义就是程序跳转到r13所指的地方继续运行。r3指到哪里去了呢,第1句ldr r13, __switch_data @ address to jump to after 1 中不早就准备好了么,r13指向了__switch_data处。
于是程序跳转到__switch_data处继续执行。
找到__switch_data标号处:
__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 cr_alignment @ r6
.long init_thread_union + THREAD_START_SP @ sp
/*
* The following fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses; this is not position independent.
*
* r0 = cp#15 control register
* r1 = machine ID
* r9 = processor ID
*/
.type __mmap_switched, %function
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, sp}
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r6, {r0, r4} @ Save control register values
b start_kernel
发现程序在执行__mmap_switched后最终调用到了start_kernel函数,开始了C语言的阶段。
大体调用过程应该是这个样子的,希望对你们理解内核的启动过程有所帮助!个别地方描述的有点不准确,希望多多指正,共同探讨,现在才感觉到把自己的思想描述出来是多么困难的一件事了,在此真心佩服韦东山老师了,不仅技术这么牛,还能写出《嵌入式Linux应用开发完全手册》这么好的书,我们本科学校发给学生的课本就是他的这本书。