3.linux启动流程分析(代码分析)

来源:互联网 发布:scala编程思想怎样 编辑:程序博客网 时间:2024/06/06 20:34
  一 bootloader启动内核过程

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

    Author: taoyuetao

    Email:

    [email=tao_yuetao@yahoo.com.cn]tao_yuetao@yahoo.com.cn[/email]

    Blog:

    http://www.eetop.cn/blog/?11145

    2006-11-01

    

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

    我分析的是2.4.19的内核版本,是xscale的平台,参考了网上很多有价值的帖子,也加入了自己的一些看法,

    陆续总结成文字,今天是第一篇:

    内核一般是由bootloader来引导的,通过bootloader启动内核一般要传递三个参数,

    第一个参数放在寄存器0中,一般都为0,r0 = 0;

    第二个参数放在寄存器1中,是机器类型id,r1 = Machine Type Number;

    第三个参数放在寄存器2中,是启动参数标记列表在ram中的起始基地址;

    bootloader首先要将ramdisk(如果有)和内核拷贝到ram当中,然后可以通过c语言的模式启动内核:

    void (*startkernel)(int zero, int arch, unsigned int params_addr) = (void(*)(int, int, unsigned int))KERNEL_RAM_BASE;

    startkernel(0, ARCH_NUMBER, (unsigned int)kernel_params_start);

    其中KERNEL_RAM_BASE为内核在ram中启动的地址,ARCH_NUMBER是Machine Type Number,kernel_params_start是参数在ram的偏移地址。

    这时候就将全力交给了内核。

    二 内核启动地址的确定

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

    Author: taoyuetao

    Email:

    [email=tao_yuetao@yahoo.com.cn]tao_yuetao@yahoo.com.cn[/email]

    Blog:

    http://www.eetop.cn/blog/?11145

    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中

    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

    [email=$@']$@'[/email]

     @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;

     _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文件中看到了对该地址设置的几行注释:

    # 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)/

    使得TEXT_START = ZTEXTADDR,LOAD_ADDR = ZRELADDR。

    这样vmlinux.lds的生成过程如下:

    vmlinux.lds: vmlinux.lds.in Makefile $(TOPDIR)/arch/$(ARCH)/boot/Makefile $(TOPDIR)/.config

     @sed "$(SEDFLAGS)" $@

    

    以上就是我对内核启动地址的分析,总结一下内核启动地址的设置:

    1、设置kernel/arch/arm/Makefile文件中的

     TEXTADDR = 0xC0008000

     内核启动的虚拟地址

    2、设置kernel/arch/arm/boot/Makefile文件中的

     ZRELADDR = 0xa0008000

     内核启动的物理地址

     如果需要从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()进行设置,

    然后使用在打印出信息“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

    

    下面就开始真正的内核了。

    四 汇编部分(1)

    在网上参考很多高手的文章,又加入了自己的一点儿内容,整理了一下,里面还有很多不明白的地方,而且也会有理解错误

    的地方,望高手指点,自己也会不断进行修改

    当进入linux内核后,arch/arm/kernel/head-armv.S是内核最先执行的一个文件,包括从内核入口ENTRY(stext)到

    start_kernel之间的初始化代码,下面以我所是用的平台intel pxa270为例,说明一下他的汇编代码:

    1 .section ".text.init",#alloc,#execinstr

    2 .type stext, #function

    /* 内核入口点 */

    3 ENTRY(stext)

    4 mov r12, r0

    /* 程序状态,禁止FIQ、IRQ,设定SVC模式 */

    5 mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode

    6 msr cpsr_c, r0 @ and all irqs disabled

    /* 判断CPU类型,查找运行的CPU ID值与Linux编译支持的ID值是否支持 */

    7 bl __lookup_processor_type

    /* 判断如果r10的值为0,则表示函数执行错误,跳转到出错处理,*/

    /* 出错处理函数__error的实现代码定义在debug-armv.S中,这里就不再作过多介绍了 */

    8 teq r10, #0 @ invalid processor?

    9 moveq r0, #'p' @ yes, error 'p'

    10 beq __error

    /* 判断体系类型,查看R1寄存器的Architecture Type值是否支持 */

    11 bl __lookup_architecture_type

    /* 判断如果r7的值为0,则表示函数执行错误,跳转到出错处理,*/

    12 teq r7, #0 @ invalid architecture?

    13 moveq r0, #'a' @ yes, error 'a'

    14 beq __error

    /* 创建核心页表 */

    15 bl __create_page_tables

    16 adr lr, __ret @ return address

    17 add pc, r10, #12 @ initialise processor

     @ (return control reg)

    

    第5行,准备进入SVC工作模式,同时关闭中断(I_BIT)和快速中断(F_BIT)

    第7行,查看处理器类型,主要是为了得到处理器的ID以及页表的flags。

    第11行,查看一些体系结构的信息。

    第15行,建立页表。

    第17行,跳转到处理器的初始化函数,其函数地址是从__lookup_processor_type中得到的,

    需要注意的是第16行,当处理器初始化完成后,会直接跳转到__ret去执行,

    这是由于初始化函数最后的语句是mov pc, lr。

    四 汇编部分(2)

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

    Author: taoyuetao

    Email:

    [email=tao_yuetao@yahoo.com.cn]tao_yuetao@yahoo.com.cn[/email]

    Blog:

    http://www.eetop.cn/blog/?11145

    2007-02-14

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

    前面一篇文章,简单介绍了内核启动的汇编主流程,这篇介绍其中调用的汇编子函数__lookup_processor_type

    函数__lookup_processor_type介绍:

    内核中使用了一个结构struct proc_info_list,用来记录处理器相关的信息,该结构定义在

    kernel/include/asm-arm/procinfo.h头文件中。

    /*

     * Note! struct processor is always defined if we're

     * using MULTI_CPU, otherwise this entry is unused,

     * but still exists.

     *

     * NOTE! The following structure is defined by assembly

     * language, NOT C code. For more information, check:

     * arch/arm/mm/proc-*.S and arch/arm/kernel/head-armv.S

     */

    struct proc_info_list {

     unsigned int cpu_val;

     unsigned int cpu_mask;

     unsigned long __cpu_mmu_flags; /* used by head-armv.S */

     unsigned long __cpu_flush; /* used by head-armv.S */

     const char *arch_name;

     const char *elf_name;

     unsigned int elf_hwcap;

     struct proc_info_item *info;

     struct processor *proc;

    };

    在arch/arm/mm/proc-xscale.S文件中定义了所有和xscale有关的proc_info_list,我们使用的pxa270定义如下:

    .section ".proc.info", #alloc, #execinstr

    .type __bva0_proc_info,#object

    __bva0_proc_info:

     .long 0x69054110 @ Bulverde A0: 0x69054110, A1 : 0x69054111.

     .long 0xfffffff0 @ and this is the CPU id mask.

    #if CACHE_WRITE_THROUGH

     .long 0x00000c0a

    #else

     .long 0x00000c0e

    #endif

     b __xscale_setup

     .long cpu_arch_name

     .long cpu_elf_name

     .long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_XSCALE

     .long cpu_bva0_info

     .long xscale_processor_functions

     .size __bva0_proc_info, . - __bva0_proc_info

    

    由于.section指示符,上面定义的__bva0_proc_info信息在编译的时候被放到了.proc.info段中,这是由linux的

    链接脚本文件vmlinux.lds指定的,参考如下:

     SECTIONS

     {

     . = 0xC0008000;

     .init : { /* Init code and data */

     _stext = .;

     __init_begin = .;

     *(.text.init)

     __proc_info_begin = .;

     *(.proc.info)

     __proc_info_end = .;

    

    这里的符号__proc_info_begin指向.proc.info的起始地址,而符号__proc_info_end指向.proc.info的结束地址。

    后面就会引用这两个符号,来指向.proc.info这个段。

    

    下面来来看看函数的源代码,为了分析方便将函数按行进行编号,其中17-18行就是前面提到的对.proc.info的引用,

    第2行将17行的地址放到寄存器r5中,adr是小范围的地址读取伪指令。第3行将r5所指向的数据区的数据读出到r7,r9

    r10,执行结果是r7=__proc_info_end,r9=__proc_info_begin,r10=第19行的地址,第4-6行的结果应该是r10指向

    __proc_info_begin的地址,第7行读取cpu的id,这是一个协处理器指令,将processor ID存储在r9中,第8行将r10指向

    的__bva0_proc_info开始的数据读出放到寄存器r5,r6,r8,结果r5=0x69054110(cpu_val),r6=0xfffffff0(cpu_mask),

    r8=0x00000c0e(__cpu_mmu_flags),第9-10行将读出的id和结构中的id进行比较,如果id相同则返回,返回时r9存储

    processor ID,如果id不匹配,则将指针r10增加36(proc_info_list结构的长度),如果r10小于r7指定的地址,也就是

    __proc_info_end,则继续循环比较下一个proc_info_list中的id,如第11-14行的代码,如果查找到__proc_info_end

    仍未找到一个匹配的id,则将r10清零并返回,如15-16行,也就是说如果函数执行成功则r10指向匹配的proc_info_list

    结构地址,如果函数返回错误则r10为0。

    /*

     * Read processor ID register (CP#15, CR0), and look up in the linker-built

     * supported processor list. Note that we can't use the absolute addresses

     * for the __proc_info lists since we aren't running with the MMU on

     * (and therefore, we are not in the correct address space). We have to

     * calculate the offset.

     *

     * Returns:

     * r5, r6, r7 corrupted

     * r8 = page table flags

     * r9 = processor ID

     * r10 = pointer to processor structure

     */

    1 __lookup_processor_type:

    2 adr r5, 2f

    3 ldmia r5, {r7, r9, r10}

    4 sub r5, r5, r10 @ convert addresses

    5 add r7, r7, r5 @ to our address space

    6 add r10, r9, r5

    7 mrc p15, 0, r9, c0, c0 @ get processor id

    8 1: ldmia r10, {r5, r6, r8} @ value, mask, mmuflags

    9 and r6, r6, r9 @ mask wanted bits

    10 teq r5, r6

    11 moveq pc, lr

    12 add r10, r10, #36 @ sizeof(proc_info_list)

    13 cmp r10, r7

    14 blt 1b

    15 mov r10, #0 @ unknown processor

    16 mov pc, lr

    

    /*

     * Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for

     * more information about the __proc_info and __arch_info structures.

     */

    17 2: .long __proc_info_end

    18 .long __proc_info_begin

    19 .long 2b

    20 .long __arch_info_begin

    21 .long __arch_info_end

    四 汇编部分(3)

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

    Author: taoyuetao

    Email:

    [email=tao_yuetao@yahoo.com.cn]tao_yuetao@yahoo.com.cn[/email]

    Blog:

    http://www.eetop.cn/blog/?11145

    2007-03-08

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

    前一篇介绍了汇编函数__lookup_processor_type,这一篇介绍__lookup_architecture_type函数

    函数__lookup_architecture_type介绍:

    每个机器(一般指的是某一个电路板)都有自己的特殊结构,如物理内存地址,物理I/O地址,显存起始地址等等,

    这个结构为struct machine_desc,定义在asm-arm/mach/arch.h中:

    struct machine_desc {

    /*

    * Note! The first four elements are used

    * by assembler code in head-armv.S

    */

    unsigned intnr;/* architecture number*/

    unsigned intphys_ram;/* start of physical ram */

    unsigned intphys_io;/* start of physical io*/

    unsigned intio_pg_offst;/* byte offset for io page table entry*/

    const char*name;/* architecture name*/

    unsigned intparam_offset;/* parameter page*/

    unsigned intvideo_start;/* start of video RAM*/

    unsigned intvideo_end;/* end of video RAM*/

    unsigned intreserve_lp0 :1;/* never has lp0*/,

    unsigned intreserve_lp1 :1;/* never has lp1*/

    unsigned intreserve_lp2 :1;/* never has lp2*/

    unsigned intsoft_reboot :1;/* soft reboot*/

    void(*fixup)(struct machine_desc *,

    struct param_struct *, char **,

    struct meminfo *);

    void(*map_io)(void);/* IO mapping function*/

    void(*init_irq)(void);

    };

    这个结构一般都定义在(以arm平台为例)kernel\arch\arm\mach-xxx\xxx.c中,是用宏来定义的,以mainstone的开发板为例:

    定义在kernel\arch\arm\mach-pxa\mainstone.c文件中,如下所示:

    MACHINE_START(MAINSTONE, "Intel DBBVA0 Development Platform")

     MAINTAINER("MontaVista Software Inc.")

     BOOT_MEM(0xa0000000, 0x40000000, io_p2v(0x40000000))

     FIXUP(fixup_mainstone)

     MAPIO(mainstone_map_io)

     INITIRQ(mainstone_init_irq)

    MACHINE_END

    这些宏也定义在kernel/include/asm-arm/mach/arch.h中,以MACHINE_START为例:

    #define MACHINE_START(_type,_name) \

    const struct machine_desc __mach_desc_##_type \

    __attribute__((__section__(".arch.info"))) = { \

     .nr = MACH_TYPE_##_type, \

     .name = _name,

    展开之后结构的是:

    __mach_desc_MAINSTONE = {

     .nr = MACH_TYPE_MAINSTIONE,

     .name = "Intel DBBVA0 Development Platform",

    中间的1行__attribute__((__section__(".arch.info"))) = {说明将这个结构放到指定的段.arch.info中,这和前面的

    .proc.info是一个意思,__attribute__((__section__的含义参考GNU手册。后面的宏都是类似的含义,这里就不再一一

    介绍。下面开始说明源码:

    第1行实现r4指向2b的地址,2b如__lookup_processor_type介绍的第19行,将machine_desc结构中的数据存放到r2, r3, r5, r6, r7。

    读取__mach_desc_MAINSTONE结构中的nr参数到r5中,如第7行,比较r5和r1中的机器编号是否相同,如第8行,

    r5中的nr值MACH_TYPE_MAINSTONE定义在kernel\include\asm-arm\mach-types.h中:

    #define MACH_TYPE_MAINSTONE 303

    r1中的值是由bootloader传递过来的,这在>中有说明,

    如果机器编号相同,跳到15行执行,r5=intphys_ram,r6=intphys_io,r7=intio_pg_offst,并返回。如果

    不同则将地址指针增加,在跳到7行继续查找,如10--12行的代码,如果检索完所有的machine_desc仍然没

    有找到则将r7清零并返回。

    /*

     * Lookup machine architecture in the linker-build list of architectures.

     * Note that we can't use the absolute addresses for the __arch_info

     * lists since we aren't running with the MMU on (and therefore, we are

     * not in the correct address space). We have to calculate the offset.

     *

     * r1 = machine architecture number

     * Returns:

     * r2, r3, r4 corrupted

     * r5 = physical start address of RAM

     * r6 = physical address of IO

     * r7 = byte offset into page tables for IO

     */

    1 __lookup_architecture_type:

    2 adr r4, 2b

    3 ldmia r4, {r2, r3, r5, r6, r7} @ throw away r2, r3

    4 sub r5, r4, r5 @ convert addresses

    5 add r4, r6, r5 @ to our address space

    6 add r7, r7, r5

    7 1: ldr r5, [r4] @ get machine type

    8 teq r5, r1

    9 beq 2f

    10 add r4, r4, #SIZEOF_MACHINE_DESC

    11 cmp r4, r7

    12 blt 1b

    13 mov r7, #0 @ unknown architecture

    14 mov pc, lr

    15 2: ldmib r4, {r5, r6, r7} @ found, get results

    16 mov pc, lr

    四 汇编部分(4)

    函数__create_page_tables介绍:

    假设内核起始物理地址是0xA0008000,虚拟地址是0xC0008000,下面的代码是建立内核起始处4MB空间的映射,

    采用了一级映射方式,即段式(section)映射方式,每段映射范围为1MB空间。于是需要建立4个表项,实现:

    虚拟地址0xC0000000~0xC0300000,映射到物理地址0xA0000000~0xA0300000。

     .macro pgtbl, reg, rambase

     adr \reg, stext

     sub \reg, \reg, #0x4000

     .endm

    

     .macro krnladr, rd, pgtable, rambase

     bic \rd, \pgtable, #0x000ff000

     .endm

    

    /*

     * Setup the initial page tables. We only setup the barest

     * amount which are required to get the kernel running, which

     * generally means mapping in the kernel code.

     *

     * We only map in 4MB of RAM, which should be sufficient in

     * all cases.

     *

     * r5 = physical address of start of RAM

     * r6 = physical IO address

     * r7 = byte offset into page tables for IO

     * r8 = page table