頁(yè)表創(chuàng)建源碼分析
RISC-V Linux啟動(dòng),經(jīng)歷了兩次頁(yè)表創(chuàng)建過(guò)程,第一次使用C函數(shù)setup_vm()
創(chuàng)建臨時(shí)頁(yè)表,第二次使用C函數(shù)setup_vm_final()
創(chuàng)建最終頁(yè)表。
具體細(xì)節(jié)參考代碼中的注釋,下面的代碼省略了一些不重要的部分。
setup_vm()
asmlinkage void __init setup_vm(uintptr_t dtb_pa)
{
uintptr_t va, pa, end_va;
uintptr_t load_pa = (uintptr_t)(&_start);
uintptr_t load_sz = (uintptr_t)(&_end) - load_pa;
uintptr_t map_size;
//load_pa就是kernel加載的其實(shí)物理地址
//load_sz就是kernel的實(shí)際大小
//page_offset就是kernel的起始物理地址對(duì)應(yīng)的虛擬地址,va_pa_offset是他們的偏移量
va_pa_offset = PAGE_OFFSET - load_pa;
//計(jì)算得到kernel起始物理地址的物理頁(yè),PFN_DOWN是將物理地址右移12位,因?yàn)閟v39的物理地址的低12位是pa_offset,所以右移12位,得到pfn
pfn_base = PFN_DOWN(load_pa);
map_size = PMD_SIZE;//PMD_SIZE為2M,在當(dāng)前,map_size只能為PGDIR_SIZE或PMD_SIZE。這時(shí)kernel默認(rèn)不允許建立PTE。
//檢查PAGE_OFFSET是否1G對(duì)齊,以及kernel入口地址是否2M對(duì)齊
BUG_ON((PAGE_OFFSET % PGDIR_SIZE) != 0);
BUG_ON((load_pa % map_size) != 0);
//allc_pte_early里面是BUG(),對(duì)于臨時(shí)頁(yè)表,kernel不允許我們建立PTE
pt_ops.alloc_pte = alloc_pte_early;
pt_ops.get_pte_virt = get_pte_virt_early;
#ifndef __PAGETABLE_PMD_FOLDED
pt_ops.alloc_pmd = alloc_pmd_early;
pt_ops.get_pmd_virt = get_pmd_virt_early;
#endif
/* 設(shè)置 early PGD for fixmap */
create_pgd_mapping(early_pg_dir, FIXADDR_START,
(uintptr_t)fixmap_pgd_next, PGDIR_SIZE, PAGE_TABLE);
/* 設(shè)置 fixmap PMD */
create_pmd_mapping(fixmap_pmd, FIXADDR_START,
(uintptr_t)fixmap_pte, PMD_SIZE, PAGE_TABLE);
/* 設(shè)置 trampoline PGD and PMD */
create_pgd_mapping(trampoline_pg_dir, PAGE_OFFSET,
(uintptr_t)trampoline_pmd, PGDIR_SIZE, PAGE_TABLE);
create_pmd_mapping(trampoline_pmd, PAGE_OFFSET,
load_pa, PMD_SIZE, PAGE_KERNEL_EXEC);
/*
* 設(shè)置覆蓋整個(gè)內(nèi)核的早期PGD,這將使我們能夠達(dá)到paging_init()。
* 稍后在下面的 setup_vm_final() 中映射所有內(nèi)存。
*/
end_va = PAGE_OFFSET + load_sz;
for (va = PAGE_OFFSET; va < end_va; va += map_size)
create_pgd_mapping(early_pg_dir, va,
load_pa + (va - PAGE_OFFSET),
map_size, PAGE_KERNEL_EXEC);
/* 為dtb創(chuàng)建早期的PMD */
create_pgd_mapping(early_pg_dir, DTB_EARLY_BASE_VA,
(uintptr_t)early_dtb_pmd, PGDIR_SIZE, PAGE_TABLE);
/* 為 FDT 早期掃描創(chuàng)建兩個(gè)連續(xù)的 PMD 映射 */
pa = dtb_pa & ~(PMD_SIZE - 1);
create_pmd_mapping(early_dtb_pmd, DTB_EARLY_BASE_VA,
pa, PMD_SIZE, PAGE_KERNEL);
create_pmd_mapping(early_dtb_pmd, DTB_EARLY_BASE_VA + PMD_SIZE,
pa + PMD_SIZE, PMD_SIZE, PAGE_KERNEL);
dtb_early_va = (void *)DTB_EARLY_BASE_VA + (dtb_pa & (PMD_SIZE - 1));
......
}
setup_vm()在最開(kāi)始就進(jìn)行了kernel入口地址的對(duì)齊檢查,要求入口地址2M對(duì)齊。假設(shè)內(nèi)存起始地址為0x80000000,那么kernel只能放在0x80000000、0x80200000等2M對(duì)齊處。為什么會(huì)有這種對(duì)齊要求呢?
我猜測(cè)單純是為給opensbi預(yù)留了2M空間,因?yàn)閗ernel之前還有opensbi,而opensbi運(yùn)行完之后,默認(rèn)跳轉(zhuǎn)地址就是偏移2M,kernel只是為了跟opensbi對(duì)應(yīng),所以設(shè)置了2M對(duì)齊。
那opensbi需要占用2M這么大?實(shí)際上只需要幾百KB,因此opensbi和kernel中間有一段內(nèi)存是空閑的,沒(méi)有人使用。這個(gè)問(wèn)題我們下篇再講。
setup_vm_final()
在該函數(shù)中開(kāi)始為整個(gè)物理內(nèi)存做內(nèi)存映射,通過(guò)swapper
頁(yè)表來(lái)管理,并且清除掉匯編階段的頁(yè)表。
static void __init setup_vm_final(void)
{
uintptr_t va, map_size;
phys_addr_t pa, start, end;
u64 i;
/**
* 此時(shí)MMU已經(jīng)開(kāi)啟,但是頁(yè)表還沒(méi)完全建立。
*/
pt_ops.alloc_pte = alloc_pte_fixmap;
pt_ops.get_pte_virt = get_pte_virt_fixmap;
#ifndef __PAGETABLE_PMD_FOLDED
pt_ops.alloc_pmd = alloc_pmd_fixmap;
pt_ops.get_pmd_virt = get_pmd_virt_fixmap;
#endif
/* Setup swapper PGD for fixmap */
create_pgd_mapping(swapper_pg_dir, FIXADDR_START,
__pa_symbol(fixmap_pgd_next),
PGDIR_SIZE, PAGE_TABLE);
/* 為整個(gè)物理內(nèi)存創(chuàng)建頁(yè)表 */
for_each_mem_range(i, &start, &end) {
if (start >= end)
break;
if (start <= __pa(PAGE_OFFSET) &&
__pa(PAGE_OFFSET) < end)
start = __pa(PAGE_OFFSET);
//best_map_size是選擇合適的映射大小,kernel入口地址2M對(duì)齊或者kernel大小能被2M整除時(shí),map_size就是2M,否則就是4K。
map_size = best_map_size(start, end - start);
for (pa = start; pa < end; pa += map_size) {
va = (uintptr_t)__va(pa);
create_pgd_mapping(swapper_pg_dir, va, pa,
map_size, PAGE_KERNEL_EXEC);
}
}
/* 清除fixmap的PMD和PTE */
clear_fixmap(FIX_PTE);
clear_fixmap(FIX_PMD);
/* 切換到swapper頁(yè)表,這個(gè)是最終的頁(yè)表,匯編階段relocate開(kāi)啟MMU的操作,跟下面這句是一樣的。 */
csr_write(CSR_SATP, PFN_DOWN(__pa_symbol(swapper_pg_dir)) | SATP_MODE);
local_flush_tlb_all();//刷新TLB
......
}
說(shuō)明:
在setup_vm_final()函數(shù)中,通過(guò)swapper_pg_dir
頁(yè)表來(lái)管理整個(gè)物理內(nèi)存的訪問(wèn)。并且清除匯編階段的頁(yè)表fixmap_pte和early_pg_dir。(本質(zhì)上就是把該頁(yè)表項(xiàng)的內(nèi)容清0,即賦值為0)
最終把swapper_pg_dir
頁(yè)表的物理地址賦值給SATP
寄存器。這樣CPU就可以通過(guò)該頁(yè)表訪問(wèn)整個(gè)物理內(nèi)存。
切換頁(yè)表通過(guò)如下實(shí)現(xiàn):
csr_write(CSR_SATP,PFN_DOWN(_pa(swapper_pg_dir))|SATP_MODE);
在swapper_pg_dir管理的kernel space中,其虛擬地址與物理地址空間的偏移是固定的,為va_pa_offset
(定義在arch/riscv/mm/init.c中的一個(gè)全局變量)
注意:swapper_pg_dir管理的是kernel space的頁(yè)表,即它把物理內(nèi)存映射到的虛擬地址空間是只能kernel訪問(wèn)的。user space不能訪問(wèn),用戶空間如果訪問(wèn),必須自行建立頁(yè)表,把物理地址映射到user space的虛擬地址空間。kernel線程共享這個(gè)swapper_pg_dir頁(yè)表。
-
Linux
+關(guān)注
關(guān)注
87文章
11314瀏覽量
209785 -
代碼
+關(guān)注
關(guān)注
30文章
4797瀏覽量
68712 -
RISC
+關(guān)注
關(guān)注
6文章
462瀏覽量
83764
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論