用crash tool觀察ARM64 Linux地址轉換

初學者學習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

部落格://www.cnblogs.com/bigfish0506/

公眾號:大魚嵌入式

Tags: