1.汇编的第二阶段分析
其中的汇编阶段也主要可以分为两个过程,在此分析第二个阶段,创建页表,启动mmu,跳转至c语言阶段。
1).校验处理器的id,以及传过来的r2参数,完成部分通用寄存器的传参;
r1 = machine no, r2 = atags or dtb,
r8 = phys_offset, r9 = cpuid, r10 = procinfo
2).创建mmu的页表,使能mmu
在经历了汇编第一阶段后,已经完成了处理器等参数的校验;实现了通用寄存器的传参;接下来进行汇编的第二个阶段,创建页表__create_page_tables,并且启动mmu;
2.MMU的简介
MMU,内存管理单元memory management unit,首先mmu是一个硬件,内核所做的内存管理就是针对mmu就行辅助操作映射页表,将页表放置在mmu指定的寄存器中。
最终的目地为创建页表,实现虚拟地址至物理地址的映射,先明确几个概念:虚拟地址,物理地址,编译地址,运行地址。
虚拟地址起始就是编译器生成的符号表对应的地址;物理地址即RAM对应的运行地址;
虚拟地址=编译地址;
物理地址=运行地址;
3.页表的创建__create_page_tables
这里需要说明,页表是存储在物理内存中,而不是虚拟内存;
此完成了三处的页表映射: 内核镜像代码,页表与内核启动参数映射,内核代码段映射。
__create_page_tables:
pgtbl r4, r8 @ page table address
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
ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
adr r0, __enable_mmu_loc
ldmia r0, {r3, r5, r6}
sub r0, r0, r3 @ virt->phys offset
add r5, r5, r0 @ phys __enable_mmu
add r6, r6, r0 @ phys __enable_mmu_end
mov r5, r5, lsr #20
mov r6, r6, lsr #20
1: orr r3, r7, r5, lsl #20 @ flags + kernel base
str r3, [r4, r5, lsl #2] @ identity mapping
teq r5, r6
addne r5, r5, #1 @ next section
bne 1b
mov r3, pc 3.4.1
mov r3, r3, lsr #20 3.4.2
orr r3, r7, r3, lsl #20 3.4.3
add r0, r4, #(KERNEL_START & 0xff000000) >> 18 3.4.4
str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! 3.4.5
ldr r6, =(KERNEL_END - 1) 3.4.6
add r0, r0, #4 3.4.7
add r6, r4, r6, lsr #18 3.4.8
1: cmp r0, r6 3.4.9
add r3, r3, #1 << 20 3.4.10
strls r3, [r0], #4 3.4.11
bls 1b 3.4.12
#ifdef CONFIG_XIP_KERNEL
add r3, r8, #TEXT_OFFSET
orr r3, r3, r7
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
mov r0, r2, lsr #20
movs r0, r0, lsl #20
moveq r0, r8
sub r3, r0, r8
add r3, r3, #PAGE_OFFSET
add r3, r4, r3, lsr #18
orr r6, r7, r0
str r6, [r3]
#ifdef CONFIG_DEBUG_LL
#ifndef CONFIG_DEBUG_ICEDCC
addruart r7, r3
mov r3, r3, lsr #20
mov r3, r3, lsl #2
add r0, r4, r3
rsb r3, r3, #0x4000 @ PTRS_PER_PGD*sizeof(long)
cmp r3, #0x0800 @ limit to 512MB
movhi r3, #0x0800
add r6, r0, r3
mov r3, r7, lsr #20
ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
orr r3, r7, r3, lsl #20
1: str r3, [r0], #4
add r3, r3, #1 << 20
teq r0, r6
bne 1b
#else
ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
#endif
#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
add r0, r4, #0xff000000 >> 18
orr r3, r7, #0x7c000000
str r3, [r0]
#endif
#ifdef CONFIG_ARCH_RPC
add r0, r4, #0x02000000 >> 18
orr r3, r7, #0x02000000
str r3, [r0]
add r0, r4, #0xd8000000 >> 18
str r3, [r0]
#endif
#endif
mov pc, lr
ENDPROC(__create_page_tables)
3.1.pgtbl宏分析
其中r8中存放的是,PHYS_OFFSET为物理内存RAM的起始偏移地址;
pgtbl是一个汇编宏,具体实现为从PHYS_OFFSET找到内核的页表起始的物理地址;
.macro pgtbl, rd, phys add rd, phys, #TEXT_OFFSET - 0x4000 .endm
最终将r4设置成页表的物理地址,后续的页表映射依赖于此参数来配置页表项。
3.2.清除内核页表
因为后续需要使用到内核页表,所以开始先把此物理地址块进行初始化,另外,内核页表是存储在物理地址中的,在此处是使用了arm的L1的1M的section映射方式,需要映射4G的虚拟内存,因此只需要4096个即可满足要求(4G/1M=4096个)。
r4寄存器里面目前存储的是页表的起始物理地址,r6中存储的是起始物理地址+0x4000即刚好可以满足4096个L1页表项的使用,然后通过r3寄存器循环清除此段内存的值,便实现了页表清空工作。
最后一句实现了将proinfo结构体的__cpu_mm_mmu_flags的地址赋值给r7寄存器;
struct proc_info_list {
unsigned int cpu_val;
unsigned int cpu_mask;
unsigned long __cpu_mm_mmu_flags;
unsigned long __cpu_io_mmu_flags;
unsigned long __cpu_flush;
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;
};
3.3 内核代码段区域映射
实现内核代码段的映射,此和3.4 3.5的操作是类似的,实际就是填充物理页表里面的页表项;
3.4内核镜像段区域映射
首先3.4.1拿到当前的PC指针;
3.4.2:r3=r3>>20
3.4.3:r3=r7+r3<<20;
3.4.4:R4已保存了页表物理起始地址,最终实现将实际的页表项保存在r0中;
3.4.5:将r3的结果写入内核起始虚拟地址所在的L1表项
3.4.6:r6为内核代码的截止处的虚拟地址;
3.4.7:r0保存下一个即将要填写的L1页表项的物理地址;
3.4.8:R6值是内核代码结尾的所在L1页表的表项的物理地址;
3.4.9-3.4.12:循环填充页表直到r0==r6;
3.5启动参数与页表映射
我们知道,在执行此处映射之前 r2 = atags or dtb, r8 = phys_offset,r7存放的是section的描述符的内容,也就是r2存放的是启动参数,r8存放的是DDR的物理起始地址;uboot将内核启动参数放在物理内存起始的1M内存中,所以需要对其映射,以便访问,此处实际是向物理页表中写入对应的页表项;
4.使能mmu
如何操作mmu,主要是通过cp15协处理器进行实现,主要的配置就是将页表的物理地址写入ttbl0中,然后使能相关寄存器进行实现, 此处是先将使能mmu存在r13里面,后续再执行r13里面的内容,然后设置内核堆栈段的信息,保存cpuid、dtb等参数存入至全局变量中;
__enable_mmu: #if defined(CONFIG_ALIGNMENT_TRAP) && __LINUX_ARM_ARCH__ < 6 orr r0, r0, #CR_A #else bic r0, r0, #CR_A #endif #ifdef CONFIG_CPU_DCACHE_DISABLE bic r0, r0, #CR_C #endif #ifdef CONFIG_CPU_BPREDICT_DISABLE bic r0, r0, #CR_Z #endif #ifdef CONFIG_CPU_ICACHE_DISABLE bic r0, r0, #CR_I #endif 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 ENDPROC(__enable_mmu)
5.跳转至c阶段
做了这么多,完成了汇编阶段的使命,就可以通过b跳转指令跳转至c语言中,第一句c语言为start_kernel处。



