背景 By 魯迅 By 高爾基 說明: 1. Kernel版本:4.14 2. ARM64處理器,Contex A53,雙核 3. 使用工具:Source Insight 3.5, Visio 1. 介紹 從 中,可知在 調用之前,存放 和`DTB memblock_add memblock_all ...
背景
Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基
說明:
- Kernel版本:4.14
- ARM64處理器,Contex-A53,雙核
- 使用工具:Source Insight 3.5, Visio
1. 介紹
從(二)Linux物理記憶體初始化
中,可知在paging_init
調用之前,存放Kernel Image
和DTB
的兩段物理記憶體區域可以訪問了(相應的頁表已經建立好)。儘管物理記憶體已經通過memblock_add
添加進系統,但是這部分的物理記憶體到虛擬記憶體的映射還沒有建立,可以通過memblock_alloc
分配一段物理記憶體,但是還不能訪問,一切還需要等待paging_init
的執行。最終頁表建立好後,可以通過虛擬地址去訪問最終的物理地址了。
按照慣例,先上圖,來一張ARM64內核的記憶體佈局圖片吧,最終的佈局如下所示:
開啟探索之旅吧!
2. paging_init
paging_init
源代碼短小精悍,直接貼上來,分模塊來介紹吧。
/*
* 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(); /********(mark 1)*******/
pgd_t *pgd = pgd_set_fixmap(pgd_phys);
map_kernel(pgd); /********(mark 2)*******/
map_mem(pgd); /********(mark 3)*******/
/*
* 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)); /********(mark 4)*******/
memcpy(swapper_pg_dir, pgd, PGD_SIZE);
cpu_replace_ttbr1(lm_alias(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_symbol(swapper_pg_dir) + PAGE_SIZE,
SWAPPER_DIR_SIZE - PAGE_SIZE);
}
mark 1
:分配一頁大小的物理記憶體存放pgd
;mark 2
:將內核的各個段進行映射;mark 3
:將memblock子系統添加的物理記憶體進行映射;mark 4
:切換頁表,並將新建立的頁表內容替換swappper_pg_dir
頁表內容;
代碼看起來費勁?圖來了:
下邊將對各個子模塊進一步的分析。
3. early_pgtable_alloc
這個模塊與FIX MAP
映射區域相關,建議先閱讀前文(二)Linux物理記憶體初始化
先上圖:
FIX MAP
的區域劃分從圖中可以看出來
本函數會先分配物理記憶體,然後借用之前的全局頁表bm_pte
,建立物理地址到虛擬地址的映射,這次映射的作用是為了去訪問物理記憶體,把記憶體清零,所以它只是一個臨時操作,操作完畢後,會調用pte_clear_fixmap()
來清除映射。
early_pgtable_alloc
之後,我們看到paging_init
調用了pgd_set_fixmap
函數,這個函數調用完後,通過memblock_alloc
分配的物理記憶體,最終就會用來存放pgd table
了,這片區域的內容最後也會拷貝到swapper_pg_dir
中去。
4. map_kernel
map_kernel
的主要工作是完成內核中各個段的映射,此外還包括了FIXADDR_START
虛擬地址的映射,如下圖:
映射完成之後,可以看一下具體各個段的區域,以我自己使用的平臺為例:
這些地址信息也能從System.map
文件中找到。
aarch64-linux-gnu-objdump -x vmlinux
能查看更詳細的地址信息。
5. map_mem
從函數名字中可以看出,map_mem
主要完成的是物理記憶體的映射,這部分的物理記憶體是通過memblock_add
添加到系統中的,當對應的memblock設置了MEMBLOCK_NOMAP
的標誌時,則不對其進行地址映射。
map_mem
函數中,會遍歷memblock中的各個塊,然後調用__map_memblock
來完成實際的映射操作。先來一張效果圖:
map_mem
都是將物理地址映射到線性區域中,我們也發現了Kernel Image
中的text, rodata
段映射了兩次,原因是其他的子系統,比如hibernate
,會映射到線性區域中,可能需要線性區域的地址來引用內核的text, rodata
,映射的時候也會限製成了只讀/不可執行
,防止意外修改或執行。
map_kernel
和map_mem
函數中的頁表映射,最終都是調用__create_pgd_mapping
函數實現的:
總體來說,就是逐級頁表建立映射關係,同時中間會進行許可權的控制等。
細節不再贅述,代碼結合圖片閱讀,效果會更佳噢。
6. 頁表替換及記憶體釋放
這部分代碼不多,不上圖了,看代碼吧:
/*
* 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, PGD_SIZE);
cpu_replace_ttbr1(lm_alias(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_symbol(swapper_pg_dir) + PAGE_SIZE,
SWAPPER_DIR_SIZE - PAGE_SIZE);
簡單來說,將新建立好的pgd頁表內容,拷貝到swapper_pg_dir
中,也就是覆蓋掉之前的臨時頁表了。當拷貝完成後,顯而易見的是,我們可以把paging_init
一開始分配的物理記憶體給釋放掉。
此外,在之前的文章也分析過swapper_pg_dir
頁表存放的時候,是連續存放的pgd, pud, pmd
等,現在只需要復用swapper_pg_dir
,其餘的當然也是可以釋放的了。
好了,點到為止,前路漫漫,離Buddy System,Slab,Malloc以及各種記憶體的騷操作好像還有很遠的樣子,待續吧。