主要完成的映射關係:
1.將虛擬地址空間按照“1:1”對等映射到內核映像的第一個1M處
作用:在啓動MMU之前只能使用實地址模式運行
2.將整個內核地址空間直接映射區的代碼那部分映射到SDRAM上
3.將內核地址空間的開始的1M映射到SDRAM開始的第一個1M空間,因爲那裏存放了內核啓動參數
看圖意會時間:
ARM920T內置的MMU地址轉換方式:這個建議去看看ARM920T的使用手冊,畢竟那纔是原汁原味的
一級描述符的格式:這裏主要涉及的是一級段式頁表描述符
具體使用一級段式頁表將MVA轉換成PA的過程
代碼時間到了
第一段註釋:臨時頁表採用的是段式頁表
分析linux-2.6.30.4/arch/arm/kernel/head.S的__create_page_tables彙編代碼:
/*
* Setup the initial page tables. We only setup the barest
* amount which are required to get the kernel running, which
* generally means mapping in the kernel code.
*
* r8 = machinfo
* r9 = cpuid
* r10 = procinfo
*
* Returns:
* r0, r3, r6, r7 corrupted
* r4 = physical page table address:r4=頁表的基地址,r4的值還是在內核自解壓工作的時候賦給的:內核解壓後映像的起始地址0x30008000
*/
__create_page_tables:
pgtbl r4 @ page table address
//pgtbl是一個彙編宏定義 .macro pgtbl, rd,通過這個宏將r4(0x30004000)設置成頁表的物理基地址,往後r4值一直沒有變
/*
* Clear the 16K level 1 swapper page table 清零一級交換頁表16K
* 頁表將4GB的地址空間分成若干個1MB的段(section),因此頁表包含4096個頁表項(section entry)。每個頁表項是32bits(4 bytes),因而頁表佔用4096*4=16k的內存空間。
*/
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 獲得proc_info_list的__cpu_mm_mmu_flags的值,並存儲到r7中,看第二段註釋
//r7 = __cpu_mm_mmu_flags = 0x00000C1E
/*
* Create identity mapping for first MB of kernel to
* cater for the MMU enable. This identity mapping
* will be removed by paging_init(). We use our current program
* counter to determine corresponding section base address.
* 接下來是爲內核空間的直接映射區第一個1MB建立頁表,這都是爲之後MMU的啓動,之後會被paging_init()函數移除銷燬
* 我們通過當前的程序計數寄存器PC值來獲得段基址
*/
mov r6, pc, lsr #20 @ start of kernel section r6 = 0x300
orr r3, r7, r6, lsl #20 @ flags + kernel base(PA) r3 = (r7) | ((r6)<<20) r3裏邊放的內容就是一級描述符
str r3, [r4, r6, lsl #2] @ identity mapping [0x30004000+0x300<<2] = r3 將一級描述符放到頁目錄中
@不過這裏爲什麼要左移兩位(乘於4)???爲什麼又是存放到這個地址處
@想到了:r4裏邊是ttb基址(啓動MMU時會被寫到cp15的c2寄存器),r6裏邊的是ttb表中對應1M的索引值,每個描述符是4字節
@實際上只是將pc的值右移了18位
@上面這一段代碼實現了VA=PA(1:1)的映射:根據ARM920T的MMU一級地址轉換,4G的虛擬地址空間被分成4096個條目(描述符,每個4字節),因此每一個條目對應1M=4G/4096的地址空間映射。
@到這裏第一個映射關係建立完成
/*
* Now setup the pagetables for our kernel direct
* mapped region.
* 現在爲了內核直接映射區來設置頁表
* 即爲kernel鏡像所佔有空間,即KERNL_START到KERNEL_END建立內存映射
* 由於這塊內核虛擬空間要映射到SDRAM中內核映像0x30008000開始處,所以第一個1M的描述符(保存到r3寄存器中)和上面的是樣的
* 這裏是將整個內核空間的直接映射區全部映射完畢--以段的方式(1M)
*/
add r0, r4, #(KERNEL_START & 0xff000000) >> 18 @取內核空間地址(VA)高8位,arm的立即數只能是8位
str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! @再取內核空間地址(VA)高8位往後4位,和上一句加起來共12位,“!”將地址寫到r0
ldr r6, =(KERNEL_END - 1) @r4=ttb基址 r0=ttb中kernel第1M描述符檢索值
add r0, r0, #4
add r6, r4, r6, lsr #18 @r6= 用來標識ttb中內核地址空間結束的檢索地址
1: cmp r0, r6
add r3, r3, #1 << 20 ;將描述符的段偏移地址加1,即物理段基地址加1
strls r3, [r0], #4 ;表示小於等於,即只要r0<=r6,strls就會執行
bls 1b
#ifdef CONFIG_XIP_KERNEL ;沒有定義CONFIG_XIP_KERNEL,註釋掉這個條件分支,XIP技術就是內核代碼可以不用拷貝到SDRAM而立地執行
.......
#endif
/*
* Then map first 1MB of ram in case it contains our boot params.
* 接下來建立內核臨時頁表的最後一個頁表描述符,即SDRAM開始1M的地址空間,那裏保存了uboot傳遞給內核的啓動參數
*/
add r0, r4, #PAGE_OFFSET >> 18 ;PAGE_OFFSET被定義爲SDRAM起始地址0x30000000
orr r6, r7, #(PHYS_OFFSET & 0xff000000) ;r6 = PA section base addr + flags
.if (PHYS_OFFSET & 0x00f00000)
orr r6, r6, #(PHYS_OFFSET & 0x00f00000)
.endif
str r6, [r0] ;將描述符寫入到對應的頁表位置,第三個頁表描述符建立完成
;........中間省略一段代碼,因爲條件編譯不成立,相當於註釋掉...............
mov pc, lr ; 從__create_page_tables返回
ENDPROC(__create_page_tables)
#include "head-common.S"
第二段註釋:
在arch/arm/include/asm/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;
};
在文件arch/arm/kernel/vmlinux.lds.S中定義了一個“.proc.info.init”段屬性 __proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
因此,以關鍵字“.proc.info.init”在工程裏面搜索,找到arch/arm/mm/proc-arm920.S文件,第三行的定義就是對__cpu_mm_mmu_flags“映射屬性標識”的賦值__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
.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 __arm920_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
.long cpu_arm920_name
.long arm920_processor_functions
.long v4wbi_tlb_fns
.long v4wb_user_fns
#ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
.long arm920_cache_fns
#else
.long v4wt_cache_fns
#endif
.size __arm920_proc_info, . - __arm920_proc_info
在arch/arm/include/asm/pgtable-hwdef.h找到上面用到的宏的定義:#define PMD_TYPE_SECT (2 << 0) 採用段式地址映射
#define PMD_SECT_BUFFERABLE (1 << 2)
#define PMD_SECT_CACHEABLE (1 << 3)
#define PMD_BIT4 (1 << 4)
#define PMD_SECT_AP_WRITE (1 << 10)
#define PMD_SECT_AP_READ (1 << 11)
.long是分配長整型長度的空間,在arm9處理器佔4個字節(32位)按位或之後:__cpu_mm_mmu_flags = 0x00000C1E
從臨時頁表創建原理到具體的代碼實現過程可以說到這裏我已經消化了。突然又想到一個問題:三張頁表描述符創建好了,MMU也啓動了,cpu執行過程中究竟是怎麼知道什麼時候使用對等映射描述表符,什麼時候使用內核空間與內核映像的那個描述符,又是什麼時候要知道切換使用啓動參數那塊的映射描述符???
仔細想想就可以知道了,我們知道ttb的“選擇子”就是MVA(這裏就相當於VA)的高12位,這12位的變化就是cpu自動選擇ttb中哪一個描述符的根本原因。arm9從cpu上電開始工作到在SDRAM取指執行時(MMU未開啓)PC指針都是0x3打頭的,在開始真正運行內核那段head.S代碼並且C語言運行環境未設置好時,必須使用位置無關碼來實現彙編函數跳轉,也就是說這就是爲了保持PC是0x3打頭的,這樣一來,即使之後啓動了MMU,我的PC保持原有作風就可以讓我的“選擇子”等於“0x300”選中1:1的對等映射描述符表項。同理只要往後pc的值不再是0x3打頭就會選中0xC打頭的描述符表項,具體選中哪一個就看0xCxx了。
內核臨時頁表的創建過程分析完畢,linux之路又前進了一小步,加油!