您好,欢迎访问三七文档
当前位置:首页 > 行业资料 > 其它行业文档 > ARM+Linux的启动分析(zImage)
基于ARM的Linux的启动分析报告摘要:本文主要分析基于ARM的Linux-2.2.26内核启动过程。将首先从/arch/arm/Makefile着手,介绍三种不同的启动方案,再剖析典型的压缩内核zImage启动方案的代码结构,最后将详细分析这种方案的启动过程,直到调用start_kernel()为止。1、Linux内核的启动方案:由/arch/arm/Makefile的代码可以看出,主要有三种启动方案,分别是:echo'*zImage-Compressedkernelimage(arch/$(ARCH)/boot/zImage)'echo'Image-Uncompressedkernelimage(arch/$(ARCH)/boot/Image)'echo'bootpImage-CombinedzImageandinitialRAMdisk'echo'(supplyinitrdimageviamakevariableINITRD=path)'Linux内核有两种映像:一种是非压缩内核,叫Image,另一种是它的压缩版本,叫zImage。根据内核映像的不同,Linux内核的启动在开始阶段也有所不同。zImage是Image经过压缩形成的,所以它的大小比Image小。但为了能使用zImage,必须在它的开头加上解压缩的代码,将zImage解压缩之后才能执行,因此它的执行速度比Image要慢。但考虑到嵌入式系统的存储空容量一般比较小,采用zImage可以占用较少的存储空间,因此牺牲一点性能上的代价也是值得的。所以一般的嵌入式系统均采用压缩内核的方式(另外bootpImage是编译包含zImage和initrd的映像,可以通过make变量INITRD=path提供initrd映像)。2、基于zImage的启动方案。1、zImage的生成过程1、编译链接vmlinux2、生成vmlinux.lds链接脚本3、链接生成zImage2、zImage的代码结构在内核编译完成后会在arch/arm/boot/下生成zImage。#arch/arm/boot/Makefile:$(obj)/zImage:$(obj)/compressed/vmlinuxFORCE$(callif_changed,objcopy)@echo'Kernel:$@isready'由此可见,zImage的是elf格式的,由内核顶层目录下的arch/arm/boot/compressed/vmlinux二进制化得到的:#arch/armboot/compressed/Makefile:$(obj)/vmlinux:$(obj)/vmlinux.lds$(obj)/$(HEAD)$(obj)/piggy.o\$(addprefix$(obj)/,$(OBJS))FORCE$(callif_changed,ld)@:$(obj)/piggy.gz:$(obj)/../ImageFORCE$(callif_changed,gzip)$(obj)/piggy.o:$(obj)/piggy.gzFORCE总结一下zImage的组成,它是由一个压缩后的内核piggy.o,连接上一段初始化及解压功能的代码(head.omisc.o)组成的。3、zImage的启动过程1.Linux内核的一般启动过程: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后将直接跳到该地址处,从而启动Linux内核。2)执行镜像:解压後/非压缩镜像直接执行(linux/arch/arm/kernel/head-armv.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文件中,设置内核启动的虚拟地址textaddr-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=ZTEXTADDR(从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)*(.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是压缩过的内核)压缩过的内核,就是由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中,decompress_kernel()又调用proc_decomp_setup(),arch_decomp_setup()进行设置,然后使用在打印出信息“UncompressingLinux...”后,调用gunzip()。将内核放于指定的位置。4)以下分析#/arch/arm/boot/compressed/head.S文件:(1)对于各种ArmCPU的DEBUG输出设定,通过定义宏来统一操作。(2)设置kernel开始和结束地址,保存architectureID。(3)如果在ARM2以上的CPU中,用的是普通用户模式,则升到超级用户模式,然后关中断。(4)分析LC0结构deltaoffset,判断是否需要重载内核地址(r0存入偏移量,判断r0是否为零)。接下来要把内核镜像的相对地址转化为内存的物理地址,即重载内核地址:(5)需要重载内核地址,将r0的偏移量加到BSSregion和GOTtable中。(6)清空bss堆栈空间r2-r3。(7)建立C程序运行需要的缓存,并赋于64K的栈空间。(8)这时r2是缓存的结束地址,r4是kernel的最后执行地址,r5是kernel境象文件的开始地址。检查是否地址有冲突。将r5等于r2,使decompress后的kernel地址就在64K的栈之后。(9)调用文件misc.c的函数decompress_kernel(),解压内核于缓存结束的地方(r2地址之后)。此时各寄存器值有如下变化:r0为解压后kernel的大小r4为kernel执行时的地址r5为解压后kernel的起始地址r6为CPU类型值(processorID)r7为系统类型值(architectureID)(10)将reloc_start代码拷贝之kernel之后(r5+r0之后),首先清除缓存,而后执行reloc_start。(11)reloc_start将r5开始的kernel重载于r4地址处。(12)清除cache内容,关闭cache,将r7中architectureID赋于r1,执行r4开始的kernel代码。5)我们在内核启动的开始都会看到这样的输出UncompressingLinux...done,bootingthekernel.这也是由decompress_kernel函数内部输出的,它调用了putc()输出字符串,putc是在#/include/asm-arm/arch-pxa/uncompress.h中实现的。执行完解压过程,再返回到#/arch/arm/boot/compressed/head.S中,启动内核:call_kernel:blcache_clean_flushblcache_offmovr0,#0movr1,r7@restorearchitecturenumbermovpc,r4@callkernel6)执行zImage镜像,到start_kernel()整个armlinux内核的启动可分为三个阶段:第一阶段主要是进行cpu和体系结构的检查、cpu本身的初始化以及页表的建立等;第一阶段的初始化是从内核入口(ENTRY(stext))开始到start_kernel前结束。这一阶段的代码在/arch/arm/kernel/head.S中。/arch/arm/kernel/head.S用汇编代码完成,是内核最先执行的一个文件。这一段汇编代码的主要作用,是检查cpuid,architecturenumber,初始化页表、cpu、bbs等操作,并跳到start_kernel函数。它在执行前,处理器的状态应满足:r0-shouldbe0r1-uniquearchitecturenumberMMU-offI-cache-onoroffD-cache–offa)流程图b)代码详细注释#/arch/arm/kernel/head.S/**swapper_pg_diristhevirtualaddressoftheinitialpagetable.*Weplacethepagetables16KbelowKERNEL_RAM_VADDR.Therefore,we*mustmakesurethatKERNEL_RAM_VADDRiscorrectlyset.Currently,we*expecttheleastsignificant16bitstobe0x8000,butwecouldp
本文标题:ARM+Linux的启动分析(zImage)
链接地址:https://www.777doc.com/doc-23590 .html