基于ARM的Linux的启动分析.docx
《基于ARM的Linux的启动分析.docx》由会员分享,可在线阅读,更多相关《基于ARM的Linux的启动分析.docx(22页珍藏版)》请在冰豆网上搜索。
基于ARM的Linux的启动分析
基于ARM的Linux的启动分析
说明:
本文主要的分析基于ARM的linux-2.6.17内核启动过程分析。
1、Linux内核的启动方案:
由/arch/arm/Makefile的代码:
definearchhelp
echo'*zImage-Compressedkernelimage(arch/$(ARCH)/boot/zImage)'
echo'Image-Uncompressedkernelimage(arch/$(ARCH)/boot/Image)'
echo'*xipImage-XIPkernelimage,ifconfigured(arch/$(ARCH)/boot/xipImage)'
echo'bootpImage-CombinedzImageandinitialRAMdisk'
echo'(supplyinitrdimageviamakevariableINITRD=)'
echo'install-Installuncompressedkernel'
echo'zinstall-Installcompressedkernel'
echo'Installusing(your)~/bin/installkernelor'
echo'(distribution)/sbin/installkernelor'
echo'installto$$(INSTALL_PATH)andrunlilo'
endef
可以看出,主要有三种启动方案,分别是:
echo'*zImage-Compressedkernelimage(arch/$
(ARCH)/boot/zImage)'
echo'Image-Uncompressedkernelimage(arch/$
(ARCH)/boot/Image)'
echo'bootpImage-CombinedzImageandinitialRAMdisk'
echo'(supplyinitrdimageviamakevariableINITRD=)'。
Linux内核有两种映像:
一种是非压缩内核,叫Image,另一种是它的压缩版
本,叫zImage。
根据内核映像的不同,Linux内核的启动在开始阶段也有所不同。
zImage是Image经过压缩形成的,所以它的大小比Image小。
但为了能使用
zImage,必须在它的开头加上解压缩的代码,将zImage解压缩之后才能执行,
因此它的执行速度比Image要慢。
但考虑到嵌入式系统的存储空容量一般比较小,
采用zImage可以占用较少的存储空间,因此牺牲一点性能上的代价也是值得的。
所以一般的嵌入式系统均采用压缩内核的方式(另外bootpImage是编译包含
zImage和initrd的映像,可以通过make变量INITRD=提供initrd映像)
2.典型启动方案的代码结构:
(1).Image启动方案的代码结构:
#arch/arm/boot/Makefile:
$(obj)/Image:
vmlinuxFORCE
$(callif_changed,objcopy)
@echo'Kernel:
$@isready'
由此可见,Image由vmlinux二进制化得到的。
Image的是由kernel/vmlinux构成的。
(2).zImage启动方案的代码结构:
在内核编译完成后会在arch/arm/boot/下生成zImage。
#arch/arm/boot/Makefile:
$(obj)/zImage:
$(obj)/compressed/vmlinuxFORCE
$(callif_changed,objcopy)
@echo'Kernel:
$@isready'
由此可见,zImage由内核顶层目录下的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)BootpImage启动方案的代码结构:
#arch/arm/boot/Makefile:
$(obj)/bootp/bootp:
$(obj)/zImageinitrdFORCE
$(Q)$(MAKE)$(build)=$(obj)/bootp$@
$(obj)/bootpImage:
$(obj)/bootp/bootpFORCE
$(callif_changed,objcopy)
@echo'Kernel:
$@isready'
由此可知bootpImage是由zImage与initrd经过二进制化得到的,所以他包含了zImageinitrd。
压缩内核zImage启动方案的分析
通常从系统加电到执行到linuxkenel这部分的任务是由bootloader来完成。
关于这部分内容在这里不不多分析了这里主要分析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/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文件中,设置内核启动的虚拟地址
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_flush
blcache_off
movr0,#0
movr1,r7@restorearchitecturenumber
movpc,r4@callkernel
6)执行zImage镜像,到start_kernel()
整个armlinux内核的启动可分为三个阶段:
第一阶段主要是进行cpu和
体系结构的检查、cpu本身的初始化以及页表的建立等;第一阶段的初始化是从
内核入口(ENTRY(stext))开始到start_kernel前结束。
这一阶段的代码
在/arch/arm/kernel/head.S中。
/arch/arm/kernel/head.S用汇编代码完成,
是内核最先执行的一个文件。
这一段汇编代码的主要作用,是检查cpu
id,architecturenumber,初始化页表、cpu、bbs等操作,并跳到
start_kernel函数。
它在执行前,处理器的状态应满足:
r0-shouldbe0
r1-uniquearchitecturenumber
MMU-off
I-cache-onoroff
D-cache–off
流程图
代码详细注释
#/arch/arm/kernel/head.S
/*
*swapper_pg_diristhevirtualaddressoftheinitialpagetable.
*Weplacethepagetables16KbelowKERNEL_RAM_VADDR.Therefore,we
*mustmakesurethatKERNEL_RAM_VADDRiscorrectlyset.Currently,
we*expecttheleastsignificant16bitstobe0x8000,butwecould
probablyrelaxthis*restrictiontoKERNEL_RAM_VADDR>=PAGE_OFFSET+
0x4000.
*/
#if(KERNEL_RAM_VADDR&0xffff)!
=0x8000
#errorKERNEL_RAM_VADDRmuststartat0xXXXX8000
#endif
.globlswapper_pg_dir
.equswapper_pg_dir,KERNEL_RAM_VADDR-0x4000
.macropgtbl,rd
ldr\rd,=(KERNEL_RAM_PADDR-0x4000)
.endm
/*
*Sincethepagetableiscloselyrelatedtothekernelstart
address,we
*canconvertthepagetablebaseaddresstothebaseaddressofthe
section
*containingboth.
*/
.macrokrnladr,rd,pgtable,rambase
bic\rd,\pgtable,#0x000ff000
.endm
/*
/*
*Kernelstartupentrypoint.
*---------------------------
*
*Thisisnormallycalledfromthedecompressorcode.The
requirements
*are:
MMU=off,D-cache=off,I-cache=dontcare,r0=0,
*r1=machinenr,r2=atagspointer.
*
*Seelinux/arch/arm/tools/mach-typesforthecompletelistof
machine
*numbersforr1.
*/
.section".text.head","ax"
.typestext,%function
ENTRY(stext)//内核入口点
msrcpsr_c,#PSR_F_BIT|PSR_I_BIT|SVC_MODE
//程序状态,禁止FIQ、IRQ,设定Supervisor模式。
0b11010011
mrcp15,0,r9,c0,c0@getprocessorid
bl__lookup_processor_type@r5=procinfor9=cupid//跳转到
判断
//cpu类型,查找运行的cpu的id值和此linux编译支持的id值是否有相
等
movsr10,r5@invalidprocessor(r5=0)?
beq__error_p@yes,error'p'
bl__lookup_machine_type@r5=machinfo
//跳转到判断体系类型,看r1寄存器的architecturenumber值是否支持。
movsr8,r5@invalidmachine(r5=0)?
beq__error_a@yes,error'a'
bl__vet_atags
bl__create_page_tables//创建核心页表
/*
*ThefollowingcallsCPUspecificcodeinapositionindependent
*manner.Seearch/arm/mm/proc-*.Sfordetails.r10=baseof
*xxx_proc_infostructureselectedby__lookup_machine_type
*above.Onreturn,theCPUwillbereadyfortheMMUtobe
*turnedon,andr0willholdtheCPUcontrolregistervalue.
*/
ldrr13,__switch_data@addresstojumptoafter
@mmuhasbeenenabled
adrlr,__enable_mmu@return(PIC)address//lr=0xc0028054
addpc,r10,#PROCINFO_INITFUNC
@initialiseprocessor//r10:
pointertoprocessor
structure
#/arch/arm/kernel/head-common.S
*/
#defineATAG_CORE0x54410001
#defineATAG_CORE_SIZE((2*4+3*4)>>2)
.type__switch_data,%object
__switch_data:
.long__mmap_switched
.long__data_loc@r4
.long__data_start@r5
.long__bss_start@r6
.long_end@r7
.longprocessor_id@r4
.long__machine_arch_type@r5
.long__atags_pointer@r6
.longcr_alignment@r7
.longinit_thread_union+THREAD_START_SP@sp
/*
*ThefollowingfragmentofcodeisexecutedwiththeMMUoninMMU
mode,
*andusesabsoluteaddresses;thisisnotpositionindependent.
*r0=cp#15controlregister
*r1=machineID
*r2=atagspointer
*r9=processorID
*/
.type__mmap_switched,%function
__mmap_switched:
//把sp指针指向init_task_union+8192(include/linux/sched.h)处,即第
//一个进程的task_struct和系统堆栈的地址;清空BSS段;保存processor
ID
//和machinetype到全局变量processor_id和__machine_arch_type,这些值
//以后要用到;r0为"A"置位的controlregister值,r2为"A"清空的
//controlregister值,即对齐检查(Alignmentfaultchecking)位,并保
//存到cr_alignment,和cr_no_alignment(在文件entry-armv.S中)。
最
//后跳转到start_kernel(init/main.c)
adrr3,__switch_data+4
ldmiar3!
{r4,r5,r6,r7}@r2=compat//r2=0xc0000000
cmpr4,r5@Copydatasegmentifneeded//r4=0xc00c04e0;
__bss_start
1:
cmpner5,