Arm Linux 內存管理(一)————開啓MMU【轉】

轉自:https://blog.csdn.net/qq_39150545/article/details/105386414?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-105386414-blog-106109251.pc_relevant_multi_platform_whitelistv1&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-105386414-blog-106109251.pc_relevant_multi_platform_whitelistv1&utm_relevant_index=1

首先我們根據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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章