转载请注明出处:青核桃的个人博客:
gnssinfo.com
嵌入式linux内核启动:汇编时代(1)
早先也做过一些嵌入式LINUX的项目,改改驱动、增加一些功能等,但未完整重新定义过系统,对嵌入式LINUX只能算是比较熟悉,不能做过信手摘来即用,这次刚好有些时间,也在做新项目,就一起对整个过程再深入一点、OK? 青核桃,加油,相信这是可以完成的。
嗯,从启动开始吧!(BTW:这里用的是AT91SAM9X35的CPU做入口,在内核之前还有bootstrap与u-boot,笔者已重定制了一个版本,是从spi-flash启动的,用SPI0-NPCS0,更多请参照:
AT91SAM9x5ek boot模式)
注:笔者用的内核版本为:2.6.39
当uBoot将CPU的控制权交给内核后,第一个执行的是Head.S, 它完成了加载内核的大部分工作;针对ARM版,head.S存放在目录:arch/arm/boot/compressed/head.S,这里笔者对其作简要的分析:
//批注:撇开head.S头的版本声明不提,// 文中先放了几个ASM汇编的宏,符合宏定义,以.macro开头,.endm结尾// 代码从.section开始执行,启动段定义为.start,以ARM格式执行,
.section “.start”, #alloc, #execinstr
/*
* sort out different calling conventions
*/
.align
.arm @ Always enter in ARM state
start:
.type start,#function
.rept 8
//批注:定义Magic Number、zImage的起止地址// 打开Cache
…… …… …… …… …… …… …… ……
// 中间省略若干,从这里继续,这里将开始解压内核喽,有一个重要问题:内核是在原地址解压,还是解压到另一个地址运行?// 即Head.S中的delta。
wont_overwrite:
/*
* If delta is zero, we are running at the address we were linked at.
* r0 = delta
* r2 = BSS start
* r3 = BSS end
* r4 = kernel execution address
* r7 = architecture ID
* r8 = atags pointer
* r11 = GOT start
* r12 = GOT end
* sp = stack pointer
*/
…… …… …… …… …… …… …… ……
// 中间省略若干,从这里继续,这里将开始解压内核喽
/*
* The C runtime environment should now be setup sufficiently.
* Set up some pointers, and start decompressing.
* r4 = kernel execution address
* r7 = architecture ID
* r8 = atags pointer
*/
mov r0, r4
mov r1, sp @ malloc space above stack
add r2, sp, #0×10000 @ 64k max
mov r3, r7
bl decompress_kernel
//调用misc.c中的解压函数
bl cache_clean_flush
bl cache_off
mov r0, #0 @ must be zero
mov r1, r7 @ restore architecture number
mov r2, r8 @ restore atags pointer
mov pc, r4 @ call kernel //此处跳转到解压后的地址执行
//?? 解压后跳转到哪了呢?以91SAMx5为例,附一段启动输出来解释,
## Booting kernel from Legacy Image at 22000000 …
Image Name: linux-2.6
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1407128 Bytes = 1.3 MiB
Load Address: 20008000
Entry Point: 20008000
Verifying Checksum … OK
Loading Kernel Image … OK
OK
从上边的输出,可以看到,这里笔者将内核的压缩文件存放在内存0×22000000处,解压后至0×20800000,所以这里跳转至0×20800000执行。
此时,从arch/arm/kernel/head.S开始启动,如下:
/*
* Kernel startup entry point.
* —————————
*
该处描述了执行的前提条件,该条件在解压完成后准备,并在跳转前完成。* This is normally called from the decompressor code. The requirements * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0, * r1 = machine nr, r2 = atags pointer. * * This code is mostly position independent, so if you link the kernel at * 0xc0008000, you call this at __pa(0xc0008000).
*
* See linux/arch/arm/tools/mach-types for the complete list of machine
* numbers for r1.
*
* We’re trying to keep crap to a minimum; DO NOT add any machine specific
* crap here – that’s what the boot loader (or in extreme, well justified
* circumstances, zImage) is for.
*/
__HEAD
ENTRY(stext)
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ 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)?
THUMB( it eq ) @ force fixup-able long branch encoding
beq __error_p @ yes, error ‘p’
#ifndef CONFIG_XIP_KERNEL
adr r3, 2f
ldmia r3, {r4, r8}
sub r4, r3, r4 @ (PHYS_OFFSET – PAGE_OFFSET)
add r8, r8, r4 @ PHYS_OFFSET
#else
ldr r8, =PLAT_PHYS_OFFSET
#endif
/*
* r1 = machine no, r2 = atags,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/
bl __vet_atags
#ifdef CONFIG_SMP_ON_UP
bl __fixup_smp
#endif
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
bl __fixup_pv_table
#endif
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_processor_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, =__mmap_switched @ address to jump to after
@ mmu has been enabled
adr lr, BSYM(1f) @ return (PIC) address
ARM( add pc, r10, #PROCINFO_INITFUNC )
THUMB( add r12, r10, #PROCINFO_INITFUNC )
THUMB( mov pc, r12 )
1: b __enable_mmu
ENDPROC(stext)
.ltorg
#ifndef CONFIG_XIP_KERNEL
2: .long .
.long PAGE_OFFSET
#endif
// 最后跳转至__mmap_switched, 其在head-common.S中定义,其详细代码如下:
__mmap_switched:
adr r3, __mmap_switched_data
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
ARM( ldmia r3, {r4, r5, r6, r7, sp})
THUMB( ldmia r3, {r4, r5, r6, r7} )
THUMB( ldr sp, [r3, #16] )
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
bic r4, r0, #CR_A @ Clear ‘A’ bit
stmia r7, {r0, r4} @ Save control register values
b start_kernel
//终于结束汇编,开始进入C内核时代了,新时代开始了,下一篇再续。加油!!
ENDPROC(__mmap_switched)
题尾:汇编结束了,笔记也写了不短的时间,明天继续!
---------华丽的分隔线,以下为misc.c内容----------------
unsigned long
decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
unsigned long free_mem_ptr_end_p,
int arch_id)
{
unsigned char *tmp;
output_data = (unsigned char *)output_start;
free_mem_ptr = free_mem_ptr_p;
free_mem_end_ptr = free_mem_ptr_end_p;
__machine_arch_type = arch_id;
arch_decomp_setup();
tmp = (unsigned char *) (((unsigned long)input_data_end) – 4);
output_ptr = get_unaligned_le32(tmp);
putstr(“Uncompressing Linux…”);
// 这个很熟悉吧,启动时输出的文本,开始解压内核
do_decompress(input_data, input_data_end – input_data,
output_data, error);
putstr(” done, booting the kernel.
”);
//解压完成,开始运行
return output_ptr;
}