Chinaunix首页 | 论坛 | 博客
  • 博客访问: 188327
  • 博文数量: 63
  • 博客积分: 725
  • 博客等级: 军士长
  • 技术积分: 375
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-12 19:55
文章分类

全部博文(63)

文章存档

2012年(35)

2011年(28)

分类: LINUX

2012-02-22 00:12:54

Linux启动之/arch/arm/kernel/head.S

 

当解压缩部分的head.S执行完后,就开始执行kernel/目录下真正的linux内核代码。在内核连接文件/kernel/vmlinux/lds里定义了这部分开始所处的段空间为.text.head,也即内核代码段的头

关键代码如下:

       mrc p15, 0, r9, c0, c0        @ get processor id//读出CPUid

       bl    __lookup_processor_type         @ r5=procinfo r9=cpuid

       movs      r10, r5                         @ invalid processor (r5=0)?

       beq __error_p                    @ yes, error 'p'

       bl    __lookup_machine_type            @ r5=machinfo

       movs      r8, r5                           @ invalid machine (r5=0)?

       beq __error_a                    @ yes, error 'a'

       bl    __vet_atags

       bl    __create_page_tables

大致流程为,寻找CPU类型查找机器信息,解析内核参数列表,创建内存分页机制

__lookup_processor_type__lookup_machine_type__vet_atags函数都在kernel/head-comm.S内,这个文件实际上是被包含在head.S

Linux之所以把搜索机器类型和CPU类型独立出来,就是为了让内核尽可能的和bootload独立,增强移植性,把不同CPU的差异性处理减到最小。比如不同ARM架构的CPU处理中断的,打开MMUcach操作是不同的,因此,在内核开始执行前需要定位CPU架构,比如高通利用的ARM11Ti用的cortex-8架构

__lookup_machine_type 寻找的机器类型结构定义在arch/arm/include/asm/mach.h

查询方法比较简单,利用bootloa传进来的参数依次查询上述结构表项

这个表项是在编译阶段将#define MACHINE_START(_type,_name)宏定义的结构体struct machine_desc 连接到

__arch_info段,那么结构体开始和结束地址用__arch_info_begin__arch_info_end符号引用

3:    .long       .

       .long       __arch_info_begin

       .long       __arch_info_end

//r1 = 机器架构代码 number,由bootload最后阶段传进来

       .type       __lookup_machine_type, %function

__lookup_machine_type:

       adr  r3, 3b

       ldmia      r3, {r4, r5, r6}

       sub  r3, r3, r4                     @ 此时没有开MMU,因此需要确定放置__arch_info_begin的实际物理地址

       add r5, r5, r3                     @ 调整地址,找到__arch_info的实际地址(连接地址和物理地址不一定一样,因此需要调整)

       add r6, r6, r3                     @

1:    ldr   r3, [r5, #MACHINFO_TYPE] @ MACHINFO_TYPE=机器类型域的偏移量

       teq  r3, r1                           @ 是否和bootload传进来的参数相同?

       beq 2f                         @ 找到则跳出循环

       add r5, r5, #SIZEOF_MACHINE_DESC     @ 地址偏移至下个__arch_inf表项

       cmp r5, r6

       blo  1b

       mov r5, #0                          @ 未知的类型

2:    mov pc, lr//返回

__lookup_processor_type的查询的结构为struct proc_info_list

机器类型确定后即开始解析(__vet_atags)内核参数列表,判断第一个参数类型是不是ATAG_CORE

内核参数列表一般放在内核前面16K地址空间处。列表的表项由struct tag构成,每个struct tag有常见的以下类型:

ATAG_COREATAG_MEMATAG_CMDLINEATAG_RAMDISKATAG_INITRD等。

这些类型是宏定义,比如#define ATAG_CORE   0x54410001

arch/arm/include/asm/setup.h

struct tag_header {

       __u32 size;

       __u32 tag;

};

struct tag {

       struct tag_header hdr;

       union {

              struct tag_core             core;//有效的内核

              struct tag_mem32 mem;

              struct tag_videotext      videotext;

              struct tag_ramdisk       ramdisk;//文件系统

              struct tag_initrd     initrd;//临时根文件系统

              struct tag_serialnr  serialnr;

              struct tag_revision revision;

              struct tag_videolfb       videolfb;

              struct tag_cmdline cmdline;//命令行

       } u;

};

接下来就是创建页表,因为要使能MMU进行虚拟内存管理,因此必须创建映射用的页表。页表就像一个函数发生器,保证访问虚拟地址时能从物理地址里取到正确代码

       pgtbl       r4                         @ page table address

//页表放置的位置可由下面的宏确定,即在内核所在空间的前16K

.macro    pgtbl, rd

       ldr   /rd, =(KERNEL_RAM_PADDR - 0x4000)

       .endm

 

       mov r0, r4

       mov r3, #0

       add r6, r0, #0x4000//16K的空间,r6即是页表结束处

1:    str   r3, [r0], #4//清空页表项,页表项共有16K/4

       str   r3, [r0], #4

       str   r3, [r0], #4

       str   r3, [r0], #4

       teq  r0, r6

       bne  1b

 

       ldr   r7, [r10, #PROCINFO_MM_MMUFLAGS]

//从从差得的proc_info_list结构PROCINFO_MM_MMUFLAGS处获取MMU的信息

       /*

为内核创建1M的映射空间,这里是按照11一致映射,即代码的基地址(12bit)对应相同的物理块地址。这种映射关系只是在启动阶段,在跳进start_kernel后会被paging_init().移除。这种映射可以直接利用当前地址的高12bit作为基地址,这种方式很巧妙,因为当前的PC(加颜色处的地址)依然在1M空间内,因此,高12bit(段基地址)1M空间内都是相同的。

        */

       mov r6, pc, lsr #20                     @ 内核映像的基地址

       orr   r3, r7, r6, lsl #20         @ 基地址偏移后再加上标示符,即可得一个页表项的值

       str   r3, [r4, r6, lsl #2]         @将此表项按照页表项的索引存入对应的表项中。比如,若//基地址是0xc0001000,那么存入页表的第0xc00项中

//目前的映射依然是1:1的映射

        //然后移到下个段基地址处,开始映射此KERNEL_START对应的空间

//这个空间映射的物理地址与上面的相同,也就是两个虚拟地址映射到了同一个物理地址空间

        //r0+基地址组成//在第一级页表中索引到相关的项

       add r0, r4,  #(KERNEL_START & 0xff000000) >> 18

       str   r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!

       ldr   r6, =(KERNEL_END - 1)

       add r0, r0, #4//移到下个表项

       add r6, r4, r6, lsr #18//结束的基地址

1:    cmp r0, r6

       add r3, r3, #1 << 20//下个1M物理地址空间

       strls r3, [r0], #4//建立映射表项,开始创建所有的内核空间页表项

       bls   1b//

#ifdef CONFIG_XIP_KERNEL

       /*

        * Map some ram to cover our .data and .bss areas.

        */

       orr   r3, r7, #(KERNEL_RAM_PADDR & 0xff000000)

       .if    (KERNEL_RAM_PADDR & 0x00f00000)

       orr   r3, r3, #(KERNEL_RAM_PADDR & 0x00f00000)

       .endif

       add r0, r4,  #(KERNEL_RAM_VADDR & 0xff000000) >> 18

       str   r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]!

       ldr   r6, =(_end - 1)

       add r0, r0, #4

       add r6, r4, r6, lsr #18

1:    cmp r0, r6

       add r3, r3, #1 << 20

       strls r3, [r0], #4

       bls   1b

#endif

 

       /*

        * Then map first 1MB of ram in case it contains our boot params.

        */

//虚拟ram地址的第一个1M空间包含了参数列表,也需要映射

       add r0, r4, #PAGE_OFFSET >> 18

       orr   r6, r7, #(PHYS_OFFSET & 0xff000000)

       .if    (PHYS_OFFSET & 0x00f00000)

       orr   r6, r6, #(PHYS_OFFSET & 0x00f00000)

       .endif

       str   r6, [r0]

       mov pc, lr//页表建立完成,返回

 

页表创建后,具体的映射空间如下图:

执行完上述页表创建,开始执行内核跳转:

ldr   r13, __switch_data             @ address to jump to after

                                          @ mmu has been enabled

       adr  lr, __enable_mmu        @ return (PIC) address

       add pc, r10, #PROCINFO_INITFUNC

 

__switch_data      是一个数据结构,如下

       .type       __switch_data, %object

__switch_data:

       .long       __mmap_switched

       .long       __data_loc                  @ r4

       .long       __data_start                @ r5

       .long       __bss_start                  @ r6

       .long       _end                            @ r7

       .long       processor_id               @ r4

       .long       __machine_arch_type         @ r5

       .long       __atags_pointer                  @ r6

       .long       cr_alignment                @ r7

       .long       init_thread_union + THREAD_START_SP @ sp

 

语句“add pc, r10, #PROCINFO_INITFUNC”通过查表调用proc-v7.s__v7_setup函数,该函数末尾通过将lr寄存器赋给pc,导致对__enable_mmu的调用,完成使能mmu的操作,之后将r13寄存器值赋给pc,调用__switch_data数据结构中的第一个函数__mmap_switched

.type       __mmap_switched, %function

__mmap_switched:

       adr  r3, __switch_data + 4

 

       ldmia      r3!, {r4, r5, r6, r7}

       cmp r4, r5                           @ 拷贝数据段

1:    cmpne    r5, r6

       ldrne       fp, [r4], #4

       strne       fp, [r5], #4

       bne  1b

 

       mov fp, #0                          @ 清除BSS

1:    cmp r6, r7

       strcc       fp, [r6],#4

       bcc  1b

 

       ldmia      r3, {r4, r5, r6, r7, sp}//然后调整指针到processor_id      

       str   r9, [r4]                 @ 保存CPU ID

       str   r1, [r5]                 @保存机器类型

       str   r2, [r6]                 @ 保存参数列表指针

       bic   r4, r0, #CR_A                    @ Clear 'A' bit

       stmia      r7, {r0, r4}                  @ 保存控制信息

       b     start_kernel

最终调用init/main.c文件中的start_kernel函数。

这个start_kernel正是kernel/init/main.c的内核起始函数

 

 

阅读(1543) | 评论(0) | 转发(2) |
0

上一篇:Linux启动(1)

下一篇:Linux启动(3)

给主人留下些什么吧!~~