crash tool是一款內核調試工具,常用來分析內核崩潰問題。我們可以手動觸發內核崩潰,然後借用該工具來分析當時系統的運行情況,當然也包括記憶體的運行情況。 ...
初學者學習Linux系統地址轉換時,如果只是學習理論,又或者研讀代碼,那可能感覺比較枯燥。此時如果可以利用某些工具實際觀察一下地址轉換的過程,那可能會給枯燥的內核學習帶來些微的樂趣。crash tool是一款內核調試工具,常用來分析內核崩潰問題。我們可以手動觸發內核崩潰,然後借用該工具來分析當時系統的運行情況,當然也包括記憶體的運行情況。
本文基於ARMv8 AArch64 (簡稱ARM64)架構,Linux 4.14內核來講述。首先回顧一下記憶體訪問的相關知識點。
1、ARM記憶體訪問的硬體架構
ARM有MMU部件,現代操作系統一般都會啟用MMU來訪問記憶體。啟用MMU之後,多進程就有了可能,每個進程可以維護各自私有的虛擬地址空間,無需關心物理記憶體佈局。
2、虛擬地址空間到物理地址空間的映射
虛擬地址到物理地址的映射是通過查表的機制來實現,下圖是一種典型的地址映射佈局。內核空間地址的高16位(bit[63:48])為全1,其轉換表的基地址存放在TTBR1_EL1寄存器中;用戶空間地址的高16位(bit[63:48])為全0,其轉換表的基地址存放在TTBR0_EL0寄存器中。
除了高16位外,剩下的48位,也並不全用作虛擬地址空間,使用多少位是可以配置的,比如Linux系統的內核一般做如下配置,表示有39位虛擬地址空間。
CONFIG_ARM64_VA_BITS=39
39位虛擬地址空間,內核空間範圍為0xFFFFFF80_00000000 ~ 0xFFFFFFFF_FFFFFFFF,用戶空間範圍為0x00000000_00000000 ~ 0x0000007F_FFFFFFFF。
3、轉換表的格式
轉換表有4個級別,level 0 ~ level 3。
如下圖所示,當bit[1:0]為2b'11時,表示該表項是Table descriptor,指向下一級轉換表的地址。而當 bit[1:0]為2b'01時,表示Block entry,不指向下一級轉換表,而是直接輸出block address。當表項處於level 3時,即使bit[1:0]為2b'11,也不再指向下一級轉換表,而是輸出block address。
4、地址轉換的過程
如下圖所示,以39位虛擬地址為例,來瞭解地址轉換過程。圖片是取自ARMv8官方文檔,建議放大了看。
記憶體中維護著三個轉換表,從虛擬地址轉換成物理地址,要經過三次查表的過程。
39位虛擬地址被分成了4部分,作用如下:
-
bit[38:30] —— 索引第一級表中的表項
-
bit[29:21] —— 索引第二級表中的表項
-
bit[20:12] —— 索引第三級表中的表項
-
bit[11:0] —— 頁內偏移
第一步,TTBR寄存器中存放了第一級轉換表的起始地址,虛擬地址的bit[38:30]的值表示要查找轉換表中的第幾項,這個值左移三位(64位地址,每個表項占用8位元組),再加上第一級轉換表的地址,就是該表項的地址。該表項存放的是第二級轉換表的起始地址。
第二步,用第二級轉換表的起始地址,再結合虛擬地址的bit[29:21],與第一步計算方法類似,可以計算出第二級表項的地址。第二級表項中存放了第三級轉換表的起始地址。
第三步,用第三級轉換表的起始地址,再結合虛擬地址的bit[20:12],與第一步計算方法類似,可以計算出第三級表項的地址。第三級表項存放的是最終的頁面描述符,有頁面的物理地址信息,用這個地址再加上虛擬地址的bit[11:0],就是該虛擬地址對應的物理地址。
5、Linux內核中的關鍵數據結構
mm_struct結構體是記憶體描述符,內核用它來維護一個進程的地址空間的所有信息。這個結構體中包含了一個重要成員:pgd指針,pgd的名稱是頁全局目錄,指向的是第一級轉換表的的起始地址。
每個進程的task_struct結構體中,都包含了記憶體描述符。
init_mm全局變數,是內核本身的記憶體描述符。
有了以上知識點做支撐後,就可以用crash tool來驗證自己的理解了。
6、用crash tool觀察地址轉換
手動觸發內核崩潰的shell指令是:
echo "c" > /proc/sysrq-trigger
crash tool使用示例如下:
crash_arm64 vmlinux dumpfile -m phys_offset=0x80000000
進入crash tool環境後,我們選擇1號進程,也就是init進程來分析。首先用bt命令看一下1號進程當前的調用棧。
我們選擇該進程TASK的地址和SP寄存器指向的地址來進行實際分析。TASK的高位地址全為1,為內核空間的虛擬地址;SP地址高位全為0,為用戶空間的地址。
先分析用戶空間的虛擬地址 0000007feeac5cb0,將它分解如下:
bit[38:30] 0x1ff ,左移三位是 0xff8
bit[29:21] 0x175,左移三位是 0xba8
bit[20:12] 0x0c5,左移三位是 0x628
bit[11:0] 0xcb0
怎麼找到它對應的物理地址呢?首先要找到第一級轉換表所在的位置,也就是pgd的位置。我們在前面提到過,進程的記憶體描述中有pgd指針。所以我們可以先通過crash tool的task命令查看記憶體描述符的地址,再通過struct命令查看記憶體描述符中pgd指針的值。
知道了第一級轉換表所在的位置,結合虛擬地址的bit[38:30],就可以算出虛擬地址在第一級轉換表中所對應的表項位置:0xffffffc01bf60000 + 0xff8 = 0xffffffc01bf60ff8。用rd命令可以讀取這個表項的值,這個值裡面含有第二級轉換表的起始地址信息。
讀出的值是99c00003,bit[1:0]=2b'11,表示該表項類型為Table descriptor,指向下一級(第二級)轉換表,起始地址是99c00000。結合虛擬地址的bit[29:21],可以算出虛擬地址在第二級轉換表中的表項地址:0x99c00000 + 0xba8 = 0x99c00ba8。該地址是物理地址,需要用rd -p命令讀取其值,這個值裡面含有第三級轉換表的起始地址信息。
讀出的值是99c04003,表項類型也為Table descriptor。結合虛擬地址的bit[20:12],可以算出虛擬地址在第三級轉換表中對應的表項地址:0x99c04000 + 0x628 = 0x99c04628。三級表項存放的是頁面描述符信息,不再指向下一級轉換表。
從0x99c04628地址讀出的值是00e800008594bf53,結合第4節的地址轉換過程圖,bit[47:12]存放的是地址信息,與虛擬地址的bit[11:0](頁內偏移)結合後,就構成了實際的物理地址:0x8594b000 + 0xcb0 = 0x8594bcb0。這個地址就是0x0000007feeac5cb0所對應的物理地址。
上述過程我們手動計算出了物理地址,那如何知道有沒有算對呢?其實crash tool提供了vtop這個命令,可以直接顯示虛擬地址到物理地址的轉換結果,如下圖所示。可以看到vtop命令的結果和我們手動計算的結果一致,說明我們對轉換過程的理解是正確的。
再來分析內核空間的虛擬地址 ffffffc01bf60000,將它分解如下:
bit[38:30] 0x100,左移三位是 0x800
bit[29:21] 0x0df,左移三位是 0x6f8
bit[20:12] 0x160,左移三位是 0xb00
bit[11:0] 0x000
首先我們要知道內核空間的 pgd,結合前面第4節講的Linux關鍵數據結構,內核空間的 pgd 是保存在 init_mm 變數中,用p命令列印變數結果,可以看到pgd指針的值。
每一級表項的計算過程與用戶空間例子類似,可以得到如下觀察結果:
註意,第二級表項的值是00f800008be00f11,bit[1:0]=2b'01,表示Block entry,不再指向下一級轉換表,而是指示物理地址,地址值為9be00000。
兩級轉換表對應的是虛擬地址的bit[38:30]和bit[29:21],又沒有第三級轉換表,因此剩下的bit[20:0]都是頁內偏移。所以計算出最終物理地址為:0x9be00000 + 0x160000 = 0x9bf60000。也就是說, ffffffc01bf60000的物理地址是9bf60000。
用vtop命令驗證,和手動計算的結果一致。
------ END ------
作者:bigfish99
博客:https://www.cnblogs.com/bigfish0506/
公眾號:大魚嵌入式