• 2.4内核启动流程及启动过程调试方法


    bios将内核拷贝到内存后,将执行权限交给内核。一般嵌入式使用的zImage格式。此种格式的首地址是一个未压缩的可执行代码入口,这段未压缩的代码主要功能一是解压缩,二是保护好bios传入的参数转给内核。那么这个过程的相关代码及文件如下(2410为例):

    arch/arm/boot/compressed/head.s

    ---调用C函数-->

    arch/arm/boot/compressed/misc.c的decompress_kernel()
    函数解压内核。

    ---跳转到解压后内核的可执行代码-->arch/arm/kernel/head-armv.s 初始化

    ---跳转到内核的C代码段-->init/main.c的asmlinkage void __init start_kernel(void)

    调试方法:在内核的console_init();函数被调用之前printk函数在内核中无效或者说打印不出来,使用的串口配置为bios所设置。console_init();函数在init/main.c文件的start_kernel(void)函数中被调用,特别的是在调用这个函数之前内核使用了printk,但是没有实际的打印输出,但在初始化console后前面的printk内容都将会打印出来。

    1.head.s :可在misc.c文件中用C函数写调试信息输出,在head.s用汇编bl调用

    2.head-armv.s :需要两个操作

    a.配置内核打开err输出功能

    Kernel hacking  ---> 

      [*] Kernel debugging

      [*]   Verbose kernel error messages

      [*]   Kernel low-level debugging functions

    b.调用文件中现有的汇编__err输入功能

    参考网友资料:

    linux2.4启动分析(1)---内核启动地址的确定 vmlinux LOAD_ADDR ZRELADDR

    http://blog.chinaunix.net/u/31100/showart_244622.html

    ================================

    Author: taoyuetao
    Email: tao_yuetao@yahoo.com.cn
    Blog: taoyuetao.cublog.cn

    2006-11-03

    ================================

    内核编译链接过程是依靠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中

    (在2.6中没有找到这个,在/arch/arm/boot/Makefile)

    LDSCRIPT     = arch/arm/vmlinux-armv.lds.in

    arch/arm/vmlinux.lds: arch/arm/Makefile $(LDSCRIPT) \
    $(wildcard include/config/cpu/32.h) \
    $(wildcard include/config/cpu/26.h) \
    $(wildcard include/config/arch/*.h)
    @echo '  Generating $@'
    @sed 's/TEXTADDR/$(TEXTADDR)/;s/DATAADDR/$(DATAADDR)/' $(LDSCRIPT) >$@

    vmlinux-armv.lds.in文件的内容:

    OUTPUT_ARCH(arm)
    ENTRY(stext)
    SECTIONS
    {
        . = TEXTADDR;
        .init : {           /* Init code and data       */
            _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
    LDSCRIPT     = arch/arm/vmlinux-armv.lds.in
    endif
    需要注意的是这里是虚拟地址而不是物理地址。

    一般情况下都在生成vmlinux后,再对内核进行压缩成为zImage,压缩的目录是kernel/arch/arm/boot。
    下载到flash中的是压缩后的zImage文件,zImage是由压缩后的vmlinux和解压缩程序组成,如下图所示:

                |-----------------|\    |-----------------|
                |                    | \   |                    |
                |                    |  \  | decompress code |
                |     vmlinux        \  |-----------------|    zImage
                |                    |    \|                    |
                |                    |     |                    |
                |                    |     |                    |   
                |                    |     |                    |
                |                    |    /|-----------------|
                |                    |   /
                |                    |  /
                |                    | /
                |-----------------|/
    zImage链接脚本也叫做vmlinux.lds,位于kernel/arch/arm/boot/compressed。
    是由同一目录下的vmlinux.lds.in文件生成的,内容如下:
    OUTPUT_ARCH(arm)
    ENTRY(_start)
    SECTIONS
    {
       . = LOAD_ADDR;   //这个在2.6中已经去掉了
       _load_addr = .;    //也去掉
       . = TEXT_START;
       _text = .;
       .text : {
         _start = .;
    其中LOAD_ADDR就是zImage中解压缩代码的ram偏移地址,TEXT_START是内核ram启动的偏移地址,这个地址是物理地址。
    在kernel/arch/arm/boot/Makefile文件中定义了:
    ZTEXTADDR   =0
    ZRELADDR     = 0xa0008000  //这个在2.6中是makefile.boot定义

    ZTEXTADDR就是解压缩代码的ram偏移地址,ZRELADDR是内核ram启动的偏移地址,这里看到指定ZTEXTADDR的地址为0,
    明显是不正确的,因为我的平台上的ram起始地址是0xa0000000,在Makefile文件中看到了对该地址设置的几行注释:
    # We now have a PIC decompressor implementation.  Decompressors running
    # from RAM should not define ZTEXTADDR.  Decompressors running directly
    # from ROM or Flash must define ZTEXTADDR (preferably via the config)
    他的意识是如果是在ram中进行解压缩时,不用指定它在ram中的运行地址,如果是在flash中就必须指定他的地址。所以
    这里将ZTEXTADDR指定为0,也就是没有真正指定地址。

    在kernel/arch/arm/boot/compressed/Makefile文件有一行脚本:

    SEDFLAGS    = s/TEXT_START/$(ZTEXTADDR)/;s/LOAD_ADDR/$(ZRELADDR)/;s/BSS_START/$(ZBSSADDR)/

    //2.6改为s/TEXT_START/$(ZTEXTADDR)/;s/BSS_START/$(ZBSSADDR)/

    使得TEXT_START = ZTEXTADDR,LOAD_ADDR = ZRELADDR。

    这样vmlinux.lds的生成过程如下:
    vmlinux.lds:    vmlinux.lds.in Makefile $(TOPDIR)/arch/$(ARCH)/boot/Makefile $(TOPDIR)/.config
    @sed "$(SEDFLAGS)" < vmlinux.lds.in > $@
    以上就是我对内核启动地址的分析,总结一下内核启动地址的设置:
    1、设置kernel/arch/arm/Makefile文件中的
       TEXTADDR     = 0xC0008000 //2.6暂时没找到
       内核启动的虚拟地址
    2、设置kernel/arch/arm/boot/Makefile文件中的
       ZRELADDR     = 0xa0008000
       内核启动的物理地址
       如果需要从flash中启动还需要设置
       ZTEXTADDR地址。

     

    linux2.4启动分析(2)---内核解压缩过程 compress booting kernel

    ================================

    Author: taoyuetao
    Email: tao_yuetao@yahoo.com.cn
    Blog: taoyuetao.cublog.cn

    2006-11-06

    ================================

    内核压缩和解压缩代码都在目录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组成的。

    (

    2.6中,在compressed/makefile里,定义了

    #生成vmlinux镜像
    targets       := vmlinux vmlinux.lds piggy.gz piggy.o font.o font.c \
             head.o misc.o $(OBJS)

    )

    在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()。将内核放于指定的位置。

    以下分析head.S文件:
    (1)对于各种Arm CPU的DEBUG输出设定,通过定义宏来统一操作。
    (2)设置kernel开始和结束地址,保存architecture ID。
    (3)如果在ARM2以上的CPU中,用的是普通用户模式,则升到超级用户模式,然后关中断。
    (4)分析LC0结构delta offset,判断是否需要重载内核地址(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的偏移量加到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代码。

    下面简单介绍一下解压缩过程,也就是函数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表示解压成功。

    我们在内核启动的开始都会看到这样的输出:
    Uncompressing Linux...done, booting the kernel.
    这也是由decompress_kernel函数内部输出的,它调用了puts()输出字符串,
    puts是在kernel/include/asm-arm/arch-pxa/uncompress.h中实现的。

    执行完解压过程,再返回到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
    下面就开始真正的内核了。

    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/lanmanck/archive/2009/06/18/4278724.aspx

     

    linux2.4启动分析(2)---内核解压缩过程(续,更详细) compress booting kernel

    自:http://blog.csdn.net/BoySKung/archive/2008/12/05/3448083.aspx#

    文件:linux-2.6.20.6/arch/arm/boot/compressed/head.S
    开头有一段宏定义,我们只看其中一段,分析一下gnu arm汇编的宏定义
    #elif defined(CONFIG_ARCH_S3C2410)
                  .macro loadsp, rb
                  mov \rb, #0x50000000
                  add  \rb, \rb, #0x4000 * CONFIG_S3C2410_LOWLEVEL_UART_PORT
                  .endm
    #else
    这里定义了一个宏,宏名是loadsp,rb是这个宏的参数。宏的参数在被引用时必须加”\”,如:
    mov \rb, #0x50000000.
    宏定义结束之后定义了一个段,
                  .section ".start", #alloc, #execinstr
    这个段的段名是 .start,#alloc表示Section contains allocated data, #execinstr表示Section contains executable instructions.
    /*
    * sort out different calling conventions
    */
                  .align
    start:
                  .type       start,#function /*.type指定start这个符号是函数类型*/
                  .rept 8
                  mov r0, r0 //将此命令重复8次,相当于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
    r1和r2中分别存放着由bootloader传递过来的architecture ID和指向标记列表的指针。这里将这两个参数先保存。
    #ifndef __ARM_ARCH_2__
                  /*
                   * Booting from Angel - need to enter SVC mode and disable
                   * FIQs/IRQs (numeric definitions from angel arm.h source).
                   * We only do this if we were in user mode on entry.
                   */
    读取cpsr并判断是否处理器处于supervisor模式——从u-boot进入kernel,系统已经处于SVC32模式;而利用angel进入则处于user模式,还需要额外两条指令。之后是再次确认中断关闭,并完成cpsr写入
    Angel 是 ARM 的调试协议,现在用的 MULTI-ICE 用的是 RDI 通讯协议, ANGLE 需要在板子上有 驻留程序,然后通过 串口就可以调试了
    这里介绍一下半主机.
    半主机是用于 ARM 目标的一种机制,可将来自应用程序代码的输入/输出请求
    传送至运行调试器的主机。 例如,使用此机制可以启用 C 库中的函数,如
    printf() 和 scanf(),来使用主机的屏幕和键盘,而不是在目标系统上配备屏幕和
    键盘。
    半主机是通过一组定义好的软件指令(如 swi)来实现的,这些指令通过程序控
    制生成异常。 应用程序调用相应的半主机调用,然后调试代理处理该异常。 调
    试代理提供与主机之间的必需通信。
                  mrs  r2, cpsr          @ get current mode
                  tst    r2, #3                    @ not user?
                  bne  not_angel
    下面两行实现了在主机和 ARM 目标之间启用调试 I/O 功能,
                  mov r0, #0x17              @ angel_SWIreason_EnterSVC
                  swi  0x123456              @ angel_SWI_ARM
    0x17是angel_SWIreason_EnterSVC半主机操作,将处理器设置为超级用户模式,通过设置新 CPSR 中的两个中断掩码位来禁用所有中断。0x123456是arm指令集的半主机操作编号
    not_angel:  //不是通过angel调试进入内核
                  mrs  r2, cpsr          @ turn off interrupts to
                  orr   r2, r2, #0xc0         @ prevent angel from running
                  msr  cpsr_c, r2   //这里将cpsr中I、F位分别置“1”,关闭IRQ和FIQ
    #else
                  teqp pc, #0x0c000003          @ turn off interrupts
    常用 TEQP PC,#(新模式编号) 来改变模式
    #endif
    链接器会把一些处理器相关的代码链接到这个位置,也就是arch/arm/boot/compressed/head-xxx.S文件中的代码。在那个文件里会对I/D cache以及MMU进行一些操作
    /*
                   * Note that some cache flushing and other stuff may
                   * be needed here - is there an Angel SWI call for this?
                   */
                  /*
                   * some architecture specific code can be inserted
                   * by the linker here, but it should preserve r7, r8, and r9.
                   */
                  .text
                  adr   r0, LC0 //当前运行时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   //的偏移量,存入r0中。
                  beq  not_relocated         @ running at the address we
                                              @ were linked at.
    上面这几行代码用于判断代码是否已经重定位到内存中,LC0这个符号在288行定义。
                  .type       LC0, #object
    LC0:              .word      LC0               @ r1 //这个要加载到r1中的LC0是链接时LC0的地址
                  .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
    通过当前运行时LC0的地址与链接器所链接的地址进行比较判断。若相等则是运行在链接的地址上。
    如果不是运行在链接的地址上,则下面的代码必须运行
                  /*
                   * 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 //修改got表的起始和结束位置
    #ifndef CONFIG_ZBOOT_ROM
                  /*若没有定义CONFIG_ZBOOT_ROM,此时运行的是完全位置无关代码
    位置无关代码,也就是不能有绝对地址寻址。所以为了保持相对地址正确,
    需要将bss段以及堆栈的地址都进行调整
                   * If we're running fully PIC === CONFIG_ZBOOT_ROM = n,
                   * we need to fix up pointers into the BSS region.
                   *   r2 - BSS start
                   *   r3 - BSS end
                   *   sp - stack pointer
                   */
                  add  r2, r2, r0
                  add  r3, r3, r0
                  add  sp, sp, r0
                  /*
                   * Relocate all entries in the GOT table.
                   */
    1:            ldr   r1, [r6, #0]            @ relocate entries in the GOT
                  add  r1, r1, r0        @ table.  This fixes up the
                  str   r1, [r6], #4            @ C references.
                  cmp r6, ip
                  blo   1b
    #else //若定义了CONFIG_ZBOOT_ROM,只对got表中在bss段以外的符号进行重定位
    //为什么要这样做呢??我也不清楚
                  /*
                   * Relocate entries in the GOT table.  We only relocate
                   * the entries that are outside the (relocated) BSS region.
                   */
    1:            ldr   r1, [r6, #0]            @ relocate entries in the GOT
                  cmp r1, r2                    @ entry < bss_start ||
                  cmphs     r3, r1                    @ _end < entry
                  addlo       r1, r1, r0        @ table.  This fixes up the
                  str   r1, [r6], #4            @ C references.
                  cmp r6, ip
                  blo   1b
    #endif
    如果运行当前运行地址和链接地址相等,则不需进行重定位。直接清除bss段
    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
    之后跳转到cache_on处
                  /*
                   * The C runtime environment should now be setup
                   * sufficiently.  Turn the cache on, set up some
                   * pointers, and start decompressing.
                   */
                  bl     cache_on
    cache_on在327行定义
                  .align       5
    cache_on:       mov r3, #8                    @ cache_on function
                  b     call_cache_fn
    把r3的值设为8有什么用呢?下面会看到。这里又跳转到call_cache_fn。这个函数的定义在512行
    call_cache_fn: adr   r12, proc_types  //把proc_types的地址加载到r12中
    #ifdef CONFIG_CPU_CP15
                  mrc p15, 0, r6, c0, c0   @ get processor ID
    #else
                  ldr   r6, =CONFIG_PROCESSOR_ID
    #endif
    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
    这一段代码首先获得当前处理器id,然后查proc_types表,也就是处理器类型表与获得的处理器id进行比较,当找到相应的处理器后,就加载对应的cache处理函数。
    addeq      pc, r12, r3             @ call cache function
    这里用到了上面说的r3,他的值是8,也就是一个偏移量,r12中存储的是某个处理器相关处理模块的基地址。
    proc_type的定义如下,在541行
                  .type       proc_types,#object
    proc_types:
                  .word      0x41560600           @ ARM6/610
                  .word      0xffffffe0
                  b     __arm6_mmu_cache_off      @ works, but slow
                  b     __arm6_mmu_cache_off
                  mov pc, lr
    @           b     __arm6_mmu_cache_on              @ untested
    @           b     __arm6_mmu_cache_off
    @           b     __armv3_mmu_cache_flush
                  .word      0x00000000           @ old ARM ID
                  .word      0x0000f000
                  mov pc, lr
                  mov pc, lr
                  mov pc, lr
                  .word      0x41007000           @ ARM7/710
                  .word      0xfff8fe00
                  b     __arm7_mmu_cache_off
                  b     __arm7_mmu_cache_off
                  mov pc, lr
                  .word      0x41807200           @ ARM720T (writethrough)
                  .word      0xffffff00
                  b     __armv4_mmu_cache_on
                  b     __armv4_mmu_cache_off
                  mov pc, lr
                  .word      0x41007400           @ ARM74x
                  .word      0xff00ff00
                  b     __armv3_mpu_cache_on
                  b     __armv3_mpu_cache_off
                  b     __armv3_mpu_cache_flush
                  .word      0x41009400           @ ARM94x
                  .word      0xff00ff00
                  b     __armv4_mpu_cache_on
                  b     __armv4_mpu_cache_off
                  b     __armv4_mpu_cache_flush
                  .word      0x00007000           @ ARM7 IDs
                  .word      0x0000f000
                  mov pc, lr
                  mov pc, lr
                  mov pc, lr
                  @ Everything from here on will be the new ID system.
                  .word      0x4401a100           @ sa110 / sa1100
                  .word      0xffffffe0
                  b     __armv4_mmu_cache_on
                  b     __armv4_mmu_cache_off
                  b     __armv4_mmu_cache_flush
                  .word      0x6901b110           @ sa1110
                  .word      0xfffffff0
                  b     __armv4_mmu_cache_on
                  b     __armv4_mmu_cache_off
                  b     __armv4_mmu_cache_flush
                  @ These match on the architecture ID
                  .word      0x00020000    @ ARMv4T //这个就是我们要找的arm920t的处理器相关数
                  .word      0x000f0000        //据,偏移8后刚好是b   __armv4_mmu_cache_on
                  b     __armv4_mmu_cache_on  //指令的地址
                  b     __armv4_mmu_cache_off
                  b     __armv4_mmu_cache_flush
                  .word      0x00050000           @ ARMv5TE
                  .word      0x000f0000
                  b     __armv4_mmu_cache_on
                  b     __armv4_mmu_cache_off
                  b     __armv4_mmu_cache_flush
                  .word      0x00060000           @ ARMv5TEJ
                  .word      0x000f0000
                  b     __armv4_mmu_cache_on
                  b     __armv4_mmu_cache_off
                  b     __armv4_mmu_cache_flush
                  .word      0x0007b000           @ ARMv6
                  .word      0x0007f000
                  b     __armv4_mmu_cache_on
                  b     __armv4_mmu_cache_off
                  b     __armv6_mmu_cache_flush
                  .word      0                   @ unrecognised type
                  .word      0
                  mov pc, lr
                  mov pc, lr
                  mov pc, lr
                  .size proc_types, . - proc_types
    当找到我和我们处理器后,就调用相应的处理函数,我根据我们的arm920t处理器,这里应该调用__armv4_mmu_cache_on,这句调用指令在605行
                  .word      0x00020000           @ ARMv4T
                  .word      0x000f0000
                  b     __armv4_mmu_cache_on
                  b     __armv4_mmu_cache_off
                  b     __armv4_mmu_cache_flush
    __armv4_mmu_cache_on的在424行定义,
    __armv4_mmu_cache_on:
                  mov r12, lr
                  bl     __setup_mmu
                  mov r0, #0
                  mcr p15, 0, r0, c7, c10, 4     @ drain write buffer
                  mcr p15, 0, r0, c8, c7, 0      @ flush I,D TLBs
                  mrc p15, 0, r0, c1, c0, 0      @ read control reg
                  orr   r0, r0, #0x5000             @ I-cache enable, RR cache replacement
                  orr   r0, r0, #0x0030   
                  bl     __common_mmu_cache_on
                  mov r0, #0
                  mcr p15, 0, r0, c8, c7, 0      @ flush I,D TLBs
                  mov pc, r12  //返回到cache_on
    这里首跳转到__setup_mmu,然后清空write buffer、I/Dcache、TLB.接着打开i-cache,设置为Round-robin replacement。调用__common_mmu_cache_on,打开mmu和d-cache.把页表基地址和域访问控制写入协处理器寄存器 c2、c3. __common_mmu_cache_on函数数定义在450行。
    __common_mmu_cache_on:
    #ifndef DEBUG
                  orr   r0, r0, #0x000d             @ Write buffer, mmu
    #endif
                  mov r1, #-1 //-1的补码是ffff ffff,
                  mcr p15, 0, r3, c2, c0, 0      @ load page table pointer
                  mcr p15, 0, r1, c3, c0, 0      @ load domain access control //将domain access control寄存
                  b     1f                                 //全部置’1’
                  .align       5                   @ cache line aligned
    1:            mcr p15, 0, r0, c1, c0, 0      @ load control register
                  mrc p15, 0, r0, c1, c0, 0      @ and read it back to
                  sub  pc, lr, r0, lsr #32    @ properly flush pipeline
    重占来看一下__setup_mmu这个函数,定义在386行
    __setup_mmu:       sub  r3, r4, #16384        @ Page directory size
                  bic   r3, r3, #0xff          @ Align the pointer
                  bic   r3, r3, #0x3f00
    这里r4中存放着内核执行地址,将16K的一级页表放在这个内核执行地址下面的16K空间里,上面通过 sub  r3, r4, #16384  获得16K空间后,又将页表的起始地址进行16K对齐放在r3中。即ttb的低14位清零。
    /*
    * Initialise the page tables, turning on the cacheable and bufferable
    * bits for the RAM area only.
    */
    //初始化页表,并在RAM空间里打开cacheable 和bufferable位
                  mov r0, r3
                  mov r9, r0, lsr #18
                  mov r9, r9, lsl #18         @ start of RAM
                  add  r10, r9, #0x10000000    @ a reasonable RAM size
    上面这几行把一级页表的起始地址保存在r0中,并通过r0获得一个ram起始地址(256K对齐),并从这个起始地址开始的256M ram空间对应的描述符的C和B位均置”1” (参考arm920t datasheet 3.3.3, table 3-2 level one descryiptor bits), r9和r10中存放了这段内存的起始地址和结束地址
                  mov r1, #0x12 //一级描述符的bit[1:0]为10,表示这是一个section描述符。bit[4]
    //为1(参考arm9205 datasheet 3.3.3 table 3-2 level one
    //descryiptor bits)此时bit[8:5]均为0,选择了D0域。
                  orr   r1, r1, #3 << 10 //一级描述符的access permission bits bit[11:10]为11. 即
                             //all access types permitted in both modes
    // (参考arm920t datasheet 3.3.3, table 3-2 level
    //one descryiptor bits, 3.6, table 3-11 interpreting access
    // permission(AP) bit)
                  add  r2, r3, #16384  //一级描述符表的结束地址存放在r2中。
    1:            cmp r1, r9                    @ if virt > start of RAM
                  orrhs       r1, r1, #0x0c         @ set cacheable, bufferable
                  cmp r1, r10                  @ if virt > end of RAM
                  bichs       r1, r1, #0x0c         @ clear cacheable, bufferable
                  str   r1, [r0], #4            @ 1:1 mapping
                  add  r1, r1, #1048576
                  teq   r0, r2
                  bne  1b
    上面这段就是对一级描述符表(页表)的初始化,首先比较这个描述符所描述的地址是否在那个256M的空间中,如果在则这个描述符对应的内存区域是 cacheable ,bufferable。如果不在则noncacheable, nonbufferable.然后将描述符写入一个一级描述符表的入口,并将一级描述符表入口地址加4,而指向下一个1M section的基地址。如果页表入口未初始化完,则继续初始化。
    一级描述符表的高12位是每个setcion的基地址,可以描述4096个section。一级页表大小为16K,每个页表项,即描述符占4字节,刚好可以容纳4096个描述符,所以这里就映射了4096*1M = 4G的空间。
    /*
    * If ever we are running from Flash, then we surely want the cache
    * to be enabled also for our execution instance...  We map 2MB of it
    * so there is no map overlap problem for up to 1 MB compressed kernel.
    * If the execution is in RAM then we would only be duplicating the above.
    */
                  mov r1, #0x1e
                  orr   r1, r1, #3 << 10 //这两行将描述的bit[11:10] bit[4:1]置位,(参考arm920t
    // datasheet 3.3.3, table 3-2 level one descryiptor bits)
                  mov r2, pc, lsr #20
                  orr   r1, r1, r2, lsl #20  //将当前地址进1M对齐,并与r1中的内容结合形成一个
                                //描述当前指令所在section的描述符。
                  add  r0, r3, r2, lsl #2   //r3为刚才建立的一级描述符表的起始地址。通过将当前地
    //址(pc)的高12位左移两位(形成14位索引)与r3中的地址
                                // (低14位为0)相加形成一个4字节对齐的地址,这个
                                //地址也在16K的一级描述符表内。当前地址对应的
                                //描述符在一级页表中的位置
                  str   r1, [r0], #4
                  add  r1, r1, #1048576
                  str   r1, [r0]          //这里将上面形成的描述符及其连续的下一个section描述
    //写入上面4字节对齐地址处(一级页表中索引为r2左移
    //2位)
                  mov pc, lr       //返回,调用此函数时,调用指令的下一语句mov   r0, #0的地
                           //址保存在lr中
    这里进行的是1:1的映射,物理地址和虚拟地址是一样。
    __common_mmu_cache_on:执行完后返回到bl cache_on下一条指令处226行,
                  mov r1, sp                    @ malloc space above stack
                  add  r2, sp, #0x10000    @ 64k max
    /*
    * Check to see if we will overwrite ourselves.
    *   r4 = final kernel address
    *   r5 = start of this image
    *   r2 = end of malloc space (and therefore this image)
    * We basically want:
    *   r4 >= r2 -> OK
    *   r4 + image length <= r5 -> OK
    */
                  cmp r4, r2
                  bhs  wont_overwrite
                  sub  r3, sp, r5        @ > compressed kernel size
                  add  r0, r4, r3, lsl #2      @ allow for 4x expansion
                  cmp r0, r5
                  bls   wont_overwrite
    这段代码首先在堆栈上确定了64K的malloc空间,空间的起始地址和结束地址分别存放在r1、r2中。然后判断最终内核地址,也就是解压后内核的起始地址,是否大于malloc空间的结束地址,如果大于就跳到wont_overwrite执行,wont_overwrite函数后面会讲到。否则,检查最终内核地址加解压后内核大小,也就是解压后内核的结束地址,是否小于现在未解压内核映像的起始地址。小于也会跳到wont_owerwrite执行。如两这两个条件都不满足,则继续往下执行。
                  mov r5, r2                    @ decompress after malloc space
                  mov r0, r5
                  mov r3, r7
                  bl     decompress_kernel
    这里将解压后内核的起始地址设为malloc空间的结束地址。然后后把处理器id(开始时保存在r7中)保存到r3中,调用 decompress_kernel开始解压内核。这个函数的四个参数分别存放在r0-r3中,它在arch/arm/boot/compressed /misc.c中定义。
                  add  r0, r0, #127
                  bic   r0, r0, #127           @ align the kernel length
    /*
    * 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
    */
                  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
    重定位结束后跳到解压后内核的起始处开始执行,在运行解压后内核之前,先调用了
    cache_clean_flush这个函数。这个函数的定义在第700行
    cache_clean_flush:
                  mov r3, #16
                  b     call_cache_fn
    其实这里又调用了call_cache_fn这个函数,注意,这里r3的值为16,call_cache_fn这个函数在前面有讲解,下面看看当r3为16时会调用到哪个函数,回到proc_types这个对像的定义,最终找到处理器相关的处理代码在603行开始
                  .word      0x00020000           @ ARMv4T
                  .word      0x000f0000
                  b     __armv4_mmu_cache_on
                  b     __armv4_mmu_cache_off
                  b     __armv4_mmu_cache_flush
    当偏移量为16时,会跳到b       __armv4_mmu_cache_flush这条指令,调用__armv4_mmu_cache_flush这个函数,它的定义在730行
    __armv4_mmu_cache_flush:
                  mov r2, #64*1024         @ default: 32K dcache size (*2)
                  mov r11, #32         @ default: 32 byte line size
                  mrc p15, 0, r3, c0, c0, 1      @ read cache type
                  teq   r3, r6                    @ cache ID register present?
                  beq  no_cache_id
                  mov r1, r3, lsr #18
                  and  r1, r1, #7  //获得Dsize中的size
                  mov r2, #1024
                  mov r2, r2, lsl r1           @ base dcache size *2//获得dcache字节大小
                  tst    r3, #1 << 14          @ test M bit
                  addne      r2, r2, r2, lsr #1     @ +1/2 size if M == 1
                  mov r3, r3, lsr #12
                  and  r3, r3, #3  //上两句获得Dsize中 cache line的长度len
                  mov r11, #8
                  mov r11, r11, lsl r3 @ cache line size in bytes //cache line的字节长度
    no_cache_id:
                  bic   r1, pc, #63            @ align to longest cache line
                  add  r2, r1, r2
    1:            ldr   r3, [r1], r11           @ s/w flush D cache 这个是指什么呢??
                  teq   r1, r2
                  bne  1b
    上面这几句做了什么呢?为什么要这么做呢?
                  mcr p15, 0, r1, c7, c5, 0      @ flush I cache
                  mcr p15, 0, r1, c7, c6, 0      @ flush D cache
                  mcr p15, 0, r1, c7, c10, 4     @ drain WB
                  mov pc, lr
    这里主要还是刷新I/Dcache和写缓冲。
    下面看看前面提到的wont_overwrite函数。这个函数在282行定义
    wont_overwrite:     mov r0, r4
                  mov r3, r7
                  bl     decompress_kernel
                  b     call_kernel
    同样,这里先设置好decompress_kernel的参数,然后调用decompress_kernel解压内核映像。然后调用call_kernel函数。此函数在491行定义
    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
    这里也是先调用cache_clean_flush刷新i/d-cache,然后调用cashe_off函数。最后设置好参数,跳到解压后的内核执行。
    cashe_off函数在644行定义
    cache_off:      mov r3, #12                  @ cache_off function
                  b     call_cache_fn
    同样又是调用call_cache_fn函数,注意,这里r3的值是12,也就是偏移量是12,最终通过call_cache_fn找到603行的一段代码
                  .word      0x00020000           @ ARMv4T
                  .word      0x000f0000
                  b     __armv4_mmu_cache_on
                  b     __armv4_mmu_cache_off
                  b     __armv4_mmu_cache_flush
    因这里的偏移量是12,所以将执行b       __armv4_mmu_cache_off指令,调用__armv4_mmu_cache_off函数,这个函数在665行定义。
    __armv4_mmu_cache_off:
                  mrc p15, 0, r0, c1, c0
                  bic   r0, r0, #0x000d
                  mcr p15, 0, r0, c1, c0   @ turn MMU and cache off
                  mov r0, #0
                  mcr p15, 0, r0, c7, c7   @ invalidate whole cache v4
                  mcr p15, 0, r0, c8, c7   @ invalidate whole TLB v4
                  mov pc, lr
    这里首先读控制寄存器,然后关闭icache和mmu,接着使全部cache和tlb无效。
    现在总结一下在进入解压后的内核入口前都做了些什么(解压后的kernel入口在arch/arm/kernel/head.S中):
    首先保存从uboot中传入的参数,然后会执行一段处理器相关的代码位于arch/arm/boot/compressed/head-xxx.S中,这段代码我们这里没有分析,在移植内核时会对这段代码作出分析。接着会判断一下要不要重定位,我们这里是不需要重定位,所以开始对bss段清零。之后初始化页表,进行1:1映射。因为打开cache前必须打开mmu,所以这里先对页表进行初始化,然后打开mmu和cache。这些都准备好后,判断一下解压内核是否会覆盖未解压的内核映像。如果会,则进行一些调整,然后开始解压内核;如果不会,则直接解压。最后是刷新cache,关闭mmu和dcache,使 cache和tlb内容无效,跳到解压后的内核入口执行arm相关的内核代码。

    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/lanmanck/archive/2009/06/19/4282395.aspx

  • 相关阅读:
    vue强制更新$forceUpdate()
    js数组拼接成字符串,去除最后一个逗号
    JavaScript数组遍历:for、foreach、for in、for of、、().each的区别
    json.stringify()的妙用,json.stringify()与json.parse()的区别
    第四次博客作业-结对项目
    于达——第九次作业
    于达——第八次作业
    软件工程第三次作业——关于软件质量保障初探
    于达——第七次作业
    于达——第六次作业
  • 原文地址:https://www.cnblogs.com/leaven/p/1802071.html
Copyright © 2020-2023  润新知