1、Linux 内核的启动方案:
由/arch/arm/Makefile的代码可以看出,主要有三种启动方案,分别是:
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 initrd image via make variable INITRD=
)'
Linux内核有两种映像:一种是非压缩内核,叫 Image,另一种是它的压缩版
本,叫zImage。根据内核映像的不同,Linux内核的启动在开始阶段也有所不同。
zImage是Image经过压缩形成的,所以它的大小比 Image小。但为了能使用
zImage,必须在它的开头加上解压缩的代码,将 zImage解压缩之后才能执行,
因此它的执行速度比Image要慢。但考虑到嵌入式系统的存储空容量一般比较小,
采用zImage可以占用较少的存储空间,因此牺牲一点性能上的代价也是值得的。
所以一般的嵌入式系统均采用压缩内核的方式(另外bootpImage 是编译包含
zImage和initrd的映像,可以通过make变量INITRD=提供initrd映像)。
2、基于zImage 的启动方案。
1、zImage 的生成过程
1、编译链接vmlinux
2、生成vmlinux.lds链接脚本
3、链接生成zImage
2、zImage 的代码结构
在内核编译完成后会在arch/arm/boot/下生成zImage。
#arch/arm/boot/Makefile:
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
@echo ' Kernel: $@ is ready'
由此可见,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
$(call if_changed,ld)
@:
$(obj)/piggy.gz: $(obj)/../Image FORCE
$(call if_changed,gzip)
$(obj)/piggy.o: $(obj)/piggy.gz FORCE
总结一下zImage 的组成,它是由一个压缩后的内核piggy.o,连接
上一段初始化及解压功能的代码(head.o misc.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/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()进行设置,然后使用在打印出信
息“Uncompressing Linux...”后,调用gunzip()。将内核放于指定的位置。
4) 以下分析#/arch/arm/boot/compressed/head.S 文件:
(1) 对于各种Arm CPU的DEBUG输出设定,通过定义宏来统一操作。
(2) 设置kernel开始和结束地址,保存architecture ID。
(3) 如果在ARM2以上的CPU中,用的是普通用户模式,则升到超级用户模式,
然后关中断。
(4) 分析LC0结构delta offset,判断是否需要重载内核地址(r0存入偏移量,
判断
r0是否为零)。
接下来要把内核镜像的相对地址转化为内存的物理地址,即重载内核地
址:
(5) 需要重载内核地址,将r0的偏移量加到BSS region和GOT table中。
(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类型值(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) 我们在内核启动的开始都会看到这样的输出
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_flush
bl cache_off
mov r0, #0
mov r1, r7 @ restore architecture number
mov pc, r4 @ call kernel
6) 执行zImage 镜像,到start_kernel( )
整个arm linux内核的启动可分为三个阶段:第一阶段主要是进行cpu和
体系结构的检查、cpu本身的初始化以及页表的建立等;第一阶段的初始化是从
内核入口(ENTRY(stext))开始到start_kernel前结束。这一阶段的代码
在/arch/arm/kernel/head.S中。/arch/arm/kernel/head.S用汇编代码完成,
是内核最先执行的一个文件。这一段汇编代码的主要作用,是检查cpu
id,architecture number,初始化页表、cpu、bbs等操作,并跳到
start_kernel函数。它在执行前,处理器的状态应满足:
?r? 0 - should be 0
??r1 - unique architecture number
??MMU - off
??I-cache - on or off
??D-cache – off
流程图
阅读(2296) | 评论(0) | 转发(0) |