概述
本平台采用的是高通apq8009 arm平台,linux内核版本3.18,采用设备树方式。
linux系统启动过程从软件方面看可分为:bootloader,linux内核,文件系统和应用程序。
设备是以emmc方式启动的, 烧写文件都烧写到emmc中。上电后读取emmc,emmc被分了很多区,这里会有一个分区信息描述,就像x86 windows的mbr, 启动时候可确定bootloader在emmc中位置,开始启动bootloader。
bootloader是引导加载程序, uboot是常用的bootloader,它有很多功能,初始化处理器,调试口,有些还会初始化usb,以太网,便于数据传输;uboot还得支持设备树方式;uboot可以通过帮助命令,查看支持哪些功能,本平台就可以支持fastboot功能,bootcmd中是对内核影响的参数;
进入linux内核阶段,解析设备树, 根据设备树信息的的描述,内核匹配了apq8009 arm平台, 初始化,加载驱动,最后内核会启动一个init进程。它是Linux系统中的1号进程(Linux系统没有0号进程)。到此,完成了内核启动阶段的工作,交接给init来管理。
init进程运行一系列的脚本(startup scripts),可到etc/下查看,这些脚本功能可检测文件系统,挂载硬盘,设置网络,运行各种应用程序, 等等。
启动完以后,命令行方式会显示login, 把权限交给用户,然后linux系统旅程就开始了。
映像文件介绍
内核编译后会产生映像文件,映像文件类型分为压缩和非压缩方式。这主要是嵌入式系统存储器容量小,对容量有严格要求的,一般采用压缩式映像文件,内核启动用时间换空间。这两种方式,内核启动时是不同的,压缩式启动的时候,需要解压映像文件。
压缩式映像文件:uImage和zImage。
非压缩方式映像文件:Image, 由elf格式vmlinux转换而来。
vmlinux属于未压缩,带调试信息、符号表
Image是由objcopy工具去除vmlinux调试信息、注释、符号表等内容。
uImage可由工具mkimage 把zImage和64字节的头信息生成uImage。头信息描述映像文件的类型、加载位置、生成时间、大小等信息。
熟悉了zImage生成过程,才能更容易明白内核是怎么启动起来的。linux内核映像文件生成如下图:
生成带设备树的内核,命令格式: cat zImage xxx.dtb > zImage-dtb
ARM GCC 内嵌(inline)汇编手册
可直接看网址:http://www.ethernut.de/en/documents/arm-inline-asm.html
摘录网址中内容,gcc 中ARM寄存器使用:
Register |
Alt. Name |
Usage |
r0 |
a1 |
First function argument Integer function result Scratch register |
r1 |
a2 |
Second function argument Scratch register |
r2 |
a3 |
Third function argument Scratch register |
r3 |
a4 |
Fourth function argument Scratch register |
r4 |
v1 |
Register variable |
r5 |
v2 |
Register variable |
r6 |
v3 |
Register variable |
r7 |
v4 |
Register variable |
r8 |
v5 |
Register variable |
r9 |
v6 rfp |
Register variable Real frame pointer |
r10 |
sl |
Stack limit |
r11 |
fp |
Argument pointer |
r12 |
ip |
Temporary workspace |
r13 |
sp |
Stack pointer |
r14 |
lr |
Link register Workspace |
r15 |
pc |
Program counter |
理解了这些后,看内核中汇编代码就容易多了。
linux内核启动
- 内核启动第一阶段 --- 解压内核
该平台是arm系列处理器,入口程序路径为arch/arm/;对于非arm平台, 比如x86, 入口程序路径为arch/x86/。
用的是压缩式内核zImage,zImage的入口程序即为 arch/arm/boot/compressed/head.S。
解压准备阶段将执行中断禁用、分配动态内存、初始化BBS区域、初始化页目录、打开缓存等任务。
对软硬件进行初始化完成后,开是执行任务start任务,见下方代码:
- start:
- .type start,#function
- .rept 7
- mov r0, r0
- .endr
- ARM( mov r0, r0 )
- ARM( b 1f )
- THUMB( adr r12, BSYM(1f) )
- THUMB( bx r12 )
-
- .word _magic_sig @ Magic numbers to help the loader
- .word _magic_start @ absolute load/run zImage address
- .word _magic_end @ zImage end address
- .word 0x04030201 @ endianness flag
-
- THUMB( .thumb )
- 1:
- ARM_BE8( setend be ) @ go BE8 if compiled for BE8
- mrs r9, cpsr
- #ifdef CONFIG_ARM_VIRT_EXT
- bl __hyp_stub_install @ get into SVC mode, reversibly
- #endif
- mov r7, r1 @ save architecture ID
- mov r8, r2 @ save atags pointer
-
- /*
- * Booting from Angel - need to enter SVC mode and disable
- * FIQs/IRQs (numeric definitions from angel arm.h source).
- * We only do this if we were in user mode on entry.
- */
- mrs r2, cpsr @ get current mode
- tst r2, #3 @ not user?
- bne not_angel
- mov r0, #0x17 @ angel_SWIreason_EnterSVC
- ARM( swi 0x123456 ) @ angel_SWI_ARM
- THUMB( svc 0xab ) @ angel_SWI_THUMB
- not_angel:
- safe_svcmode_maskall r0
- msr spsr_cxsf, r9 @ Save the CPU boot mode in
- @ SPSR
- /*
- * Note that some cache flushing and other stuff may
- * be needed here - is there an Angel SWI call for this?
- */
-
- /*
- * some architecture specific code can be inserted
- * by the linker here, but it should preserve r7, r8, and r9.
- */
-
- .text
-
- #ifdef CONFIG_AUTO_ZRELADDR
- @ determine final kernel image address
- mov r4, pc
- and r4, r4, #0xf8000000
- add r4, r4, #TEXT_OFFSET
- #else
- ldr r4, =zreladdr
- #endif
-
- /*
- * Set up a page table only if it won't overwrite ourself.
- * That means r4 < pc && r4 - 16k page directory > &_end.
- * Given that r4 > &_end is most unfrequent, we add a rough
- * additional 1MB of room for a possible appended DTB.
- */
- mov r0, pc
- cmp r0, r4
- ldrcc r0, LC0+32
- addcc r0, r0, pc
- cmpcc r4, r0
- orrcc r4, r4, #1 @ remember we skipped cache_on
- blcs cache_on
行23是保存体系结构ID,行24是保存atags指针。34行开始输入SVC模式,禁用FIQs/IRQs。
行68开始,给DTB留空间,记住跳开cache_on 。
- cache_on: mov r3, #8 @ cache_on function
- b call_cache_fn
-
- call_cache_fn: adr r12, proc_types
- #ifdef CONFIG_CPU_CP15
- mrc p15, 0, r9, c0, c0 @ get processor ID
- #else
- ldr r9, =CONFIG_PROCESSOR_ID
- #endif
- 1: ldr r1, [r12, #0] @ get value
- ldr r2, [r12, #4] @ get mask
- eor r1, r1, r9 @ (real ^ match)
- tst r1, r2 @ & mask
- ARM( addeq pc, r12, r3 ) @ call cache function
- THUMB( addeq r12, r3 )
- THUMB( moveq pc, r12 ) @ call cache function
- add r12, r12, #PROC_ENTRY_SIZE
- b 1b
-
- /*
- * Table for cache operations. This is basically:
- * - CPU ID match
- * - CPU ID mask
- * - 'cache on' method instruction
- * - 'cache off' method instruction
- * - 'cache flush' method instruction
- *
- * We match an entry using: ((real_id ^ match) & mask) == 0
- *
- * Writethrough caches generally only need 'on' and 'off'
- * methods. Writeback caches _must_ have the flush method
- * defined.
- */
- .align 2
- .type proc_types,#object
- proc_types:
- .word 0x41000000 @ old ARM ID
- .word 0xff00f000
- mov pc, lr
- THUMB( nop )
- mov pc, lr
- THUMB( nop )
- mov pc, lr
- THUMB( nop )
-
- .word 0x41007000 @ ARM7/710
- .word 0xfff8fe00
- mov pc, lr
- THUMB( nop )
- mov pc, lr
- THUMB( nop )
- mov pc, lr
- THUMB( nop )
-
- .word 0x41807200 @ ARM720T (writethrough)
- .word 0xffffff00
- W(b) __armv4_mmu_cache_on
- W(b) __armv4_mmu_cache_off
- mov pc, lr
- THUMB( nop )
-
- .word 0x41007400 @ ARM74x
- .word 0xff00ff00
- W(b) __armv3_mpu_cache_on
- W(b) __armv3_mpu_cache_off
- W(b) __armv3_mpu_cache_flush
-
- .word 0x41009400 @ ARM94x
- .word 0xff00ff00
- W(b) __armv4_mpu_cache_on
- W(b) __armv4_mpu_cache_off
- W(b) __armv4_mpu_cache_flush
-
- .word 0x41069260 @ ARM926EJ-S (v5TEJ)
- .word 0xff0ffff0
- W(b) __arm926ejs_mmu_cache_on
- W(b) __armv4_mmu_cache_off
- W(b) __armv5tej_mmu_cache_flush
-
- .word 0x00007000 @ ARM7 IDs
- .word 0x0000f000
- mov pc, lr
- THUMB( nop )
- mov pc, lr
- THUMB( nop )
- mov pc, lr
- THUMB( nop )
-
- @ Everything from here on will be the new ID system.
-
- .word 0x4401a100 @ sa110 / sa1100
- .word 0xffffffe0
- W(b) __armv4_mmu_cache_on
- W(b) __armv4_mmu_cache_off
- W(b) __armv4_mmu_cache_flush
-
- .word 0x6901b110 @ sa1110
- .word 0xfffffff0
- W(b) __armv4_mmu_cache_on
- W(b) __armv4_mmu_cache_off
- W(b) __armv4_mmu_cache_flush
-
- .word 0x56056900
- .word 0xffffff00 @ PXA9xx
- W(b) __armv4_mmu_cache_on
- W(b) __armv4_mmu_cache_off
- W(b) __armv4_mmu_cache_flush
-
- .word 0x56158000 @ PXA168
- .word 0xfffff000
- W(b) __armv4_mmu_cache_on
- W(b) __armv4_mmu_cache_off
- W(b) __armv5tej_mmu_cache_flush
-
- .word 0x56050000 @ Feroceon
- .word 0xff0f0000
- W(b) __armv4_mmu_cache_on
- W(b) __armv4_mmu_cache_off
- W(b) __armv5tej_mmu_cache_flush
-
- #ifdef CONFIG_CPU_FEROCEON_OLD_ID
- /* this conflicts with the standard ARMv5TE entry */
- .long 0x41009260 @ Old Feroceon
- .long 0xff00fff0
- b __armv4_mmu_cache_on
- b __armv4_mmu_cache_off
- b __armv5tej_mmu_cache_flush
- #endif
-
- .word 0x66015261 @ FA526
- .word 0xff01fff1
- W(b) __fa526_cache_on
- W(b) __armv4_mmu_cache_off
- W(b) __fa526_cache_flush
-
- @ These match on the architecture ID
-
- .word 0x00020000 @ ARMv4T
- .word 0x000f0000
- W(b) __armv4_mmu_cache_on
- W(b) __armv4_mmu_cache_off
- W(b) __armv4_mmu_cache_flush
-
- .word 0x00050000 @ ARMv5TE
- .word 0x000f0000
- W(b) __armv4_mmu_cache_on
- W(b) __armv4_mmu_cache_off
- W(b) __armv4_mmu_cache_flush
-
- .word 0x00060000 @ ARMv5TEJ
- .word 0x000f0000
- W(b) __armv4_mmu_cache_on
- W(b) __armv4_mmu_cache_off
- W(b) __armv5tej_mmu_cache_flush
-
- .word 0x0007b000 @ ARMv6
- .word 0x000ff000
- W(b) __armv6_mmu_cache_on
- W(b) __armv4_mmu_cache_off
- W(b) __armv6_mmu_cache_flush
-
- .word 0x000f0000 @ new CPU Id
- .word 0x000f0000
- W(b) __armv7_mmu_cache_on
- W(b) __armv7_mmu_cache_off
- W(b) __armv7_mmu_cache_flush
-
- .word 0 @ unrecognised type
- .word 0
- mov pc, lr
- THUMB( nop )
- mov pc, lr
- THUMB( nop )
- mov pc, lr
- THUMB( nop )
-
- .size proc_types, . - proc_types
-
- /*
- * If you get a "non-constant expression in ".if" statement"
- * error from the assembler on this line, check that you have
- * not accidentally written a "b" instruction where you should
- * have written W(b).
- */
- .if (. - proc_types) % PROC_ENTRY_SIZE != 0
- .error "The size of one or more proc_types entries is wrong."
- .endif
-
- /*
- * Turn off the Cache and MMU. ARMv3 does not support
- * reading the control register, but ARMv4 does.
- *
- * On exit,
- * r0, r1, r2, r3, r9, r12 corrupted
- * This routine must preserve:
- * r4, r7, r8
- */
- .align 5
行2,把常数8写入寄存器r3中并跳转到call_cache_fn;
打开汇编程序arch/arm/kernel/head.S,head.S依次完成:开启 MMU 和 cache,调用 decompress_kernel()解压内核,最后通过调用 call_kernel()进入非压缩内核 Image 的启动。下面将具体分析在此之后 Linux 内核的启动过程。