设为首页 收藏本站
查看: 583|回复: 0

[经验分享] ARM linux启动分析

[复制链接]

尚未签到

发表于 2015-12-11 09:15:59 | 显示全部楼层 |阅读模式
文章摘自:http://blog.csdn.net/yhmhappy2006/article/details/2775239  linux启动分析(1)---bootloader启动内核过程    我分析的是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的偏移地址。
  这时候就将全力交给了内核。
  linux启动分析(2)---内核启动地址的确定    内核编译链接过程是依靠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 $@'   
@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)" < vmlinux.lds.in > $@   
以上就是我对内核启动地址的分析,总结一下内核启动地址的设置:   
1、设置kernel/arch/arm/Makefile文件中的   
   TEXTADDR     = 0xC0008000   
内核启动的虚拟地址   
2、设置kernel/arch/arm/boot/Makefile文件中的   
   ZRELADDR     = 0xa0008000   
内核启动的物理地址   
如果需要从flash中启动还需要设置   
   ZTEXTADDR地址。
  linux启动分析(3)---内核解压缩过程    内核压缩和解压缩代码都在目录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   
下面就开始真正的内核了。
  linux启动分析(4)---汇编部分(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。
  linux启动分析(4)---汇编部分(2)    前面一篇文章,简单介绍了内核启动的汇编主流程,这篇介绍其中调用的汇编子函数__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
  linux启动分析(4)---汇编部分(3)    前一篇介绍了汇编函数__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
  linux启动分析(4)---汇编部分(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 flags            
*/     
1 __create_page_tables:   
/* r5中存放着内核启动的地址0xa0008000 */   
/* pgtbl将启动地址减去0x4000,存放到r4=0xa0004000 */   
2         pgtbl   r4, r5              @ page table address   
/*   
* Clear the 16K level 1 swapper page table   
*/     
/* r0 = 0xa0004000 */   
3         mov r0, r4   
4         mov r3, #0         
/* r2 = 0xa0008000 */   
5         add r2, r0, #0x4000   
/* 清除16k空间,addr 0xa0004000: 0xa0008000 is page table, total 16K*/   
6 1:      str r3, [r0], #4   
7         str r3, [r0], #4   
8         str r3, [r0], #4   
9         str r3, [r0], #4   
10        teq r0, r2   
11        bne 1b  
/*   
* Create identity mapping for first MB of kernel to   
* cater for the MMU enable.  This identity mapping   
* will be removed by paging_init()   
*/   
/* r2 = 0xa0040000 & 0x000ff000 = 0xa00000000 */   
12        krnladr r2, r4, r5          @ start of kernel   
/* r3 = 0xa0000000 + 0x00000c0e = 0xa00000c0e */   
/* r8 = 0x00000c0e在__lookup_processor_type函数中初始化 */   
13       add r3, r8, r2          @ flags + kernel base   
/* value r3=0xa0000c0e store to addr 0xa0006800*/   
/* r4 = 0xa0006800 */   
14        str r3, [r4, r2, lsr #18]       @ identity mapping   
/*   
* Now setup the pagetables for our kernel direct   
* mapped region.  We round TEXTADDR down to the   
* nearest megabyte boundary.   
*/   
/* TEXTADDR= 0xC0008000 有关TEXTADDR参考 */   
/* start of kernel, r0=0xa0007000 */   
15        add r0, r4, #(TEXTADDR & 0xff000000) >> 18 @ start of kernel   
/* r2=0xa0000c0e */   
16        bic r2, r3, #0x00f00000   
/* 0xa0000c0e的数据写入到0xa00070000 */   
17        str r2, [r0]            @ PAGE_OFFSET + 0MB   
/* r0=0xa0007000, no change */   
18        add r0, r0, #(TEXTADDR & 0x00f00000) >> 18   
19        str r3, [r0], #4            @ KERNEL + 0MB   
20        add r3, r3, #1 lock_depth)   
        spin_lock(&kernel_flag);   
}
  static inline void unlock_kernel(void)   
{   
     if (--current->lock_depth < 0)   
         spin_unlock(&kernel_flag);   
}   
找到两个比较好的说明如下   
1   
kernel_flag是一个内核大自旋锁,所有进程都通过这个大锁来实现向内核态的迁移。只有   
获得这个大自旋锁的处理器可以进入内核,如中断处理程序等。在任何一对lock_kernel/   
unlock_kernel函数里至多可以有一个程序占用CPU。 进程的lock_depth成员初始化为-1,   
在kerenl/fork.c文件中设置。在它小于0时(恒为 -1),进程不拥有内核锁;当大于或等   
于0时,进程得到内核锁。   
2   
kernel_flag,定义为自旋锁,因为很多核心操作(例如驱动中)需要保证当前仅由一个进程执行,   
所以需要调用lock_kernel()/release_kernel()对核心锁进行操作,它在锁定/解锁kernel_flag的   
同时还在task_struct::lock_depth上设置了标志,lock_depth小于0表示未加锁。当发生进程切换的时候,   
不允许被切换走的进程握有kernel_flag锁,所以必须调用release_kernel_lock()强制释放,同时,   
新进程投入运行时如果lock_depth>0,即表明该进程被切换走之前握有核心锁,   
必须调用reacquire_kernel_lock()再次锁定;
  代码printk(linux_banner)将linux的一些标语打印在内核启动的开始部分,需要说明的是虽然这是   
在内核一开始运行时就打印了,但是它没有马上输出到控制台上,它只是将liunx_banner存储到printk   
的内部缓冲中,因为这时printk的输出设备,一般都是串口还没有初始化,只有到输出设备初始化完毕   
在缓冲中的数据才被输出,后面会看到在哪个位置linux_banner才真正输出到终端。linux_banner定义在   
kernel/init/version.c中:
  const char *linux_banner =   
  "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"   
  LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "/n";
  这里面的字符串定义在文件kernel/include/linux/compile.h和kernel/include/linux/version.h中,   
compile.h中的内容:   
#define UTS_VERSION "#1 Thu, 01 Feb 2007 13:32:14 +0800"   
#define LINUX_COMPILE_TIME "13:32:14"   
#define LINUX_COMPILE_BY "taoyue"   
#define LINUX_COMPILE_HOST "swlinux.cecwireless.com.cn"   
#define LINUX_COMPILE_DOMAIN "cecwireless.com.cn"   
#define LINUX_COMPILER "gcc version 3.2.1"
  version.h中的内容:   
#define UTS_RELEASE "2.4.19-rmk7-pxa2"   
#define LINUX_VERSION_CODE 132115   
#define KERNEL_VERSION(a,b,c) (((a) > .ver1; fi   
     @if [ -f .name ]; then  echo -n /-`cat .name` >> .ver1; fi   
     @LANG=C echo ' '`date -R` >> .ver1   
     @echo /#define UTS_VERSION /"`cat .ver1 | $(uts_truncate)`/" > .ver   
     @LANG=C echo /#define LINUX_COMPILE_TIME /"`date +%T`/" >> .ver   
     @echo /#define LINUX_COMPILE_BY /"`whoami`/" >> .ver   
     @echo /#define LINUX_COMPILE_HOST /"`hostname | $(uts_truncate)`/" >> .ver   
     @([ -x /bin/dnsdomainname ] && /bin/dnsdomainname > .ver1) || /   
      ([ -x /bin/domainname ] && /bin/domainname > .ver1) || /   
      echo > .ver1   
     @echo /#define LINUX_COMPILE_DOMAIN /"`cat .ver1 | $(uts_truncate)`/" >> .ver   
     @echo /#define LINUX_COMPILER /"`$(CC) $(CFLAGS) -v 2>&1 | tail -1`/" >> .ver   
     @mv -f .ver $@   
     @rm -f .ver1

  include/linux/version.h: ./Makefile   
     @expr length "$(KERNELRELEASE)" / /dev/null || /   
      (echo KERNELRELEASE /"$(KERNELRELEASE)/" exceeds $(uts_len) characters >&2; false)   
     @echo /#define UTS_RELEASE /"$(KERNELRELEASE)/" > .ver   
     @echo /#define LINUX_VERSION_CODE `expr $(VERSION) //* 65536 + $(PATCHLEVEL) //* 256 + $(SUBLEVEL)` >> .ver   
     @echo '#define KERNEL_VERSION(a,b,c) (((a)

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-149493-1-1.html 上篇帖子: linux 内存管理之bootmem allocator 下篇帖子: linux内核定制3
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表