有地方看到,启动流程是arch/arm/boot/compressed/head.s ----->调用arch/arm/boot/compressed/misc.c的decompress_kernel()
函数解压内核。---->arch/arm/kernel/head-common.S初始化 ---->init/main.c的asmlinkage void __init start_kernel(void)
注意在arch/arm/kernel/smp.c文件中有一个启动多核处理器的函数 void __init smp_prepare_boot_cpu(void),被init/main.c的asmlinkage void __init start_kernel(void)调用
有人说是ARM Linux的启动代码有两处,一处是经过压缩的,一处是没有经过压缩的,压缩的最终还是会调用没有压缩的,没有压缩的入口在arch/arm/kernel/head.S文件中。看起来是这样的,第二种启动流程是在boot/compressed文件夹中,证实是压缩启动流程.
以32位arm为例,在3.9.7内核版本上分析。
1、 压缩启动流程
/arch/arm/boot/compressed/head.S
1)首先定义了一系列的宏,如writeb、loadsp、kputc、kphex、debug_reloc_start、debug_reloc_end;
2)使用r0、r7、r8分别保存r0、r1、r2的值(分别为0、MachineType、ATAG指针),r9是cpsr的值;
3)r2保存cpsr,判断最后2bit是否0,即判断是否user mode,不是的话则设置为svc模式,并把r9中的cpsr保存到spsr;
4)把最终的kernel文件的地址放在r4中,一种方式是通过pc&0xf8000000 + TEXT_OFFSET;一种方式是zreladdr;TEXT_OFFSET = ??
zreladdr在makefile中定义,但是来龙去脉不清楚。
5)跳转到cache_on
6)adr r0, LC0 //将LC0标签的地址赋给r0,LC0标签处的内容如下图所示
ldmia r0, {r1, r2, r3, r6, r10, r11, r12} //将r0地址开始的7个word数据分别保存到r1, r2, r3, r6, r10, r11, r12中
设置堆栈,是LC0开始第8个word的值
/*
* We might be running at a different address. We need
* to fix up various pointers.
*/
sub r0, r0, r1 @ calculate the delta offset
add r6, r6, r0 @ _edata
add r10, r10, r0 @ inflated kernel size location
计算kernel size存放的地址并保存在r10中
LC0: .word LC0 @ r1
.word __bss_start @ r2
.word _end @ r3
.word _edata @ r6
.word input_data_end - 4 @ r10 (inflated size location)
.word _got_start @ r11
.word _got_end @ ip
.word .L_user_stack_end @ sp
.size LC0, . - LC0
7)从r10开始的地址依次读取4个字节,拼成32bit存放在r9中,即是kernel size(解压缩的大小)的值;
8)分配空间在堆栈之上,堆栈最大64KB(0x10000),将sp + 0x10000的值赋给r10;
9)初始化dtb的大小为0,并保存在r5中;
10)如果有device trees (dtb) appended to zImage,一些处理,暂时先不分析
截止目前,各寄存器的内容如下图所示:
/*
* r0 = delta
* r2 = BSS start
* r3 = BSS end
* r4 = final kernel address
* r5 = appended dtb size (still unknown)
* r6 = _edata
* r7 = architecture ID
* r8 = atags/device tree pointer
* r9 = size of decompressed image
* r10 = end of this image, including bss/stack/malloc space if non XIP
* r11 = GOT start
* r12 = GOT end
* sp = stack pointer
*
* if there are device trees (dtb) appended to zImage, advance r10 so that the
* dtb data will get relocated along with the kernel if necessary.
*/
11)判断是否会地址覆盖,
r10 = r10 + 16KB;
r4 > r10则不会覆盖,否则r10 = r4 + r9,r10 < r9则不会覆盖否则就会覆盖
覆盖时的处理这里先不分析
12)orrs r1, r0, r5 //orr逻辑与,r5为0,当r0及delta也为0,跳转到not_relocated
beq not_relocated //否则就需要对地址进行跳转,跳转部分我们暂时先不看了
13)清零bss段,从r2开始的地址到r3开始的地址
14)解压缩C代码环境准备及解压缩
decompress_kernel(
unsigned long output_start, //(r4中的值,移送到r0)
unsigned long free_mem_ptr_p, //(sp的值,移送到r1)
unsigned long free_mem_ptr_end_p, //(sp值+64KB,移送到r2)
int arch_id //(r7中的值,移送到r3)
);
分别准备4个参数,保存在r0~r3中,然后调用decompress_kernel函数
15)刷cache、关闭cache,分别调用cache_clean_flush、cache_off
16)再把r7、r8中的architecture number、atags pointer恢复到r1、r2中
跳转到__enter_kernel标签,在这里,将r0恢复到0,r4中的解压缩内核地址移送到pc中,跳转到r4中,完成从压缩内核流程向非压缩内核流程转换。
2、非压缩启动流程
代码入口是在arch\arm\kernel\head.s,然后跳转到main.c中的start_kernel。
确保SVC模式和中断关闭 -> 获取cpuid -> 判断ATAG,貌似只判断了ATAG_CORE -> __create_page_tables,创建页表 -> 将__mmap_switched地址保存在r13中 -> 跳转到__enable_mmu,
在__turn_mmu_on的最后跳转到r13中,即跳转到__mmap_switched -> 在__mmap_switched的最后 b start_kernel
详细过程如下:
1) 定义一些宏KERNEL_RAM_VADDR(= PAGE_OFFSET + TEXT_OFFSET,必需32KB对齐)、PG_DIR_SIZE、PMD_ORDER、swapper_pg_dir(= KERNEL_RAM_VADDR - PG_DIR_SIZE,页表的虚拟地址)、pgtbl
2) 确认SVC模式及中断关闭
3) 读取CPUID及判断合法性
R9为cpuid,r5为proc_info结构体指针,并且是物理地址的,并移送到r10中
4) 支持LAPE时判断CPU硬件是否支持
5) 将PHYS_OFFSET放入r8
/*
* r1 = machine no, r2 = atags or dtb,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/
6) __vet_atags:判断ATAG指针的合法性,貌似只判断了ATAG_CORE
7) __fixup_smp:SMP的一些调整
8) __fixup_pv_table:虚实地址的一些调整
9) __create_page_tables:创建和初始化页表,r4为物理页表地址
10) __mmap_switched地址传送到r13中
11) __enable_mmu的地址传送到lr中,
12) r4物理页表地址传送到r8中,
ARM( add pc, r10, #PROCINFO_INITFUNC )
THUMB( add r12, r10, #PROCINFO_INITFUNC )
THUMB( mov pc, r12 )
这几行代码没看懂,PROCINFO_INITFUNC 是一个结构体proc_info_list中 __cpu_flush成员的偏移,应该pc调整到__cpu_flush,但是没找到这个实现
13) __enable_mmu:使能MMU,此后虚拟地址和物理地址不再一模一样,而是有对应关系,这里最后跳转到r13即__mmap_switched中;
14) __mmap_switched:主要功能是将__mmap_switched_data中的数据进行切换,从没有MMU到有MMU地址需要转换;如拷贝data segment、清零BBS、保存CPUID&machineType&AtagPoint等,然后就跳转到start_kernel了
http://linux.chinaunix.net/bbs/thread-1021226-1-1.html中也有详细的描述这段汇编。
总结来说,压缩启动这块代码主要功能是自解压,非压缩启动这块代码主要功能是检查BIOS或者BootLoader传递的3个参数是否有效,以及设置页表打开MMU。