Chinaunix首页 | 论坛 | 博客
  • 博客访问: 62276
  • 博文数量: 28
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2014-11-30 01:24
文章分类
文章存档

2017年(1)

2016年(5)

2015年(22)

我的朋友

分类: LINUX

2015-08-13 15:42:46

原文地址: Arm 的 linux 的启动分析 作者:fcdd_linux

基于 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 


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 。 
(最终就跳转到第二阶段去执行)


阅读(555) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~