【原創】(三)Linux paging_init解析


背景

  • Read the fucking source code! --By 魯迅
  • A picture is worth a thousand words. --By 高爾基

說明:

  1. Kernel版本:4.14
  2. ARM64處理器,Contex-A53,雙核
  3. 使用工具:Source Insight 3.5, Visio

1. 介紹

(二)Linux物理內存初始化中,可知在paging_init調用之前,存放Kernel ImageDTB的兩段物理內存區域可以訪問了(相應的頁表已經建立好)。盡管物理內存已經通過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_kernelmap_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以及各種內存的騷操作好像還有很遠的樣子,待續吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM