linux start kernel

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

    .section ".text.head", "ax"
    .type   stext, %function
ENTRY(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 space
1:  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 processor
2:  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_end
3:  .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 processor
2:  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_end
3:  .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 space
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 machine
2:  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&phys
add r5, r5, r3          @ convert virt addresses to
add 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 machine
2:  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 ok

1:  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, #0x4000
1:  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 base
str 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, #object
arm926_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 needed
1:  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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值