linux 216 启动流程分析Word文档下载推荐.docx
《linux 216 启动流程分析Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《linux 216 启动流程分析Word文档下载推荐.docx(11页珍藏版)》请在冰豆网上搜索。
unsignedintperm;
param_set_fnset;
param_get_fnget;
void*arg;
};
__param这个段的声明有些平台是在arch/../../vmlinux.lds.S,而大多数平台是放到
kernel\include\asm-generic\vmlinux.lds.h中,定义如下:
__param:
AT(ADDR(__param)-LOAD_OFFSET){
VMLINUX_SYMBOL(__start___param)=.;
*(__param)
VMLINUX_SYMBOL(__stop___param)=.;
}
内核启动时就会对字符串命令进行解析,在kernel\init\main.c中,内核启动函数start_kernel中
对外部数组进行了声明:
externstructkernel_param__start___param[],__stop___param[];
然后调用函数parse_args对数组进行解析:
parse_args("
Bootingkernel"
command_line,__start___param,
__stop___param-__start___param,
&
unknown_bootoption);
其中command_line就是要解析的字符串命令行,unknown_bootoption是函数指针,它用来获取指定参数的=右边的值。
parse_args就会在数组中找到和nousb名称一样的kernel_param变量,并调用它的set函数对其进行付值。
内核启动地址的确定
内核编译链接过程是依靠vmlinux.lds文件,以arm为例vmlinux.lds文件位于kernel/arch/arm/vmlinux.lds,
但是该文件是由vmlinux-armv.lds.in生成的,根据编译选项的不同源文件还可以是vmlinux-armo.lds.in,
vmlinux-armv-xip.lds.in。
vmlinux-armv.lds的生成过程在kernel/arch/arm/Makefile中
LD
=arch/arm/vmlinux-armv.lds.in
arch/arm/vmlinux.lds:
arch/arm/Makefile$(LD)\
$(wildcardinclude/config/cpu/32.h)\
$(wildcardinclude/config/cpu/26.h)\
$(wildcardinclude/config/arch/*.h)
@echo'
Generating$@'
@sed'
s/TEXTADDR/$(TEXTADDR)/;
s/DATAADDR/$(DATAADDR)/'
$(LD)>
$@
vmlinux-armv.lds.in文件的内容:
OUTPUT_ARCH(arm)
ENTRY(stext)
SECTIONS
{
.=TEXTADDR;
.init:
{
/*Initcodeanddata
*/
_stext=.;
__init_begin=.;
*(.text.init)
__proc_info_begin=.;
*(.proc.info)
__proc_info_end=.;
__arch_info_begin=.;
*(.arch.info)
__arch_info_end=.;
__tagtable_begin=.;
*(.taglist)
__tagtable_end=.;
*(.data.init)
.=ALIGN(16);
__setup_start=.;
*(.setup.init)
__setup_end=.;
__initcall_start=.;
*(.initcall.init)
__initcall_end=.;
.=ALIGN(4096);
__init_end=.;
}
其中TEXTADDR就是内核启动的虚拟地址,定义在kernel/arch/arm/Makefile中:
ifeq($(CONFIG_CPU_32),y)
PROCESSOR
=armv
TEXTADDR
=0xC0008000
endif
需要注意的是这里是虚拟地址而不是物理地址。
一般情况下都在生成vmlinux后,再对内核进行压缩成为zImage,压缩的目录是kernel/arch/arm/boot。
下载到flash中的是压缩后的zImage文件,zImage是由压缩后的vmlinux和解压缩程序组成,如下图所示:
|-----------------|\
|-----------------|
|
|\
|
\
|decompresscode|
vmlinux
\|-----------------|
zImage
\|
/|-----------------|
/
|/
|-----------------|/
zImage链接脚本也叫做vmlinux.lds,位于kernel/arch/arm/boot/compressed。
是由同一目录下的vmlinux.lds.in文件生成的,内容如下:
ENTRY(_start)
.=LOAD_ADDR;
_load_addr=.;
.=TEXT_START;
_text=.;
.text:
{
_start=.;
其中LOAD_ADDR就是zImage中解压缩代码的ram偏移地址,TEXT_START是内核ram启动的偏移地址,这个地址是物理地址。
在kernel/arch/arm/boot/Makefile文件中定义了:
ZTEXTADDR
=0
ZRELADDR
=0xa0008000
ZTEXTADDR就是解压缩代码的ram偏移地址,ZRELADDR是内核ram启动的偏移地址,这里看到指定ZTEXTADDR的地址为0,
明显是不正确的,因为我的平台上的ram起始地址是0xa0000000,在Makefile文件中看到了对该地址设置的几行注释:
#WenowhaveaPICdecompressorimplementation.
Decompressorsrunning
#fromRAMshouldnotdefineZTEXTADDR.
Decompressorsrunningdirectly
#fromROMorFlashmustdefineZTEXTADDR(preferablyviatheconfig)
他的意识是如果是在ram中进行解压缩时,不用指定它在ram中的运行地址,如果是在flash中就必须指定他的地址。
所以
这里将ZTEXTADDR指定为0,也就是没有真正指定地址。
在kernel/arch/arm/boot/compressed/Makefile文件有一行脚本:
SEDFLAGS
=s/TEXT_START/$(ZTEXTADDR)/;
s/LOAD_ADDR/$(ZRELADDR)/;
s/BSS_START/$(ZBSSADDR)/
使得TEXT_START=ZTEXTADDR,LOAD_ADDR=ZRELADDR。
这样vmlinux.lds的生成过程如下:
vmlinux.lds:
vmlinux.lds.inMakefile$(TOPDIR)/arch/$(ARCH)/boot/Makefile$(TOPDIR)/.config
@sed"
$(SEDFLAGS)"
<
vmlinux.lds.in>
$@
以上就是我对内核启动地址的分析,总结一下内核启动地址的设置:
1、设置kernel/arch/arm/Makefile文件中的
TEXTADDR
内核启动的虚拟地址
2、设置kernel/arch/arm/boot/Makefile文件中的
ZRELADDR
内核启动的物理地址
如果需要从flash中启动还需要设置
ZTEXTADDR地址。
内核解压缩过程
内核压缩和解压缩代码都在目录kernel/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是(没有--lw:
zImage是压缩过的内核)压缩过的内核,就是由piggy.o、head.o、misc.o、head-xscale.o组成的。
在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()。
将内核放于指定的位置。
以下分析head.S文件:
(1)对于各种ArmCPU的DEBUG输出设定,通过定义宏来统一操作。
(2)设置kernel开始和结束地址,保存architectureID。
(3)如果在ARM2以上的CPU中,用的是普通用户模式,则升到超级用户模式,然后关中断。
(4)分析LC0结构deltaoffset,判断是否需要重载内核地址(r0存入偏移量,判断r0是否为零)。
这里是否需要重载内核地址,我以为主要分析arch/arm/boot/Makefile、arch/arm/boot/compressed/Makefile
和arch/arm/boot/compressed/vmlinux.lds.in三个文件,主要看vmlinux.lds.in链接文件的主要段的位置,
LOAD_ADDR(_load_addr)=0xA0008000,而对于TEXT_START(_text、_start)的位置只设为0,BSS_START(__bss_start)=ALIGN(4)。
对于这样的结果依赖于,对内核解压的运行方式,也就是说,内核解压前是在内存(RAM)中还是在FLASH上,
因为这里,我们的BOOTLOADER将压缩内核(zImage)移到了RAM的0xA0008000位置,我们的压缩内核是在内存(RAM)从0xA0008000地址开始顺序排列,
因此我们的r0获得的偏移量是载入地址(0xA0008000)。
接下来的工作是要把内核镜像的相对地址转化为内存的物理地址,即重载内核地址。
(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代码。
下面简单介绍一下解压缩过程,也就是函数decompress_kernel实现的功能:
解压缩代码位于kernel/lib/inflate.c,inflate.c是从gzip源程序中分离出来的。
包含了一些对全局数据的直接引用。
在使用时需要直接嵌入到代码中。
gzip压缩文件时总是在前32K字节的范围内寻找重复的字符串进行编码,
在解压时需要一个至少为32K字节的解压缓冲区,它定义为window[WSIZE]。
inflate.c使用get_byte()读取输入文件,
它被定义成宏来提高效率。
输入缓冲区指针必须定义为inptr,inflate.c中对之有减量操作。
inflate.c调用flush_window()
来输出window缓冲区中的解压出的字节串,每次输出长度用outcnt变量表示。
在flush_window()中,还必
须对输出字节串计算CRC并且刷新crc变量。
在调用gunzip()开始解压之前,调用makecrc()初始化CRC计算表。
最后gunzip()返回0表示解压成功。
我们在内核启动的开始都会看到这样的输出:
UncompressingLinux...done,bootingthekernel.
这也是由decompress_kernel函数内部输出的,它调用了puts()输出字符串,
puts是在kernel/include/asm-arm/arch-pxa/uncompress.h中实现的。
执行完解压过程,再返回到head.S中,启动内核:
call_kernel:
bl
cache_clean_flush
cache_off
movr0,#0
movr1,r7
@restorearchitecturenumber
movpc,r4
@callkernel
下面就开始真正的内核了。
汇编部分
(1)
在网上参考很多高手的文章,又加入了自己的一点儿内容,整理了一下,里面还有很多不明白的地方,而且也会有理解错误的地方,望高手指点,自己也会不断进行修改
当进入linux内核后,arch/arm/kernel/head-armv.S是内核最先执行的一个文件,包括从内核入口ENTRY(stext)到
start_kernel之间的初始化代码,下面以我所是用的平台intelpxa270为例,说明一下他的汇编代码:
1
.section"
.text.init"
#alloc,#execinstr
2
.type
stext,#
/*内核入口点*/
3ENTRY(stext)
4
movr12,r0
/*程序状态,禁止FIQ、IRQ,设定SVC模式*/
5
movr0,#F_BIT|I_BIT|MODE_SVC
@makesuresvcmode
6
msrcpsr_c,r0
@andallirqsdisabled
/*判断CPU类型,查找运行的CPUID值与Linux编译支持的ID值是否支持*/
7
__lookup_processor_type
/*判断如果r10的值为0,则表示函数执行错误,跳转到出错处理,*/
/*出错处理函数__error的实现代码定义在debug-armv.S中,这里就不再作过多介绍了*/
8
teqr10,#0
@invalidprocessor?
9
moveq
r0,#'
p'
@yes,error'
10
beq__error
/*判断体系类型,查看R1寄存器的ArchitectureType值是否支持*/
11
__lookup_architecture_type
/*判断如果r7的值为0,则表示函数执行错误,跳转到出错处理,*/
12
teqr7,#0
@invalidarchitecture?
13
a'
14
/*创建核心页表*/
15
__create_page_tables
16
adrlr,__ret
@returnaddress
17
addpc,r10,#12
@initialiseprocessor
@(returncontrolreg)
第5行,准备进入SVC工作模式,同时关闭中断(I_BIT)和快速中断(F_BIT)
第7行,查看处理器类型,主要是为了得到处理器的ID以及页表的flags。
第11行,查看一些体系结构的信息。
第15行,建立页表。
第17行,跳转到处理器的初始化函数,其函数地址是从__lookup_processor_type中得到的,
需要注意的是第16行,当处理器初始化完成后,会直接跳转到__ret去执行,
这是由于初始化函数最后的语句是movpc,lr。