1 今日內容(分頁機制初始化) 在初始化記憶體的結點和記憶體區域之前, 內核先通過pagging_init初始化了內核的分頁機制. 在分頁機制完成後, 才會開始初始化系統的記憶體數據結構(包括記憶體節點數據和記憶體區域), 併在隨後初始化buddy伙伴系統來接管記憶體管理的工作 2 分頁機制初始化 arm64架 ...
1 今日內容(分頁機制初始化)
在初始化記憶體的結點和記憶體區域之前, 內核先通過pagging_init初始化了內核的分頁機制.
在分頁機制完成後, 才會開始初始化系統的記憶體數據結構(包括記憶體節點數據和記憶體區域), 併在隨後初始化buddy伙伴系統來接管記憶體管理的工作
2 分頁機制初始化
arm64架構下, 內核在start_kernel()
->setup_arch()
中通過arm64_memblock_init( )
完成了memblock的初始化之後, 接著通過setup_arch()
->paging_init()
開始初始化分頁機制
paging_init負責建立只能用於內核的頁表, 用戶空間是無法訪問的. 這對管理普通應用程式和內核訪問記憶體的方式,有深遠的影響
2.1 虛擬地址空間(以x86_32位系統為例)
因此在仔細考察其實現之前,很重要的一點是解釋該函數的目的
在x86_32系統上內核通常將總的4GB可用虛擬地址空間按3:1的比例劃分給用戶空間和內核空間, 虛擬地址空間的低端3GB
用於用戶狀態應用程式, 而高端的1GB則專用於內核. 儘管在分配內核的虛擬地址空間時, 當前系統上下文是不相干的, 但每個進程都有自身特定的地址空間.
這些劃分主要的動機如下所示
- 在用戶應用程式的執行切換到核心態時(這總是會發生,例如在使用系統調用或發生周期性的時鐘中斷時),內核必須裝載在一個可靠的環境中。因此有必要將地址空間的一部分分配給內核專用.
- 物理記憶體頁則映射到內核地址空間的起始處,以便內核直接訪問,而無需複雜的頁表操作.
如果所有物理記憶體頁都映射到用戶空間進程能訪問的地址空間中, 如果在系統上有幾個應用程式在運行, 將導致嚴重的安全問題. 每個應用程式都能夠讀取和修改其他進程在物理記憶體中的記憶體區. 顯然必須不惜任何代價防止這種情況出現.
雖然用於用戶層進程的虛擬地址部分隨進程切換而改變,但是內核部分總是相同的
出於記憶體保護等一系列的考慮, 內核將整個進程的虛擬運行空間劃分為內核虛擬運行空間和內核虛擬運行空間
按3:1的比例劃分地址空間, 只是約略反映了內核中的情況,內核地址空間作為內核的常駐虛擬地址空間, 自身又分為各個段
地址空間的第一段用於將系統的所有物理記憶體頁映射到內核的虛擬地址空間中。由於內核地址空間從偏移量0xC0000000開始,即經常提到的3 GiB,每個虛擬地址x都對應於物理地址x—0xC0000000,因此這是一個簡單的線性平移。
直接映射區域從0xC0000000到high_memory地址,high_memory準確的數值稍後討論。第1章提到過,這種方案有一問題。由於內核的虛擬地址空間只有1 GiB,最多只能映射1 GiB物理記憶體。IA-32系統(沒有PAE)最大的記憶體配置可以達到4 GiB,引出的一個問題是,如何處理剩下的記憶體?
這裡有個壞消息。如果物理記憶體超過896 MiB,則內核無法直接映射全部物理記憶體。該值甚至比此前提到的最大限制1 GiB還小,因為內核必須保留地址空間最後的128 MiB用於其他目的,我會稍後解釋。將這128 MiB加上直接映射的896 MiB記憶體,則得到內核虛擬地址空間的總數為1 024 MiB = 1GiB。內核使用兩個經常使用的縮寫normal和highmem,來區分是否可以直接映射的頁幀。
內核地址空間的最後128 MiB用於何種用途呢?如圖3-15所示,該部分有3個用途。
虛擬記憶體中連續、但物理記憶體中不連續的記憶體區,可以在vmalloc區域分配。該機制通常用於用戶過程,內核自身會試圖儘力避免非連續的物理地址。內核通常會成功,因為大部分大的記憶體塊都在啟動時分配給內核,那時記憶體的碎片尚不嚴重。但在已經運行了很長時間的系統上,在內核需要物理記憶體時,就可能出現可用空間不連續的情況。此類情況,主要出現在動態載入模塊時
持久映射用於將高端記憶體域中的非持久頁映射到內核中
固定映射是與物理地址空間中的固定頁關聯的虛擬地址空間項,但具體關聯的頁幀可以自由
選擇。它與通過固定公式與物理記憶體關聯的直接映射頁相反,虛擬固定映射地址與物理記憶體位置之間
的關聯可以自行定義,關聯建立後內核總是會註意到的
同樣我們的用戶空間, 也被劃分為幾個段, 包括從高地址到低地址分別為 :
區域 | 存儲內容 |
---|---|
棧 | 局部變數, 函數參數, 返回地址等 |
堆 | 動態分配的記憶體 |
BSS段 | 未初始化或初值為0的全局變數和靜態局部變數 |
數據段 | 一初始化且初值非0的全局變數和靜態局部變數 |
代碼段 | 可執行代碼, 字元串面值, 只讀變數 |
2.2 paging_init初始化分頁機制
paging_init函數定義在arch/arm64/mm/mmu.c?v=4.7, line 538
/*
* paging_init() sets up the page tables, initialises the zone memory
* maps and sets up the zero page.
*/
void __init paging_init(void)
{
phys_addr_t pgd_phys = early_pgtable_alloc();
pgd_t *pgd = pgd_set_fixmap(pgd_phys);
map_kernel(pgd);
map_mem(pgd);
/*
* We want to reuse the original swapper_pg_dir so we don't have to
* communicate the new address to non-coherent secondaries in
* secondary_entry, and so cpu_switch_mm can generate the address with
* adrp+add rather than a load from some global variable.
*
* To do this we need to go via a temporary pgd.
*/
cpu_replace_ttbr1(__va(pgd_phys));
memcpy(swapper_pg_dir, pgd, PAGE_SIZE);
cpu_replace_ttbr1(swapper_pg_dir);
pgd_clear_fixmap();
memblock_free(pgd_phys, PAGE_SIZE);
/*
* We only reuse the PGD from the swapper_pg_dir, not the pud + pmd
* allocated with it.
*/
memblock_free(__pa(swapper_pg_dir) + PAGE_SIZE,
SWAPPER_DIR_SIZE - PAGE_SIZE);
}