基于 Arm 的 linux 的启动分析(转)
(本文转载自:http://blog.csdn.net/liaobie/archive/2009/11/15/4812980.aspx 转载的目的是为了更好的去做笔记,去理解!!)
(在学习的过程中会一点点的把注释加上,要不真看不懂啊)
目录:
一 Makefile 的分析 ... 2
1.1 启动方案 ... 2
1.2 zImage 代码结构 ... 2
1.2.1 顶层vmlinux 的生成过程 ... 2
1.2.2 zImage 的生成 ... 6
二zImage 的启动过程 ... 12
2.1 compressed/vmlinux.lds 文件的分析 ... 12
2.2 compressed/head.s 文件的分析 ... 12
2.3inux/arch/arm/kernel/head.S 文件的分析 ... 18
2.4 arch/arm/kernel/common.s 文件的分析 ... 19
(看这个文章把握住两点:本文第一个过程是make zImage的过程,是在配置生成时候的过程,这个时候内核还没有往板子上下载呢,第二个过程是拷贝到板子上以后,从flash再拷贝到内存中,解压并且执行内核的第一阶段的启动)
注:目录均设有超级链接,可以按住CTRL 并单击鼠标进行跟踪链接。
一 Makefile 的分析
1.1 启动方案
在/arch/arm 文件夹下的Makefile 文件第215 行得到语句
(有的在顶层目录中就有)
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 configured (arch/$(ARCH)/boot/xipImage)'
echo ' bootpImage - Combined zImage and initial RAM disk'
由此可以知道基于ARM 的启动方案有Image 、zImage 、xipImage 和bootpImage 四种。
(都是依赖于vmlinux)
1.2 zImage 代码结构
以zImage 为例分析zImage 型启动方案的代码结构:
在arm 文件夹下的makefile 中默认目标all 在169 行
# Default target when executing plain make
ifeq ($(CONFIG_XIP_KERNEL),y)
all: xipImage
else
all: zImage
endif
其中zImage 在197 行作为目标生成:
zImage Image xipImage bootpImage uImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
(上面的这句话就是在生成vmlinux的基础上生成zImage)
zImage 等目标的生成依赖于vmlinux 。而vmlinux 是在顶层目录下的Makefile 文件中生成的。
1.2.1 顶层vmlinux 的生成过程
在linux-2.6.17 顶层目录下找到并打开Makefile 文件。在文件的464 行找到缺省目标"all :vmlinux" 语句.
根据关键字vmlinux 找到是由文件中的703 行
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(kallsyms.o) FORCE
(第一个$后面的是连接脚本,那么什么是连接脚本?要保证head.S连接到最前面来,(可以看看连接脚本的语法),就需要连接脚本来实现,通过连接脚本来保证我需要的head.S东西放在最前面)
(第二个$是一个变量,在下面又讲了他需要的变量)
$(call if_changed_rule,vmlinux__)
$(Q)rm -f .old_version
语句生成。可以看出vmlinux 依赖于vmlinux-lds ,vmlinux-init ,vmlinux-main 和kallsysm.o 变量。
这些变量在顶层Makefile 第567 行定义
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
vmlinux-lds := arch/$(ARCH)/kernel/vmlinux.lds
首先查看 vmlinux.lds 文件 arm/kernel/vmlinux.lds :
第25 行: . = PAGE_OFFSET + TEXT_OFFSET;
TEXT_OFFSET 在arm/Makefile 中定义:
第128 行:TEXT_OFFSET := $(textofs-y)
第82 行:textofs-y := 0x00008000 这是内核启动的虚拟地址
第141 行:export TEXT_OFFSET GZFLAGS MMUEXT 将TEXT_OFFSET 导出供vmlinux.lds 使用
这样就有 . = PAGE_OFFSET + 0x00008000 ;
再看vmlinux-main 变量
其中的head-y 在arch/arm/Makefile 第81 行定义
head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
(这就体现出依赖于head.S)
变量init-y 在顶层Makefile 第427 行定义
init-y := init/
而后在532 行修改
init-y := $(patsubst %/, %/built-in.o, $(init-y))
这里的patsubst 是实现匹配替换的,在这里将init/ 的'/' 替换为'/built-in.o' 。
所以变量init-y 应为
init-y := init/built-in.o
(最后在init中最终生成的built-in.o)
(makefile的核心就是把各个文件夹中的东西生成相应的点o文件,看的时候把握住核心:找依赖的关系,从最开始一步步的找依赖,来看执行过程)
因此
vmlinux-init := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o init/built-in.o
同理看vmlinux-main 的定义
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
core-y 定义在顶层Makefile 第431 行
core-y := usr/
而后在521 行追加
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
533 行修改
core-y := $(patsubst %/, %/built-in.o, $(core-y))
所以core-y 应为
core-y :=usr/kernel/ mm/ fs/ ipc/ security/ crypto/ block/built-in.o
libs-y 定义在顶层Makefile 第430 行
libs-y := lib/
而后在536 行修改
libs-y := $(patsubst %/, %/lib.a, $(libs-y))
所以libs-y 应为
libs-y := lib/lib.a
drivers-y 定义在顶层Makefile 第428 行
drivers-y := drivers/ sound/
而后在534 行修改
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
所以drivers-y 应为
drivers-y := drivers/ sound/ built-in.o
net-y 变量定义在顶层Makefile 第429 行
net-y := net/
而后在第535 行修改
net-y := $(patsubst %/, %/built-in.o, $(net-y))
所以net-y 应为
net-y := net-y/ built-in.o
因此
vmlinux-main := usr/kernel/ mm/ fs/ ipc/ security/ crypto/ block/built-in.o
lib/lib.a
drivers/ sound/ built-in.o
net-y/ built-in.o
这些依赖文件的生成规则和依赖如下:
$(sort $(vmlinux-init) $(vmlinux-main)) $(vmlinux-lds): $(vmlinux-dirs) ;
此处规则为空规则,而依赖文件$(vmlinux-dirs) 的生成如下:
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
至此vmlinux 的依赖文件分析完毕。
然后看vmlinux 的生成规则
$(Q)$(MAKE) -f $(srctree)/Makefile headers_check 是进行头文件的相关检测。下一条命令
$(call if_changed_rule,vmlinux__)
(在这里要注意makefile中函数的概念)
变量if_changed_rule 没在顶层Makefile 中定义,所以在文件中向上查找include ,第一条语句在266 行找到:
include $(srctree)/scripts/Kbuild.include
在这个文件中定义了大量的函数和变量,供顶层makefile 和其他makefile 文件使用。
在文件/linuv-2.6.27/scripts/Kbuild.include 文件中145 行找到if_changed_rule 变量。描述如下:
# Usage: $(call if_changed_rule,foo)
# will check if $(cmd_foo) changed, or any of the prequisites changed,
# and if so will execute $(rule_foo)
if_changed_rule = $(if $(strip $(filter-out $(PHONY),$?) \
$(call arg-check, $(cmd_$(1)), $(cmd_$@)) ),\
@set -e; \
$(rule_$(1)))
此处的$(1)=vmlinux__ 。$(rule_$(1)) 语句得到rule_vmlinux__ ,因此$(call if_changed_rule,vmlinux__) 语句是通过call 函数调用执行的rule_vmlinux__, 在顶层Makefile 第601 行
define rule_vmlinux__
:
$(if $(CONFIG_KALLSYMS),,+$(call cmd,vmlinux_version))
$(call cmd,vmlinux__)
$(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd
$(Q)$(if $($(quiet)cmd_sysmap), \
echo ' $($(quiet)cmd_sysmap) System.map' &&) \
$(cmd_sysmap) $@ System.map; \
if [ $$? -ne 0 ]; then \
rm -f $@; \
/bin/false; \
fi;
$(verify_kallsyms)
endef
这里主要还是调用 cmd_vmlinux__ , 定义在顶层文件 575 行
cmd_vmlinux__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) -o $@ \
-T $(vmlinux-lds) $(vmlinux-init) \
--start-group $(vmlinux-main) --end-group \
$(filter-out $(vmlinux-lds) $(vmlinux-init)\
$(vmlinux-main) FORCE ,$^) ) FORCE ,$^)
通过这个命令将变量vmlinux-init 和vmlinux-main 指定的目标链接成vmlinux 文件。链接脚本由vmlinux-lds 指定。在顶层 Makefile 570 行定义:
vmlinux-lds := arch/$(ARCH)/kernel/vmlinux.lds
至此vmlinux 生成分析完毕。
1.2.2 zImage 的生成
将zImage 生成语句重写如下
zImage Image xipImage bootpImage uImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
然后看zImage 的生成规则:
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
命令行的build 变量在arm/makefile 中没有定义,然而此文件包含于顶层makefile ,而顶层makefile 中首先包含的是scripts/Kbuild.include ,所以在Kbuild.include 中
查找build :在Kbuild.include88 行定义:
build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
boot 变量在arm/makefile 中172 行定义:
boot := arch/arm/boot
这个规则的命令最终会进入scripts 目录,执行Makefile.build 文件,并传递参数obj=scripts/arch/arm/boot.
MACHINE 在arm/makefile 的135 行定义
ifneq ($(machine-y),)
MACHINE := arch/arm/mach-$(machine-y)/
else
MACHINE :=
endif
machine-y( 假设为s3c2410) 由语句
machine-$(CONFIG_ARCH_S3C2410) := s3c2410
决定为machine-s3c2410 。
所以zImage 为:
zImage: vmlinux
$(Q)$(MAKE) -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj = arch/arm/boot \
MACHINE = arch/arm/mach-s3c2410.
然后看scripts/Makefile.build 文件(由上一句将obj = arch/arm/boot 赋值)
5 行 src := $(obj)
# The filename Kbuild has precedence over Makefile
17 行 kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
include $(if $(wildcard $(kbuild-dir)/Kbuild), $(kbuild-dir)/Kbuild, $(kbuild-dir)/Makefile)
即此处为
kbuild-dir := arch/arm/boot
include arch/arm/boot/Kbuild,arch/arm/boot/Makefile
打开boot/Makefile:
第17 行:
include $(srctree)/$(MACHINE)/Makefile.boot
在Makefile.boot 中得到
zreladdr-y := 0x30008000
注释行有:ZRELADDR == virt_to_phys(PAGE_OFFSET + TEXT_OFFSET)
这个是zImage 的运行地址。
第49 行:
$(obj)/Image: vmlinux FORCE
$(call if_changed,objcopy)
(在这里二进制化282那里)
$(obj)/compressed/vmlinux: $(obj)/Image FORCE
$(Q)$(MAKE) $(build)=$(obj)/compressed $@
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
(上面的vmlinux是一个中间文件,zImage依赖于vminux,vmlinux又依赖于Image)
49 行的vmlinux 是在顶层生成的,在前面已经得到。
50 行 $(call if_changed,objcopy) 即调用call (if_changed 在scripts/Kbuild.include 文件中125 行被执行)来执行cmd_objcopy :
if_changed = $(if $(strip $(filter-out $(PHONY),$?) \
$(call arg-check, $(cmd_$(1)), $(cmd_$@)) ), \
cmd_$(1) = cmd_objcpy;
arg-check 在scripts/Kbuild.include 第112 行进行测试:
ifneq ($(KBUILD_NOCMDDEP),1)
# Check if both arguments has same arguments. Result in empty string if equal
# User may override this check using make KBUILD_NOCMDDEP=1
arg-check = $(strip $(filter-out $(1), $(2)) $(filter-out $(2), $(1)) )
endif
我们关心的是变量cmd_objcpy
在$(call if_changed,objcopy) 规则中,将前面创建的vmlinux 文件通过二进制工具objcopy 进行处理,在scripts/Makefile.build 的第19
行包含了scripts/Makefile.lib :
include scripts/Makefile.lib
在这个makefile 文件中,有cmd_objcopy 的定义,在156 行开始定义
quiet_cmd_objcopy = OBJCOPY $@
cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $
顶层Makefile 中有
282 行 OBJCOPY = $(CROSS_COMPILE)objcopy
CROSS_COMPILE ?=
OBJCOPYFLAGS 在arm/Makefile 的15 行:
OBJCOPYFLAGS :=-O binary -R .note -R .comment -S
cmd_objcopy = objcopy -O binary -R .note -R .comment -S
objcopy 命令可以将一种格式的目标文件内容进行转换,并输出为另一种
格式的目标文件。
(具体实现就是在这里二进制化)
在 makefile 里面用-O binary 选项来生成原始的二进制文件,
即通常说的 image 文件
也就是说顶层Makefile 中生成的vmlinux 二进制化得到Image 文件。
然后是53 行:
$(obj)/compressed/vmlinux: $(obj)/Image FORCE
$(Q)$(MAKE) $(build)=$(obj)/compressed $@
这里53 的$(obj) = arch/arm/boot;
变量build 为:
build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
所以执行$(build)=$(obj)/compressed 之后,
obj = arch/arm/boot/compressed;
$(obj)/compressed/vmlinux: arch/arm/boot/Image FORCE
$(Q)$(MAKE) build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build
obj = arch/arm/boot/compressed arch/arm/boot/compressed/vmlinux
build 语句令最终会进入scripts 目录,执行Makefile.build 文件,并传递参数obj = arch/arm/boot/compressed.
在Makefile.build 的第5 行有:
src := $(obj)
这就把传递进来的值赋给了src ,所以
src := arch/arm/boot/compressed
从第16 行开始的两行把src ( 即arch/arm/boot/compressed) 目录下的Makefile 包含进来(如果有Kbuild 则包含Kbuild )
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
include $(if $(wildcard $(kbuild-dir)/Kbuild), $(kbuild-dir)/Kbuild, $(kbuild-dir)/Makefile)
到达arch/arm/boot/compressed 目录下的Makefile 第94 行:
$(obj)/vmlinux: $(obj)/vmlinux.lds.in $(obj)/$(HEAD) $(obj)/piggy.o\
$(addprefix $(obj)/,$(BOJS)) FORCE
$(call if_changed,ld)
@:
(addprefix 函数 ( 两个参数) 将源串( 第二个参数中由空格分隔) 中的每一项添加前缀( 第一个参数).)
$(obj)/piggy.gz: $(obj)/../Image FORCE
$(call if_changed,gzip)
$(obj)/piggy.o: $(obj)/piggy.gz FORCE
HEAD = head.o
OBJS = misc.o
第106 行:
$(obj)/vmlinux.lds: $(obj)/vmlinux.lds.in arch/arm/boot/Makefile .config
@sed "$(SEDFLAGS)" < $< > $@
$(obj)/misc.o: $(obj)/misc.c include/asm/arch/uncompress.h lib/inflate.c
打开vmlimux.lds.in 第14 行:
. = TEXT_START;
而在compressed/Makefile 中有语句:
第66 行: ZTEXTADDR := 0
ZBSSADDR := ALIGN(4)
ZTEXTADDR 是自解压代码的起始地址,如果从内存启动内核,设置为0 即可,如果从Rom/Flash 启动,则设置 ZTEXTADDR 为相应的值。ZRELADDR 是内核解压缩后的执行地址。
这里我们普通的启动就是得到ZTEXTADDR 为0 了,ZTEXTADDR 是指zImage 中.text 节开始的物理地址,也就是zImage 的第一条指令的物理地址.
第70 行:
SEDFLAGS = s/TEXT_START/$(ZTEXTADDR)/;s/BSS_START/$(ZBSSADDR)/
这里把链接教本中的TEXT_START 换成了ZTEXTADDR ,我们的例子来说就是0 了,TEXT_ADDR 是链接教本中引入,或说zImage 的虚拟首址.
此时TEXT_START = 0 ;
zImage 是由一个压缩后的内核piggy.o ,连接上一段初始化及解压功能的代码(head.o misc.o )组成的。
piggy.o 由内核压缩生成,head.o 和misc.o 用于初始化和内核解压缩。
即:
vmlinux-( 二进制化)->Image-->compress/vmlinux( 包含了piggy.o 和head.o 、misc.o)-( 二进制化)->zImage
二zImage 的启动过程
zImage 的启动过程:
zImage 的生成经历了两次大的链接过程:一次是顶层vmlinux 的生成,由arch/arm/boot/vmlinux.lds (这个lds 文件是由 arch/arm/kernel/vmlinux.lds.S 生成的)决定;另一次是arch/arm/boot/compressed/vmlinux 的生成,是由arch/arm/boot/compressed/vmlinux.lds (这个lds 文件是由arch/arm/boot/compressed/vmlinux.lds.in 生成的)决定。
所以zImage 的入口由arch/arm/boot/compressed/vmlinux.lds 来决定。
2.1 compressed/vmlinux.lds 文件的分析
打开vmlinux.lds.in :
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = TEXT_START; (上文分析TEXT_START = 0 )
_text = .;
.text : {
_start = .;
*(.start)
*(.text)
2.2 compressed/head.s 文件的分析
首先执行的是compressed/head.s 中的start 段:
start:
.type start,#function
.rept 8 // 重复8 次下面的指令,也就是空出中断向量表的位置
mov r0, r0 // 就是nop 指令
.endr
b 1f
.word 0x016f2818 @ Magic numbers to help the loader
.word start @ absolute load/run zImage address
.word _edata @ zImage end address
1: mov r7, r1 @ save architecture ID
mov r8, r2 @ save atags pointer
保存ID 和atags pointer 到r1 、r2 ,然后执行下面的关中断程序段。
#ifndef __ARM_ARCH_2__
mrs r2, cpsr @ get current mode
tst r2, #3 @ not user?
bne not_angel
mov r0, #0x17 @ angel_SWIreason_EnterSVC
swi 0x123456 @ angel_SWI_ARM
not_angel:
mrs r2, cpsr @ turn off interrupts to
orr r2, r2, #0xc0 @ prevent angel from running
msr cpsr_c, r2
#else
teqp pc, #0x0c000003 @ turn off interrupts
#endif
至此start 段结束,接下来是text 段。
首先LCO 段定义如下数据结构:
LC0: .word LC0 @ r1
.word __bss_start @ r2
.word _end @ r3
.word zreladdr @ r4
.word _start @ r5
.word _got_start @ r6
.word _got_end @ ip
.word user_stack+4096 @ sp
.text
adr r0, LC0
ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}
subs r0, r0, r1 @ calculate the delta offset
@ if delta is zero, we are
beq not_relocated @ running at the address we
@ were linked at.
/*
* We're running at a different address. We need to fix
* up various pointers:
* r5 - zImage base address
* r6 - GOT start
* ip - GOT end
*/
add r5, r5, r0
add r6, r6, r0
add ip, ip, r0
第206 行是BBS 清零部分:
not_relocated: mov r0, #0
1: str r0, [r2], #4 @ clear bss
str r0, [r2], #4
str r0, [r2], #4
str r0, [r2], #4
cmp r2, r3
blo 1b
结束之后到219 行执行跳转命令,执行启动cache 命令行:
bl cache_on
mov r1, sp @ malloc space above stack
add r2, sp, #0x10000 @ 64k max
cache_on 在321 行:
cache_on: mov r3, #8 @ cache_on function
b call_cache_fn
call_cache_fn 在503 行:
call_cache_fn: adr r12, proc_types
mrc p15, 0, r6, c0, c0 @ get processor ID
1: ldr r1, [r12, #0] @ get value
ldr r2, [r12, #4] @ get mask
eor r1, r1, r6 @ (real ^ match)
tst r1, r2 @ & mask
addeq pc, r12, r3 @ call cache function
add r12, r12, #4*5
b 1b
这里cache_on: r3 中存入#8, 跳到call_cache_fn, proc_types 为缓存操作表格, 一个条目有五条
* - CPU ID match
* - CPU ID mask
* - 'cache on' method instruction
* - 'cache off' method instruction
* - 'cache flush' method instruction
7
r3 的值决定了调用的是on 的还是off 的函数。
bl cache_on 之后的两条指令:
mov r1, sp @ malloc space above stack
add r2, sp, #0x10000 @ 64k max
建立了c 程序运行需要的缓存,并赋予64K 的栈空间。
这时r2 是缓存的结束地址,r4 是kernel 的最后执行地址,r5 是kernel 境象文件的开始地
址。检查是否地址有冲突。将r5 等于r2 ,使decompress 后的kernel 地址就在64K 的栈之后。
head.s 第233 行:
cmp r4, r2
bhs wont_overwrite
add r0, r4, #4096*1024 @ 4MB largest kernel size
(认为kernel最大是4m)
cmp r0, r5
bls wont_overwrite
mov r5, r2 @ decompress after malloc space
mov r0, r5
mov r3, r7
bl decompress_kernel
执行程序至:bl decompress_kernel 进入内核解压。调用文件misc.c 的函数decompress_kernel() ,解压内核于缓存结束的地方(r2 地址之后) 。
进入misc.c 文件:第325 行
decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p,
int arch_id)
然后返回head.s :
紧接bl decompress_kernel 之后的命令:
add r0, r0, #127
bic r0, r0, #127 @ align the kernel length
此时各寄存器值有如下变化:
r0 为解压后kernel 的大小
r4 为kernel 执行时的地址
r5 为解压后kernel 的起始地址
r6 为CPU 类型值(processor ID)
r7 为系统类型值(architecture ID)
* r0 = decompressed kernel length
* r1-r3 = unused
* r4 = kernel execution address
* r5 = decompressed kernel start
* r6 = processor ID
* r7 = architecture ID
* r8 = atags pointer
* r9-r14 = corrupted
* r0 = decompressed kernel length
* r1-r3 = unused
* r4 = kernel execution address
* r5 = decompressed kernel start
* r6 = processor ID
* r7 = architecture ID
* r8 = atags pointer
* r9-r14 = corrupted
*/
第256 行:
add r1, r5, r0 @ end of decompressed kernel
adr r2, reloc_start
ldr r3, LC1
add r3, r2, r3
1: ldmia r2!, {r9 - r14} @ copy relocation code
stmia r1!, {r9 - r14}
ldmia r2!, {r9 - r14}
stmia r1!, {r9 - r14}
cmp r2, r3
blo 1b
bl cache_clean_flush
add pc, r5, r0 @ call relocation code
将reloc_start 代码拷贝之kernel 之后(r5+r0 之后) ,首先清除缓存(bl cache_clean_flush ),而后执行reloc_start (add pc, r5, r0 )。reloc_start 将r5 开始的kernel 重载于r4 地址处。清除cache 内容,关闭cache ,将r7 中architecture ID 赋于r1 ,执行r4 开始的kernel 代码。
然后通过长跳转指令跳至decompress_kernel
276 行:
wont_overwrite: mov r0, r4
mov r3, r7
bl decompress_kernel
b call_kernel
执行完解压过程后,再返回到head.s 执行转移语句call_kernel ,启动内核:
第482 行:
call_kernel: bl cache_clean_flush
bl cache_off
mov r0, #0 @ must be zero
mov r1, r7 @ restore architecture number
mov r2, r8 @ restore atags pointer
mov pc, r4 @ call kernel
上面分析已知道 * r4 = kernel execution address
所以mov pc, r4 语句之后,开始执行r4 开始的kernel 代码。
2.3inux/arch/arm/kernel/head.S 文件的分析
进入linux/arch/arm/kernel/head.S 文件:
文件从stext 开始:
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | MODE_SVC @ ensure svc mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
bl __create_page_tables
这里检测了cpu ID 、进程类型、机器类型以及建立并初始化页表。
然后第92 行:
ldr r13, __switch_data
__switch_data 在linux/arch/arm/kernel/common.s 文件中定义的一个地址。
之后语句#if defined(CONFIG_SMP) 为多核的初始化程序,跳过直接到第148 行:
__enable_mmu: 使能mmu ,
167 行: mcr p15, 0, r5, c3, c0, 0 @ load domain access register
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer
b __turn_mmu_on
184 行:
__turn_mmu_on:
mov r0, r0
mcr p15, 0, r0, c1, c0, 0 @ write control reg
mrc p15, 0, r3, c0, c0, 0 @ read id reg
mov r3, r3
mov r3, r3
mov pc, r13
这里打开了mmu 并且将__switch_data 地址送入pc 中,转到linux/arch/arm/kernel/common.s 文件.
2.4 arch/arm/kernel/common.s 文件的分析
__switch_data: 在第15 行,而可执行程序代码在文件35 行:
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, sp}
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r6, {r0, r4} @ Save control register values
b start_kernel
启动过程从这里开始跳转到 start_kernel 。
(最终就跳转到第二阶段去执行)