初學者學習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/
公眾號:大魚嵌入式

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/475596.html
標籤:其他
上一篇:win7某些程式字體亂碼解決教程
