Arm Linux 内存管理(一)————开启MMU【转】
作者:互联网
首先我们根据vmlinux.lds可以找到内核入口函数为 stext,我们就直接从stext开始,主要干了几件事情
1.safe_svcmode_maskall r9 //设置CPU运行模式为SVC,并关中断
2.bl __vet_atags //验证atags或者dtb是否有效
3.bl __create_page_tables //创建临时映射
4.b __enable_mmu //使能mmu
5.__mmap_switched //初始化堆栈,并进入start_kernel
一个个来看:
.macro safe_svcmode_maskall reg:req
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, \reg
.endm
.macro setmode, mode, reg
msr cpsr_c, #\mode
.endm
这里把很多条件编译给去除掉了,其实就是将CPSR寄存器FIQ和IRQ位置0,以及设置成SVC模式。
__vet_atags:
tst r2, #0x3 @ aligned?
bne 1f
ldr r5, [r2, #0]
#ifdef CONFIG_OF_FLATTREE
ldr r6, =OF_DT_MAGIC @ is it a DTB?
cmp r5, r6 //是不是DTB
beq 2f
#endif
cmp r5, #ATAG_CORE_SIZE @ is first tag ATAG_CORE?
cmpne r5, #ATAG_CORE_SIZE_EMPTY
bne 1f
ldr r5, [r2, #4]
ldr r6, =ATAG_CORE
cmp r5, r6
bne 1f
2: ret lr @ atag/dtb pointer is ok
1: mov r2, #0
ret lr
ENDPROC(__vet_atags)
首先判断是不是DTB(根据头部MAGIC判断),如果是则返回,否则,判断是否为ATAG,如果是则返回,否则将r2寄存器清0,表示既不是DTB也不是ATAG。
__create_page_tables: //页表映射初始化
pgtbl r4, r8 @ page table address
/*
* Clear the swapper page table
*/
mov r0, r4
mov r3, #0
add r6, r0, #PG_DIR_SIZE
1: str r3, [r0], #4 //页表映射清0
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r6
bne 1b
__create_page_tables: //恒等映射
ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
/*
* Create identity mapping to cater for __enable_mmu.
* This identity mapping will be removed by paging_init().
*/
adr r0, __turn_mmu_on_loc //这一段做恒等映射
ldmia r0, {r3, r5, r6}
sub r0, r0, r3 @ virt->phys offset
add r5, r5, r0 @ phys __turn_mmu_on
add r6, r6, r0 @ phys __turn_mmu_on_end
mov r5, r5, lsr #SECTION_SHIFT
mov r6, r6, lsr #SECTION_SHIFT
1: orr r3, r7, r5, lsl #SECTION_SHIFT @ flags + kernel base
str r3, [r4, r5, lsl #PMD_ORDER] @ identity mapping
cmp r5, r6
addlo r5, r5, #1 @ next section
blo 1b
__create_page_tables: //代码段,数据段等映射(PAGE_OFFSET ------_end-1)
add r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)
ldr r6, =(_end - 1)
orr r3, r8, r7
add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
1: str r3, [r0], #1 << PMD_ORDER
add r3, r3, #1 << SECTION_SHIFT
cmp r0, r6
bls 1b
__create_page_tables: //DTB映射
mov r0, r2, lsr #SECTION_SHIFT //DTB进行映射
movs r0, r0, lsl #SECTION_SHIFT //物理地址 20位对齐
subne r3, r0, r8 //RAM偏移
addne r3, r3, #PAGE_OFFSET //虚拟地址
addne r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER) //对应页表地址
orrne r6, r7, r0 //实际要写入页表的东西
strne r6, [r3], #1 << PMD_ORDER //写入
addne r6, r6, #1 << SECTION_SHIFT //下一个1m空间
strne r6, [r3] //写入
1.页表映射初始化 :
r8寄存器代表的是phys_offset,也就是实际的(物理地址-虚拟地址)的偏移,通过pgtbl r4 r8可以得到swapper_pg_dir 的物理地址。然后按4字节对其进行清0,直至大小超过#PG_DIR_SIZE为止。
2.恒等映射 :
后面需要打开MMU,打开MMU后要使用的就是虚拟地址了,而之前的PC都是用的物理地址,可能会出现错误,所以在这一段代码段(__turn_mmu_on----__turn_mmu_on_end )需要做一个恒等映射(即物理地址与虚拟地址相同),这样打开MMU时也不会出现问题。首先需要了解在这一阶段里,内存采用的是段式管理,基地址(12bit)+偏移地址(20bit),也就是说4GB的空间分段管理,每段1MB,共4k段,所以页表大小就是4k*4 = 16k。
通过system.map我们可以得到 __turn_mmu_on的虚拟地址0xc0100000 __turn_mmu_on_end 的虚拟地址 0xc0100020,假设物理偏移是0xc0000000 r5 = 0x00100000 , r6 = 0x00100020 偏移地址是20位所以右移20位可以得到r5 = 0x001,r6 = 0x001,r5,r6其实在一个段中,所以我们知道我们需要写到页表(0x00000400)的第1项,每项都是4字节,所以第一项地址就是 0x00000404,将对应物理地址以及mmu的flag写入到该页表项,如果大小超过1M,那么重复该过程,直至r5与r6相等。
3:代码段,数据段等映射(PAGE_OFFSET ------_end-1)
这一步和第2步类似,根据虚拟地址找到对应的表项,再在表项中写入对应的地址即可。
4.DTB映射:
DTB不超过1M,所以这里就固定进行2段映射区。
这样所有的临时映射就已经都完成了,接下来就可以打开mmu了。
__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
#ifdef CONFIG_ARM_LPAE
mcrr p15, 0, r4, r5, c2 @ load TTBR0
#else
mov r5, #DACR_INIT
mcr p15, 0, r5, c3, c0, 0 @ load domain access register
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer //页表物理地址的基地址
#endif
b __turn_mmu_on
ENDPROC(__enable_mmu)
ENTRY(__turn_mmu_on)
mov r0, r0
instr_sync
mcr p15, 0, r0, c1, c0, 0 @ write control reg //打开mmu
mrc p15, 0, r3, c0, c0, 0 @ read id reg
instr_sync
mov r3, r3
mov r3, r13 //__mmap_switched
ret r3
__turn_mmu_on_end:
ENDPROC(__turn_mmu_on)
1.设置页表基地址 :
CP15中的寄存器C2保存的是页表的基地址,即一级映射描述符表的基地址。代码中就是把r4 写到cp15的c2中,r4前面也分析过了,就是swapper_pg_dir 的物理地址。
2.打开MMU :
CP15的寄存器C1中的M位,控制是否打开MMU,前面已经设置好了r0寄存器,所以只需要将r0写入cp15的C1中即可,这里省略了很多打开前的操作,如果有兴趣可以自行去分析源码。
__mmap_switched:
adr r3, __mmap_switched_data
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) //bss段数据为0
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ARM( ldmia r3, {r4, r5, r6, r7, sp})
THUMB( ldmia r3, {r4, r5, r6, r7} )
THUMB( ldr sp, [r3, #16] )
str r9, [r4] @ Save processor ID //保存 一些信息 后续要用 cpu_id
str r1, [r5] @ Save machine type //machine id
str r2, [r6] @ Save atags pointer //dtb地址
cmp r7, #0
strne r0, [r7] @ Save control register values
b start_kernel //进入c语言启动kernel
ENDPROC(__mmap_switched)
1.拷贝data段数据:
判断__data_loc和_sdata的地址是否相同,如果不相同则需要将__data_loc的数据拷贝到_sdata中来。如果相同,则表明已经拷贝过了,那就跳过。
2.bss段清0:
将bss段数据清0。
3.保存一些信息到寄存器:
将cpu_id machine_id 以及dtb地址写入对应的寄存器,在后续进入到start_kernel还会用到,所以需要保存。
4.进入start_kernel
终于到了我们熟悉的函数了,这之后就是C语言的启动的kernel了。
————————————————
版权声明:本文为CSDN博主「qq_39150545」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_39150545/article/details/105386414
标签:__,r5,r6,r0,r3,MMU,mmu,Linux,Arm 来源: https://www.cnblogs.com/sky-heaven/p/16423293.html