linux内核启动分析

来源:互联网 发布:什么软件可以看港片 编辑:程序博客网 时间:2024/04/24 14:07

本文着重分析 FS_S5PC100 平台 linux-2.6.35 内核启动的详细过程,主要包括: zImage 解压缩阶段、 vmlinux 启动汇编阶段、 startkernel 到创建第一个进程阶段三个部分,一般将其称为 linux 内核启动一、二、三阶段,本文也将采用这种表达方式。本文参考了许多技术大牛的博文,感谢他们的无私奉献。

-------------------------------------------------------------------------------------------------------

1 Linux内核启动第一阶段:内核解压缩和重定位

该阶段是从u-boot引导进入内核执行的第一阶段,我们知道u-boot引导内核启动的最后一步是:通过一个函数指针thekernel()带三个参数跳转到内核(zImage)入口点开始执行,此时,u-boot的任务已经完成,控制权完全交给内核(zImage)。

稍作解释,在u-boot的文件lib_arm/bootm.c(u-boot-2010.03)中定义了thekernel,并在do_bootm_linux的最后执行thekernel,如下:

void (*theKernel)(int zero, int arch, uint params);

theKernel = void (*)(int, int, uint))images->ep;

// images->ep指定的内核入口点,这里是0x20008000

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);其中第二个参数为机器ID,第三参数为u-boot传递给内核参数存放在内存中的首地址,此处是0x20000100

由上述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传递给内核参数的首地址 0x20000100,看来 linux-2.6.35启动时是不需要传递该参数的而是通过 struct machine_descarch/arm/include/asm/mach/arch.h)来确定,但是这个文件只是该结构的定义,真正的参数赋值在哪呢?实际上,这就是在内核移植是需要做的工作了,内核移植最主要的一个文件就是 arch/arm/mach-s5pc100/mach-smdkc100.c,通过下面的宏来实现对 machine_desc结构体的赋值,并且在该文件中对所涉及到的函数进行了具体的代码实现,这是内核移植方面的内容,与我们的主题无关,这里不再多说。

 

下面是宏定义:

下面我们采用汇编代码来进行分析: 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

 

/* We're running at a different address. We need to fix up various pointers:

     *  r5 - zImage base address

     *  r11 - GOT start

     *  ip - GOT end

这段代码的作用的是将LC0标号后面的地址,传到各个寄存器中,经过这段代码后,r1保存的是链接zImageLC0标号处的地址,r2中保存的是bss段的起始地址,r4中保存的是内核加载的物理地址,r5保存的是镜像的起始地址,r6保存的是解压内核镜像后的大小,

GOTglobal offset table

GOT是一个数组,存在ELF image的数据段中,他们是一些指向objects 的指针(通常

是数据objects).动态连接器将重新修改那些编译时还没有确定下来地址的符号的

GOT入口。所以说GOTi386动态连接中扮演着重要的角色。*/

*****将上面的变量进行重定位,转换为当前的运行时地址

       add r5, r5, r0  //zImage 的链接时首地址重定位为运行时的首地址

       add r11, r11, 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=0x20004000

    @ 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=0x20004000>>18=0xc00

           mov r8, r8, lsl #18       @ start of RAM,

//  r8=0xc00<<18=0x20000000

           add r9, r8, #0x10000000@ a reasonable RAM size

//r 9=0x30000000

           mov r1, #0x12

           orr r1, r1, #3 << 10 //r1=0xc00|0x12=0xc12

           add r2, r3, #16384 //r2=0x20004000+0x4000=0x20008000

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表明这是一个段描述符即bit4bit11

//0xc00bit11bit1011,即AP=11,允许所有读写访问

           add r1, r1, #1048576 //1048576=0x100000

           teq r0, r2

           bne 1b

//上面代码段实现0x00000000 ~0x400000001GB)地址空间也表的创建,映射的目的地址也是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

 

解压缩内核,分三种情况,后续会再跟上,敬请期待

原创粉丝点击