linux start kernel

来源:互联网 发布:网络优化的目的 编辑:程序博客网 时间:2024/06/05 20:54

查找对应arm架构下的arch/arm/kernel/vmlinux.lds.S, 找到入口点ENTRY(stext),这个stext在arch/arm/kernel/head.S中定义,定义如下:

    .section ".text.head", "ax"    .type   stext, %functionENTRY(stext)    msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode  @ and irqs disabled    mrc p15, 0, r9, c0, c0      @ get processor id    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

在进入到linux kernel时,需要确认在svc模式下,而且irq和fiq都是disable状态。

1.processor id

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

arm的协处理器指令,通过对协处理器15 c0 c0操作获取processor id,存放到r9

    .type   __lookup_processor_type, %function__lookup_processor_type:    adr r3, 3f    ldmda   r3, {r5 - r7}    sub r3, r3, r7          @ get offset between virt&phys    add r5, r5, r3          @ convert virt addresses to    add r6, r6, r3          @ physical address space1:  ldmia   r5, {r3, r4}            @ value, mask    and r4, r4, r9          @ mask wanted bits    teq r3, r4    beq 2f    add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)    cmp r5, r6    blo 1b    mov r5, #0              @ unknown processor2:  mov pc, lr/* * This provides a C-API version of the above function. */ENTRY(lookup_processor_type)    stmfd   sp!, {r4 - r7, r9, lr}    mov r9, r0    bl  __lookup_processor_type    mov r0, r5    ldmfd   sp!, {r4 - r7, r9, pc}/* * Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for * more information about the __proc_info and __arch_info structures. */    .long   __proc_info_begin    .long   __proc_info_end3:  .long   .    .long   __arch_info_begin    .long   __arch_info_end

adr r3, 3f
将前面标号为3位置的地址存放到r3中,这条指令获取的地址是基于pc的偏移地址,也就是运行时的地址,属于位置无关码。

ldmda r3, {r5 - r7}
r3的地址确定,所以指令运行结束后
r5存的是符号__proc_info_begin的地址;
r6存放的是符号__proc_info_end的地址;
r7存放的是符号3f的地址,这里需要注意运行地址和链接地址的区别,r7中的存放的地址是链接时确定的标号地址,r3中存放的是运行时的地址。

__proc_info_begin和__proc_info_end是在arch/arm/kernel/vmlinux.lds.S中的init代码段中:

__proc_info_begin = .;    *(.proc.info.init)__proc_info_end = .;

linux kernel中使用struct proc_info_list 描述processor type,
在include/asm-arm/procinfo.h定义

struct proc_info_list {    unsigned int        cpu_val;    unsigned int        cpu_mask;    unsigned long       __cpu_mm_mmu_flags; /* used by head.S */    unsigned long       __cpu_io_mmu_flags; /* used by head.S */    unsigned long       __cpu_flush;        /* used by head.S */    const char      *arch_name;    const char      *elf_name;    unsigned int        elf_hwcap;    const char      *cpu_name;    struct processor    *proc;    struct cpu_tlb_fns  *tlb;    struct cpu_user_fns *user;    struct cpu_cache_fns    *cache;};

以arm-926为例,可以在arch/arm/mm/proc-arm926.S中找到定义。

    .section ".proc.info.init", #alloc, #execinstr    .type   __arm926_proc_info,#object__arm926_proc_info:    .long   0x41069260          @ ARM926EJ-S (v5TEJ)    .long   0xff0ffff0    .long   PMD_TYPE_SECT | \        PMD_SECT_BUFFERABLE | \        PMD_SECT_CACHEABLE | \        PMD_BIT4 | \        PMD_SECT_AP_WRITE | \        PMD_SECT_AP_READ    .long   PMD_TYPE_SECT | \        PMD_BIT4 | \        PMD_SECT_AP_WRITE | \        PMD_SECT_AP_READ    b   __arm926_setup    .long   cpu_arch_name    .long   cpu_elf_name    .long   HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_JAVA    .long   cpu_arm926_name    .long   arm926_processor_functions    .long   v4wbi_tlb_fns    .long   v4wb_user_fns    .long   arm926_cache_fns    .size   __arm926_proc_info, . - __arm926_proc_info

从上述代码中可以看到__arm926_proc_info被放在“.proc.info.init”段当中。

继续分析__lookup_processor_type,
sub r3, r3, r7
上面的分析我们可以知道r3中存储的是3f处的物理地址,而r7存储的是3f处的虚拟地址,这一行是计算当前程序运行的物理地址和虚拟地址的差值,将其保存到r3中。

    add r5, r5, r3          @ convert virt addresses to    add r6, r6, r3          @ physical address space

将r5,r6存储的虚拟地址转换成物理地址,

    ldmia   r5, {r3, r4}            @ value, mask    and r4, r4, r9          @ mask wanted bits    teq r3, r4    beq 2f    add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)    cmp r5, r6    blo 1b    mov r5, #0              @ unknown processor2:  mov pc, lr

对照struct proc_info_list结构体定义,可以知道r3,r4分别保存__arm926_proc_info中的cpu_val和cpu_mask;

r9中存储了processor id,与r4的cpu_mask进行逻辑与得到我们需要的值,然后和r3的cpu_val进行比较,如果相等则找到对应的processor id,然后返回。

如果没有找到,则继续寻找比较下一个proc_info,直到__proc_info_end结束,然后设置r5为0并返回,

    movs    r10, r5             @ invalid processor (r5=0)?    beq __error_p           @ yes, error 'p'

如果r5为0,则跳转至__error_p错误处理。

2. machinfo

看完processor id,我们再来看看machinfo,linux kernel使用struct machine_desc结构体描述machine type,通过MACHINE_START来定义,例如:

MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch                    * to SMDK2410 */    /* Maintainer: Jonas Dietsche */    .phys_io    = S3C2410_PA_UART,    .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,    .boot_params    = S3C2410_SDRAM_PA + 0x100,    .map_io     = smdk2410_map_io,    .init_irq   = s3c24xx_init_irq,    .init_machine   = smdk2410_init,    .timer      = &s3c24xx_timer,MACHINE_END

__lookup_machine_type的汇编函数实现,
在arch/arm/kernel/head-common.S中定义。

    .long   __proc_info_begin    .long   __proc_info_end3:  .long   .    .long   __arch_info_begin    .long   __arch_info_end/* * 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: *  r3, r4, r6 corrupted *  r5 = mach_info pointer in physical address space */    .type   __lookup_machine_type, %function__lookup_machine_type:    adr r3, 3b    ldmia   r3, {r4, r5, r6}    sub r3, r3, r4          @ get offset between virt&phys    add r5, r5, r3          @ convert virt addresses to    add r6, r6, r3          @ physical address space1:  ldr r3, [r5, #MACHINFO_TYPE]    @ get machine type    teq r3, r1              @ matches loader number?    beq 2f              @ found    add r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc    cmp r5, r6    blo 1b    mov r5, #0              @ unknown machine2:  mov pc, lr

和分析processor type类似,
adr r3, 3b
把3b处的地址存入r3中,此处是物理地址;

ldmia r3, {r4, r5, r6}
把3b处开始的连续地址即3b处的地址,__arch_info_begin,__arch_info_end依次存入r4,r5,r6;

sub r3, r3, r4          @ get offset between virt&physadd r5, r5, r3          @ convert virt addresses toadd r6, r6, r3          @ physical address space

计算物理地址和虚拟地址的偏移,并将r5,r6的虚拟地址转换为物理地址

1:  ldr r3, [r5, #MACHINFO_TYPE]    @ get machine type    teq r3, r1              @ matches loader number?    beq 2f              @ found    add r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc    cmp r5, r6    blo 1b    mov r5, #0              @ unknown machine2:  mov pc, lr

获取machine_desc结构成员nr,并和r1进行比较,其中r1是uboot调用kernel入口地址时传入的参数。如果找到匹配直接返回,如果在“arch.info.init”段中都找不到,那就设置r5为0,然后返回进入错误处理。

3. vet_atags

检测bootloader传入参数链表atags的合法性。

    .type   __vet_atags, %function__vet_atags:    tst r2, #0x3            @ aligned?    bne 1f    ldr r5, [r2, #0]            @ is first tag ATAG_CORE?    subs    r5, r5, #ATAG_CORE_SIZE    bne 1f    ldr r5, [r2, #4]    ldr r6, =ATAG_CORE    cmp r5, r6    bne 1f    mov pc, lr              @ atag pointer is ok1:  mov r2, #0    mov pc, lr

首先检测参数链表指针是否对齐,然后检测第一个tag长度是否合法,是不是ATAG_CORE,如果正常直接返回,如果有其中某一项不正常,则将参数设置为0然后返回。

相关结构定义如下:

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;        struct tag_acorn       acorn;        struct tag_memclk    memclk;    } u;};
struct tag_header {     u32 size;       u32 tag; }; 
size=(sizeof(tag->tag_header)+sizeof(tag->u.core))>>2#define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)

4.__create_page_tables

首先来看ARM MMU所支持的虚实地址转换机制,下图所示虚地址VA的[20-31]位和CP15 CR2的[14-31]位共同构成一个地址,这个虚拟地址里存放的是一级页表项,表项中section base address对应virtual address的table index,由此找到虚拟地址对应的物理地址。

这里写图片描述

下面看下代码是如何实现创建页表的,函数定义如下:

    .macro  pgtbl, rd    ldr \rd, =(KERNEL_RAM_PADDR - 0x4000)    .endm    .type   __create_page_tables, %function__create_page_tables:    pgtbl   r4              @ page table address /* * Clear the 16K level 1 swapper page table */    mov r0, r4    mov r3, #0    add r6, r0, #0x40001:  str r3, [r0], #4    str r3, [r0], #4    str r3, [r0], #4    str r3, [r0], #4    teq r0, r6    bne 1b

为页表存放预留16K区域并清零,后面需要通过协处理器指令进行设置。

ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

获得proc_info_list的__cpu_mm_mmu_flags的值,并存储到 r7中。

mov r6, pc, lsr #20         @ start of kernel section,orr r3, r7, r6, lsl #20     @ flags + kernel basestr r3, [r4, r6, lsl #2]        @ identity mapping

通过PC值高12位得到kernel的section基地址,
r3 = r7 | (r6 << 20) @ kernel base + mmu flag
设置页表项,并将该页表项数据存放对应的页表中,具体是 *(r4 + r6 << 2) = r3

    /*     * Now setup the pagetables for our kernel direct     * mapped region.     */    add r0, r4,  #(KERNEL_START & 0xff000000) >> 18     str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!

r0 存放转换表的起始位置

    ldr r6, =(KERNEL_END - 1)

r6 存放kernel_end虚拟地址

    add r0, r0, #4

此后依次递增

add r6, r4, r6, lsr #18

计算最后一条存放的地址,存放在r6中

1:      cmp r0, r6        add r3, r3, #1 << 20        strls   r3, [r0], #4        bls 1b

从开始位置到结束,依次填充页表项,一个页表项代表了1MB空间的映射关系。XIP宏相关的映射我们直接跳过。

    /*     * Then map first 1MB of ram in case it contains our boot params.     */    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    .ltorg

设置RAM的第一MB虚拟地址的页表,映射完成之后如下图所示

这里写图片描述

4.调用平台特定的 __cpu_flush 函数

mmu页表配置完成后,在开启mmu前还需要完成很多操作,比如清除ICache,DCache,wrtiebuffer,TLB等,这些都可以通过CP15来完成。

代码如下:

    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

分别设置sp,lr,pc,注意r10存储的是proc info的基地址, PROCINFO_INITFUNC 宏指xiang的是proc_info_list结构体中的成员函数__cpu_flush,这里也就是 b __arm926_setup函数

    .type   __arm926_setup, #function__arm926_setup:    mov r0, #0    mcr p15, 0, r0, c7, c7      @ invalidate I,D caches on v4    mcr p15, 0, r0, c7, c10, 4      @ drain write buffer on v4#ifdef CONFIG_MMU    mcr p15, 0, r0, c8, c7      @ invalidate I,D TLBs on v4#endif    adr r5, arm926_crval    ldmia   r5, {r5, r6}    mrc p15, 0, r0, c1, c0      @ get control register v4    bic r0, r0, r5    orr r0, r0, r6    mov pc, lr    .size   __arm926_setup, . - __arm926_setup    .type   arm926_crval, #objectarm926_crval:    crval   clear=0x00007f3f, mmuset=0x00003135, ucset=0x00001134

使数据cache,指令cache无效;使write buffer无效;使数据TLB,指令TLB无效;
获取arm926_crval地址,并存入r5,存入该地址连续8字节数据分别到r5, r6,
r5 = 0x00007f3f, r6 = 0x00003135.
通过cp15获取操作寄存器的值,并对mmu进行设置,然后返回进入__enable_mmu.

    .type   __enable_mmu, %function__enable_mmu:    mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \              domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \              domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \              domain_val(DOMAIN_IO, DOMAIN_CLIENT))    mcr p15, 0, r5, c3, c0, 0       @ load domain access register    mcr p15, 0, r4, c2, c0, 0       @ load page table pointer    b   __turn_mmu_on/* * Enable the MMU.  This completely changes the structure of the visible * memory space.  You will not be able to trace execution through this. * If you have an enquiry about this, *please* check the linux-arm-kernel * mailing list archives BEFORE sending another post to the list. * *  r0  = cp#15 control register *  r13 = *virtual* address to jump to upon completion * * other registers depend on the function called upon completion */    .align  5    .type   __turn_mmu_on, %function__turn_mmu_on:    mov r0, r0    mcr p15, 0, r0, c1, c0, 0       @ write control reg    mrc p15, 0, r3, c0, c0, 0       @ read id reg    mov r3, r3    mov r3, r3    mov pc, r13

mmu的具体操作我们不在关心,这里主要看下最后一条指令

mov pc,r13

由此进入__switch_data,看下__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

明显可以看出会调用到__mmap_switched.

    .type   __mmap_switched, %function__mmap_switched:    adr r3, __switch_data + 4    ldmia   r3!, {r4, r5, r6, r7}    cmp r4, r5              @ Copy data segment if needed1:  cmpne   r5, r6    ldrne   fp, [r4], #4    strne   fp, [r5], #4    bne 1b    mov fp, #0              @ Clear BSS (and zero fp)1:  cmp r6, r7    strcc   fp, [r6],#4    bcc 1b    ldmia   r3, {r4, r5, r6, r7, sp}    str r9, [r4]            @ Save processor ID    str r1, [r5]            @ Save machine type    str r2, [r6]            @ Save atags pointer    bic r4, r0, #CR_A       @ Clear 'A' bit    stmia   r7, {r0, r4}    @ Save control register values    b   start_kernel

这里检测data段存储位置和数据开始位置是否不相等,是否需要搬运数据。清除bss段,并保存相关参数,最后进入start_kernel.

0 0