一篇解析Linux paging_init
說明:
Kernel版本:4.14
ARM64處理器,Contex-A53,雙核
使用工具:Source Insight 3.5, Visio
1. 介紹
從詳細講解Linux物理內(nèi)存初始化中,可知在paging_init調(diào)用之前,存放Kernel Image和DTB的兩段物理內(nèi)存區(qū)域可以訪問了(相應的頁表已經(jīng)建立好)。盡管物理內(nèi)存已經(jīng)通過memblock_add添加進系統(tǒng),但是這部分的物理內(nèi)存到虛擬內(nèi)存的映射還沒有建立,可以通過memblock_alloc分配一段物理內(nèi)存,但是還不能訪問,一切還需要等待paging_init的執(zhí)行。最終頁表建立好后,可以通過虛擬地址去訪問最終的物理地址了。
按照慣例,先上圖,來一張ARM64內(nèi)核的內(nèi)存布局圖片吧,最終的布局如下所示:

2. paging_init
paging_init源代碼短小精悍,直接貼上來,分模塊來介紹吧。
mark 1
:分配一頁大小的物理內(nèi)存存放pgd
;mark 2
:將內(nèi)核的各個段進行映射;mark 3
:將memblock子系統(tǒng)添加的物理內(nèi)存進行映射;mark 4
:切換頁表,并將新建立的頁表內(nèi)容替換swappper_pg_dir
頁表內(nèi)容;
代碼看起來費勁?圖來了:

下邊將對各個子模塊進一步的分析。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ??


3. early_pgtable_alloc
這個模塊與FIX MAP
映射區(qū)域相關(guān),建議先閱讀前文(二)Linux物理內(nèi)存初始化
先上圖:

FIX MAP
的區(qū)域劃分從圖中可以看出來
本函數(shù)會先分配物理內(nèi)存,然后借用之前的全局頁表bm_pte
,建立物理地址到虛擬地址的映射,這次映射的作用是為了去訪問物理內(nèi)存,把內(nèi)存清零,所以它只是一個臨時操作,操作完畢后,會調(diào)用pte_clear_fixmap()
來清除映射。
early_pgtable_alloc
之后,我們看到paging_init
調(diào)用了pgd_set_fixmap
函數(shù),這個函數(shù)調(diào)用完后,通過memblock_alloc
分配的物理內(nèi)存,最終就會用來存放pgd table
了,這片區(qū)域的內(nèi)容最后也會拷貝到swapper_pg_dir
中去。
4. map_kernel
map_kernel
的主要工作是完成內(nèi)核中各個段的映射,此外還包括了FIXADDR_START
虛擬地址的映射,如下圖:

映射完成之后,可以看一下具體各個段的區(qū)域,以我自己使用的平臺為例:

這些地址信息也能從System.map
文件中找到。
aarch64-linux-gnu-objdump -x vmlinux
能查看更詳細的地址信息。
5. map_mem
從函數(shù)名字中可以看出,map_mem
主要完成的是物理內(nèi)存的映射,這部分的物理內(nèi)存是通過memblock_add
添加到系統(tǒng)中的,當對應的memblock設置了MEMBLOCK_NOMAP
的標志時,則不對其進行地址映射。
map_mem
函數(shù)中,會遍歷memblock中的各個塊,然后調(diào)用__map_memblock
來完成實際的映射操作。先來一張效果圖:

map_mem
都是將物理地址映射到線性區(qū)域中,我們也發(fā)現(xiàn)了Kernel Image
中的text, rodata
段映射了兩次,原因是其他的子系統(tǒng),比如hibernate
,會映射到線性區(qū)域中,可能需要線性區(qū)域的地址來引用內(nèi)核的text, rodata
,映射的時候也會限制成了只讀/不可執(zhí)行
,防止意外修改或執(zhí)行。
map_kernel
和map_mem
函數(shù)中的頁表映射,最終都是調(diào)用__create_pgd_mapping
函數(shù)實現(xiàn)的:

總體來說,就是逐級頁表建立映射關(guān)系,同時中間會進行權(quán)限的控制等。細節(jié)不再贅述,代碼結(jié)合圖片閱讀,效果會更佳噢。
6. 頁表替換及內(nèi)存釋放
這部分代碼不多,不上圖了,看代碼吧:
簡單來說,將新建立好的pgd頁表內(nèi)容,拷貝到swapper_pg_dir
中,也就是覆蓋掉之前的臨時頁表了。當拷貝完成后,顯而易見的是,我們可以把paging_init
一開始分配的物理內(nèi)存給釋放掉。
此外,在之前的文章也分析過swapper_pg_dir
頁表存放的時候,是連續(xù)存放的pgd, pud, pmd
等,現(xiàn)在只需要復用swapper_pg_dir
,其余的當然也是可以釋放的了。
原文作者:LoyenWang
