1、基于ARM的Linux的启动分析 基于ARM 的Linux 的启动分析 说明: 本文主要的分析基于ARM的linux2.6.17内核启动过程分析。 1、Linux 内核的启动方案: 由arch/arm/Makefile的代码: define archhelp echo * zImage - Compressed kernel image (arch/$(ARCH)/boot/zImage) echo Image - Uncompressed kernel image (arch/$(ARCH)/boot/Image) echo * xipImage - XIP kernel image, if
2、 configured (arch/$(ARCH)/boot/xipImage) echo bootpImage - Combined zImage and initial RAM disk echo (supply initrd image via make variable INITRD=) echo install - Install uncompressed kernel echo zinstall - Install compressed kernel echo Install using (your) /bin/installkernel or echo (distribution
3、) /sbin/installkernel or echo install to $(INSTALL_PATH) and run lilo endef 可以看出,主要有三种启动方案,分别是: echo * zImage - Compressed kernel image (arch/$ (ARCH)/boot/zImage) echo Image - Uncompressed kernel image (arch/$ (ARCH)/boot/Image) echo bootpImage - Combined zImage and initial RAM disk echo (supply in
4、itrd image via make variable INITRD=)。Linux内核有两种映像:一种是非压缩内核,叫 Image,另一种是它的压缩版本,叫zImage。根据内核映像的不同,Linux内核的启动在开始阶段也有所不同。zImage是Image经过压缩形成的,所以它的大小比 Image小。但为了能使用zImage,必须在它的开头加上解压缩的代码,将 zImage解压缩之后才能执行,因此它的执行速度比Image要慢。但考虑到嵌入式系统的存储空容量一般比较小,采用zImage可以占用较少的存储空间,因此牺牲一点性能上的代价也是值得的。所以一般的嵌入式系统均采用压缩内核的方式(另外b
5、ootpImage 是编译包含zImage和initrd的映像,可以通过make变量INITRD=提供initrd映像)2.典型启动方案的代码结构:(1).Image启动方案的代码结构: #arch/arm/boot/Makefile: $(obj)/Image: vmlinux FORCE $(call if_changed,objcopy) echo Kernel: $ is ready 由此可见,Image由vmlinux 二进制化得到的。 Image的是由kernel/vmlinux构成的。(2).zImage启动方案的代码结构:在内核编译完成后会在arch/arm/boot/下生成z
6、Image。#arch/arm/boot/Makefile:$(obj)/zImage: $(obj)/compressed/vmlinux FORCE$(call if_changed,objcopy)echo Kernel: $ is ready由此可见,zImage由内核顶层目录下的arch/arm/boot /compressed/vmlinux二进制化得到的:#arch/armboot/compressed/Makefile:$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o $(addprefix $(o
7、bj)/, $(OBJS) FORCE$(call if_changed,ld):$(obj)/piggy.gz: $(obj)/./Image FORCE$(call if_changed,gzip)$(obj)/piggy.o: $(obj)/piggy.gz FORCEzImage 的组成,它是由一个压缩后的内核piggy.o,连接上一段初始化及解压功能的代码(head.o misc.o)组成的。 (3)BootpImage启动方案的代码结构: #arch/arm/boot/Makefile: $(obj)/bootp/bootp: $(obj)/zImage initrd FORCE
8、$(Q)$(MAKE) $(build)=$(obj)/bootp $ $(obj)/bootpImage: $(obj)/bootp/bootp FORCE $(call if_changed,objcopy) echo Kernel: $ is ready 由此可知bootpImage是由 zImage 与initrd经过二进制化得到的,所以他包含了zImage initrd。 压缩内核zImage启动方案的分析 通常从系统加电到执行到linux kenel这部分的任务是由boot loader来完成。关于这部分内容在这里不不多分析了这里主要分析zImage部分的启动过程。 1. Linu
9、x 内核的一般启动过程:1)对于ARM 系列处理器来说,zImage 的入口程序即为 arch/arm/boot/compressed/head.S。它依次完成以下工作:开启 MMU 和 Cache,调用decompress_kernel()解压内核,最后通过调用 call_kernel()进入非压缩内核Image 的启动。Linux 非压缩内核的入口位于文件/arch/arm/kernel/head-armv.S 中的 stext 段。该段的基地址就是压缩内核解压后的跳转地址。如果系统中加载的内核是非压缩的 Image,那么bootloader将内核从 Flash中拷贝到 RAM 后将直接跳
10、到该地址处,从而启动 Linux 内核。2)执行镜像:解压後/非压缩镜像直接执行(linux/arch/arm/kernel/headarmv.S:ENTRY(stext)- _entry-_ret-_switch_data-_mmap_switched-)3)该程序通过查找处理器内核类型和处理器类型调用相应的初始化函数,再建立页表,最后跳转到 start_kernel()函数开始内核的初始化工作。(linux/init/main.c:start_kernel())2、zImage 的启动过程1) 内核启动地址的确定1、#/arch/arm/Makefile文件中,设置内核启动的虚拟地址tex
11、taddr-y := 0xC0008000 这个是内核启动的虚拟地址TEXTADDR := $(textaddr-y)2、#/arch/arm/boot/Makefile文件中,设置内核启动的物理地址ZRELADDR := $(zreladdr-y)PARAMS_PHYS := $(params_phys-y)3、 #/arch/arm/boot/compressed/Makefile文件中,SEDFLAGS =s/TEXT_START/$(ZTEXTADDR)/;s/LOAD_ADDR/$(ZRELADDR)/;s/BSS_START/$(ZBSSADDR)/使得TEXT_START = Z
12、TEXTADDR(从flash 中启动时),LOAD_ADDR =ZRELADDR其中TEXT_START是内核ram启动的偏移地址,这个地址是物理地址ZTEXTADDR就是解压缩代码的ram偏移地址,LOAD_ADDR就是zImage中解压缩代码的ram偏移地址,ZRELADDR是内核ram启动的偏移地址,zImage的入口点由# /arch/arm/boot/compressed/vmlinux.lds.in决定:OUTPUT_ARCH(arm)ENTRY(_start)SECTIONS. = TEXT_START;_text = .;.text : _start = .;*(.start
13、)*(.text)2) 内核解压缩过程内核压缩和解压缩代码都在目录#/arch/arm/boot/compressed,编译完成后将产生vmlinux、head.o、misc.o、head-xscale.o、piggy.o这几个文件,其中head.o:内核的头部文件,负责初始设置;misc.o:主要负责内核的解压工作,它在head.o之后;head-xscale.o:主要针对Xscale的初始化,将在链接时与head.o合并;piggy.o:一个中间文件,其实是一个压缩的内核(kernel/vmlinux),只不过没有和初始化文件及解压文件链接而已;vmlinux:没有(zImage是压缩过的
14、内核)压缩过的内核,就是由piggy.o、head.o、misc.o、head-xscale.o组成的。3) 在BootLoader 完成系统的引导以后并将Linux 内核调入内存之后,调用bootLinux(),这个函数将跳转到kernel的起始位置。如果kernel没有压缩,就可以启动了。如果kernel压缩过,则要进行解压,在压缩过的kernel头部有解压程序。压缩过的kernel入口第一个文件源码位置arch/arm/boot/compressed/head.S。它将调用函数decompress_kernel(),这个函数在arch/arm/boot/compressed/misc.c
15、 中,decompress_kernel()又调用proc_decomp_setup(),arch_decomp_ setup()进行设置,然后使用在打印出信息“Uncompressing Linux.”后,调用gunzip()。将内核放于指定的位置。4) 以下分析#/arch/arm/boot/compressed/head.S 文件:(1) 对于各种Arm CPU的DEBUG输出设定,通过定义宏来统一操作。(2) 设置kernel开始和结束地址,保存architecture ID。(3) 如果在ARM2以上的CPU中,用的是普通用户模式,则升到超级用户模式,然后关中断。(4) 分析LC0结
16、构delta offset,判断是否需要重载内核地址(r0存入偏移量,判断r0是否为零)。接下来要把内核镜像的相对地址转化为内存的物理地址,即重载内核地址:(5) 需要重载内核地址,将r0的偏移量加到BSS region和GOT table中。(6) 清空bss堆栈空间r2r3。(7) 建立C程序运行需要的缓存,并赋于64K的栈空间。(8) 这时r2是缓存的结束地址,r4是kernel的最后执行地址,r5是kernel境象文件的开始地址。检查是否地址有冲突。将r5等于r2,使decompress后的kernel地址就在64K的栈之后。(9) 调用文件misc.c的函数decompress_ke
17、rnel(),解压内核于缓存结束的地方(r2地址之后)。此时各寄存器值有如下变化:r0为解压后kernel的大小r4为kernel执行时的地址r5为解压后kernel的起始地址r6为CPU类型值(processor ID)r7为系统类型值(architecture ID)(10) 将reloc_start代码拷贝之kernel之后(r5+r0之后),首先清除缓存,而后执行reloc_start。(11) reloc_start将r5开始的kernel重载于r4地址处。(12) 清除cache内容,关闭cache,将r7中architecture ID赋于r1,执行r4开始的kernel代码。5
18、) 我们在内核启动的开始都会看到这样的输出Uncompressing Linux.done, booting the kernel.这也是由decompress_kernel函数内部输出的,它调用了putc()输出字符串,putc是在#/include/asm-arm/arch-pxa/uncompress.h中实现的。执行完解压过程,再返回到#/arch/arm/boot/compressed/head.S中,启动内核:call_kernel: bl cache_clean_flushbl cache_offmov r0, #0mov r1, r7 restore architecture
19、numbermov pc, r4 call kernel6) 执行zImage 镜像,到start_kernel( )整个arm linux内核的启动可分为三个阶段:第一阶段主要是进行cpu和体系结构的检查、cpu本身的初始化以及页表的建立等;第一阶段的初始化是从内核入口(ENTRY(stext))开始到start_kernel前结束。这一阶段的代码在/arch/arm/kernel/head.S中。/arch/arm/kernel/head.S用汇编代码完成,是内核最先执行的一个文件。这一段汇编代码的主要作用,是检查cpuid,architecture number,初始化页表、cpu、bb
20、s等操作,并跳到start_kernel函数。它在执行前,处理器的状态应满足: r 0 - should be 0 r1 - unique architecture number MMU - off I-cache - on or off D-cache off流程图 代码详细注释#/arch/arm/kernel/head.S/* swapper_pg_dir is the virtual address of the initial page table.* We place the page tables 16K below KERNEL_RAM_VADDR. Therefore, we
21、* must make sure that KERNEL_RAM_VADDR is correctly set. Currently,we *expect the least significant 16 bits to be 0x8000, but we couldprobably relax this *restriction to KERNEL_RAM_VADDR = PAGE_OFFSET +0x4000.*/#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000#error KERNEL_RAM_VADDR must start at 0xXXXX8000
22、#endif.globl swapper_pg_dir.equ swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000.macro pgtbl, rdldr rd, =(KERNEL_RAM_PADDR - 0x4000).endm/* Since the page table is closely related to the kernel startaddress, we* can convert the page table base address to the base address of thesection* containing both.*/.m
23、acro krnladr, rd, pgtable, rambasebic rd, pgtable, #0x000ff000.endm/*/* Kernel startup entry point.* -* This is normally called from the decompressor code. Therequirements* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,* r1 = machine nr, r2 = atags pointer.* See linux/arch/arm/tools/mac
24、h-types for the complete list ofmachine* numbers for r1.*/.section .text.head, ax.type stext, %functionENTRY(stext) /内核入口点msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE/程序状态,禁止FIQ、IRQ,设定Supervisor模式。0b11010011mrc p15, 0, r9, c0, c0 get processor idbl _lookup_processor_type r5=procinfo r9=cupid /跳转到判断
25、/cpu类型,查找运行的cpu的id值和此linux编译支持的id值是否有相等movs r10, r5 invalid processor (r5=0)?beq _error_p yes, error pbl _lookup_machine_type r5=machinfo/跳转到判断体系类型,看r1寄存器的architecture number值是否支持。movs r8, r5 invalid machine (r5=0)?beq _error_a yes, error abl _vet_atagsbl _create_page_tables /创建核心页表/* The following
26、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, _
27、switch_data address to jump to after mmu has been enabledadr lr, _enable_mmu return (PIC) address /lr=0xc0028054add pc, r10, #PROCINFO_INITFUNC initialise processor /r10:pointer to processorstructure#/arch/arm/kernel/head-common.S*/#define ATAG_CORE 0x54410001#define ATAG_CORE_SIZE (2*4 + 3*4) 2).ty
28、pe _switch_data, %object_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/* The following fragment of code is
29、executed with the MMU on in MMUmode,* and uses absolute addresses; this is not position independent.* r0 = cp#15 control register* r1 = machine ID* r2 = atags pointer* r9 = processor ID*/.type _mmap_switched, %function_mmap_switched:/把sp指针指向init_task_union+8192(include/linux/sched.h)处,即第/一个进程的task_s
30、truct和系统堆栈的地址;清空BSS段;保存processorID/和machine type到全局变量processor_id和_machine_arch_type,这些值/以后要用到;r0为A置位的control register 值,r2为A清空的/control register 值,即对齐检查(Alignment fault checking)位,并保/存到cr_alignment,和cr_no_alignment(在文件entry-armv.S中)。最/后跳转到start_kernel(init/main.c)adr r3, _switch_data + 4ldmia r3!, r4, r5, r6, r7 r2 = compat/r20xc0000000cmp r4, r5 Copy data segment if needed /r40xc00c04e0;_bss_start1: cmpne r5,
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1