您的位置:首页 > 其它

Arm linxu启动过程分析(一)

2016-07-30 21:51 309 查看
origin: http://blog.csdn.net/sustzombie/article/details/5667563 本文着重分析FS2410平台linux-2.6.14内核启动的详细过程,主要包括:zImage解压缩阶段、vmlinux启动汇编阶段、startkernel到创建第一个进程阶段三个部分,一般将其称为linux内核启动一、二、三阶段,本文也将采用这种表达方式。对于zImage之前的启动过程,本文不做表述,可参考作者 “u-boot-1.3.1启动过程分析”一文。

-------------------------------------------------------------------------------------------------------
硬件平台:优龙FS2410开发板
CPU: S3C2410(Arm920T);  NOR Flash: SST39VF1601(2MB);
SDRAM: K4S561632D-TC/L75 (32M)x2);Nand Flash: K9K2G08U0M-YCB0(64MB);
Net card: CS8900A)。
软件平台:u-boot-1.3.1、linux-2.6.14
-------------------------------------------------------------------------------------------------------
本文中涉及到的术语约定如下:
         基本内核映像:即内核编译过程中最终在内核源代码根目录下生成的vmlinux映像文件,并不包含任何内核解压缩和重定位代码;
         zImage 内核映像:包含了内核及压缩和重定位代码,以及基本内核映像vmlinux的压缩挡piggy.gz的一个映像文件,通常是目标板bootloader加载的对象;
         zImage 下载地址:即bootloader将zImage 下载到目标板内存的某个地址或者nand read 将zImage 读到内存的某个地址;
         zImage 加载地址:由Linux的bootloader完成的将zImage 搬移到目标板内存的某个位置所对应的地址值,默认值0x30008000。
关于内何映像的生成过程可以参考前一篇"Arm linux内核映像生成过程"
1. 
Linux内核启动第一阶段:内核解压缩和重定位
 

该阶段是从u-boot引导进入内核执行的第一阶段,我们知道u-boot引导内核启动的最后一步是:通过一个函数指针thekernel()带三个参数跳转到内核(zImage)入口点开始执行,此时,u-boot的任务已经完成,控制权完全交给内核(zImage)。
稍作解释,在u-boot的文件lib_arm/armlinux.c(u-boot-1.3.1)或者lib_arm/bootm.c (u-boot-1.3.4)中定义了thekernel,并在do_bootm_linux的最后执行thekernel,如下:
void (*theKernel)(int zero, int arch, uint params);
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
//hdr->ih_ep----Entry Point Address uImage 中指定的内核入口点,这里是0x30008000。
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);其中第二个参数为机器ID,第三参数为u-boot传递给内核参数存放在内存中的首地址,此处是0x30000100。
由上述zImage的生成过程我们可以知道,第一阶段运行的内核映像实际就是arch/arm/boot/compressed/vmlinux,而这一阶段所涉及的文件也只有三个: 

arch/arm/boot/compressed/vmlinux.lds
arch/arm/boot/compressed/head.S

     arch/arm/boot/compressed/misc.c



 

我们的分析集中在arch/arm/boot/compressed/head.S,适当参考vmlinux.lds。
从arch/arm/boot/compressed/vmlinux 的反汇编代码可一看出,内核执行的第一个代码段位start,
*****start
vmlinux:     file format elf32-littlearm
Disassembly of section .text:
00000000 <start>:
       0: e1a00000       nop                (mov r0,r0)
           ….          ….           …..
      1c: e1a00000       nop                (mov r0,r0)
        20: ea000002       b     30 <.text+0x30>
            ….          ….           …..
*****保存参数
      30: e1a07001      mov r7, r1
u-boot向内传递参数分析
//由thekernel 传递的三个参数分别保存在r0,r1,r2中。
//将机器ID保存在r7中
      34: e3a08000       mov r8, #0      ; 0x0
//保存r0,这里似乎没有太大的意义,
这里没有保存r2,也就是u-boot传递给内核参数的首地址0x30000100,看来linux-2.6.14启动时是不需要传递该参数的而是通过struct machine_desc(include/asm-arm/mach/arch.h)来确定,但是这个文件只是该结构的定义,真正的参数赋值在哪呢?实际上,这就是在内核移植是需要做的工作了,内核移植最主要的一个文件就是arch/arm/mach-s3c2410/mach-fs2410.c,通过下面的宏来实现对machine_desc结构体的赋值,并且在该文件中对所涉及到的函数进行了具体的代码实现,这是内核移植方面的内容,与我们的主题无关,这里不再多说。
MACHINE_START(SMDK2410, "SMDK2410")

/* @TODO: request a new identifier and switch to SMDK2410 */
.phys_ram     = S3C2410_SDRAM_PA,
.phys_io  = S3C2410_PA_UART,
       .io_pg_offst   = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
       .boot_params = S3C2410_SDRAM_PA + 0x100,

//指定u-boot传递给内核的参数存放的位置
       .map_io         = smdk2410_map_io,
       .init_irq  = smdk2410_init_irq,
       .init_machine   = sdmk2410_init,
       .timer            = &s3c24xx_timer,
MACHINE_END
下面我们采用汇编代码来进行分析:arch/arm/boot/compressed/head.S
              .align
start:     //u-boot first jump to this execute
              .type       start,#function
              .rept 8
              mov r0, r0
              .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, #0                    @ save r0
*****判定是否是超级用户模式
              mrs  r2, cpsr          @ get current mode
              tst    r2, #3          @ not user?   //判断当前是否为超级用户权限模式
              bne  not_angel    //如果是超级用户权限模式,jump to 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
*****将编译时指定的一些变量加载到相应的寄存器中
/* some architecture specific code can be inserted by the linker here, but it should preserve r7 and r8.    zImage 的连接首地址为0x0  zImage的运行时首地址一般为0x30008000,当然可以不同  */
.text
adr   r0, LC0  //读取LC0的当前运行时地址,应当为zImage的运行时起始地址+(LC0到zImage链接地址的首地址(0x0)的偏移)
ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}//将LC0中的变量值加载到r1, r2, r3, r4, r5, r6, ip, sp
subs r0, r0, r1        @ calculate the delta offset //计算当前运行地址与链接地址的偏移
beq   not_relocated  @ if delta is zero, we are running at the address we were linked at.
//如果运行地址等于链接地址,则跳过重定位部分代码,否则继续执行relocate
/*                 .type    LC0, #object
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
LC1: .word    reloc_end - reloc_start
     .size    LC0, . - 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
GOT(global offset table)
GOT是一个数组,存在ELF image的数据段中,他们是一些指向objects的指针(通常
是数据objects).动态连接器将重新修改那些编译时还没有确定下来地址的符号的
GOT入口。所以说GOT在i386动态连接中扮演着重要的角色。*/
*****将上面的变量进行重定位,转换为当前的运行时地址
        add  r5, r5, r0   //zImage 的链接时首地址重定位为运行时首地址
        add  r6, r6, r0   //GOT的链接时首地址重定位为运行时首地址
        add  ip, ip, r0
#ifndef CONFIG_ZBOOT_ROM
        /* 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   //__bss_start的链接时首地址重定位为运行时首地址
        add  r3, r3, r0   //_end的链接时地址重定位为运行时地址
        add  sp, sp, r0 //user_stack+4096的链接时地址重定位为运行时地址
        /* Relocate all entries in the GOT table.
         重定位GOT中的所有链接地址为当前运行时地址 */
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
        /* Relocate entries in the GOT table.  We only relocate
         * the entries that are outside the (relocated) BSS region.
         重定位GOT中的所有链接地址为当前运行时地址但是不重定位
         BSS_START到BSS_END部分  */
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
*****准备进入C 程序的相关设置,开启cache,设置一些指针
/* The C runtime environment should now be setup sufficiently. Turn the cache on, set up some pointers, and start decompressing. */
        cache on 是一个相当复杂的过程,这里简单描述其流程,如有兴趣可参考“ Arm linux 启动第一阶段cache on 分析”
bl      cache_on -------〉call_cache_fn-------〉
通过查表proc_types调用 __armv4_cache_on
---------------------------〉__setup_mmu
__setup_mmu:   sub  r3, r4, #16384       //4k ,r3=0x30004000
@ Page directory size  //16384=16kB=0x4000
            bic   r3, r3, #0xff          @ Align the pointer
            bic   r3, r3, #0x3f00
/* Initialise the page tables, turning on the cacheable and bufferable bits for the RAM area only.   */
            mov r0, r3
            mov r8, r0, lsr #18  r8=0x30004000>>18=0xc00
            mov r8, r8, lsl #18        @ start of RAM,
//r8=0xc00<<18=0x30000000
            add  r9, r8, #0x10000000@ a reasonable RAM size
//r9=0x40000000
            mov r1, #0x12
            orr  r1, r1, #3 << 10 //r1=0xc00|0x12=0xc12
            add  r2, r3, #16384  //r2=0x30004000+0x4000=0x30008000
1:         cmp r1, r8                    @ if virt > start of RAM
            orrhs      r1, r1, #0x0c         @ set cacheable, bufferable
            cmp r1, r9                    @ if virt > end of RAM
            bichs      r1, r1, #0x0c         @ clear cacheable, bufferable
            str   r1, [r0], #4            @ 1:1 mappi



 

//r0=0x300040000 ,r1=0xc12
//0x12表明这是一个段描述符即bit4和bit1为1。
//0xc00即bit11和bit10为11,即AP=11,允许所有读写访问
            add  r1, r1, #1048576 //1048576=0x100000
            teq  r0, r2
            bne  1b
//上面代码段实现0x00000000 ~0x40000000(1GB)地址空间也表的创建,映射的目的地址也是0x00000000~0x40000000,所以没有实际意义,也许是为了打开cache以及设置访问属性。
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_cache_on------->
__common_cache_on:
#ifndef DEBUG
           orr   r0, r0, #0x000d             @ Write buffer, mmu
#endif
           mov r1, #-1
           mcr p15, 0, r3, c2, c0, 0      @ load page table pointer
//将页表基址写入页表基址寄存器cp15 c2
           mcr p15, 0, r1, c3, c0, 0     

//设置域访问控制寄存器,写入0xffffffff ,与控制位为11,也就是允许权限检查,可以访问。
           mcr p15, 0, r0, c1, c0, 0      @ load control register
           mov pc, lr
mov  r0, #0
mcr  p15, 0, r0, c8, c7, 0      @ flush I,D TLBs
mov  pc, r12
返回,cache on 结束。
//这里的r1,r2之间的空间为解压缩内核程序所使用,也是传递给decompress_kernel的第二和第三的参数
        mov r1, sp                    @ malloc space above stack  //将SP的运行时地址存入r0
        add  r2, sp, #0x10000    @ 64k max  //r2=sp+0x10000
 

*****解压缩内核,分三种情况,请看下篇...
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: