您的位置:首页 > 运维架构 > Linux

linux start kernel

2016-07-22 15:08 573 查看
查找对应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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: