linux kernel集中了世界頂尖程式員們的編程智慧,猶記作業系統課上老師講作業系統的四大功能:進程調度 記憶體管理 裝置驅動 網路。從事嵌入式軟體開發工作,對裝置驅動和網路接觸的比較多。而進程調度和記憶體管理接觸少之有少,更多的是敬而遠之。
我的理解,想在核心開發上有更深層次的技術進步,應該對核心的記憶體管理進程調度等深層技術有一定的理解。不過這2塊內容是核心最核心的部分,實際核心開發工作中涉及較少,很少有問題點來切入進去進行研究,網上也沒有系統的資料進行講解,學習起來談何容易。
本著我不入地獄,誰入地獄的原則,這段時間利用工作之餘對記憶體管理進行了一些研究,結合之前工作中遇到的一些記憶體管理的問題,對記憶體管理的架構有了一點理解。只能說,這一點點瞭解讓我更加敬畏核心,不要說進程調度了,單單記憶體管理就夠寫一本500頁的書了。
我的理解記憶體管理可以分為頁表機制和記憶體配置機制兩大塊,這段時間的研究也僅僅讓我對頁表機制有些理解。先寫幾篇文章把頁表機制寫出來吧,把頁表機制細節理通,再去學習記憶體配置機制,如bootmem和slab夥伴系統。
進程調度是比記憶體管理更大的坑,如果有幸從記憶體管理的大坑中活著爬出來,再去跳進程調度的坑吧。
忽然感覺自己肩上的擔子很重,這也許就是部落格的作用吧,分享自己的知識給大家,也讓大家督促自己去進一步學習。
但是自己還只是參加工作上才幾年的小學生,嘗試著去分析這些高深的知識,肯定會有紕漏和錯誤的地方,不過既然分享出來,就是讓大家和我一起去偽存真,進行完善。所以希望大家多提見解,一起努力。
由於記憶體管理是比較抽象的知識,因此本著三個原則去研究:
(1)帶著一些問題去研究,提出一些疑問,由這些疑問點去切入
(2)將抽象的知識畫出邏輯圖,使架構更加清晰
(3)執行個體化,盡量由實際的裝置來對記憶體管理進行研究
本系列文章是基於ARM架構,linux核心版本號碼3.4.55
linux核心的頁表機制簡單說來就是管理著裝置真實物理地址與虛擬位址的一個動態或靜態映射,是基於硬體的MMU進行的,處理器必須提供MMU記憶體管理單元,linux的頁表機制才能正常工作,兩者相輔相成。
學習核心的記憶體管理如果脫離了MMU的硬體原理,只去學習其軟體邏輯,真的很難懂。說到底,軟體代碼的邏輯是為硬體服務,只是為了充分發揮硬體的各項功能,因此學習linux的記憶體管理機制,首先要學習下該處理器架構下MMU的工作原理,這樣對我們理解頁表機制的邏輯很有協助。(作為底層軟體工程師,沒事翻翻datasheet很有用啊,多從硬體思維去考慮問題)
MMU是處理器核內部的硬體邏輯,因此只有在處理器核的datasheet中才會有詳細的說明,ARM的MMU邏輯對於不同版本處理器大同小異,我手頭有一個ARM920T的手冊,詳細閱讀了MMU一章,我有以下幾個疑問需要解決:
一 MMU利用TLB進行PA(物理地址)和VA(虛擬位址)之間的轉換,處理器定址是直接在TLB中進行地址匹配。但核心初始化時會在記憶體中建立頁表,頁表與TLB什麼關係。
ARM的MMU中分別有64個指令TLB和資料TLB,處理器定址時的虛真實位址轉換是MMU在TLB之間進行匹配完成映射的,但是在核心初始化中會在記憶體中建立頁表swapper_pg_dir(該過程可看我的另一篇博文:http://blog.csdn.net/skyflying2012/article/details/41447843),並將該地址配置到CP15寄存器中。這個頁表跟ARM的TLB什麼關係。
920T的MMU一章我找到了答案,如下:
CPU在訪問VA(虛擬位址)時,TLB硬體完成VA到PA(物理地址)的轉換,但是如果沒有該VA的TLB entry,MMU的硬體單元translation table walk hardware(頁表索引單元)會索引CP15寄存器c0提供的記憶體頁表,進行地址轉換,擷取PA進行訪問。並且會將該頁表資訊更新到TLB中,頁表跟TLB可不是一個概念,TLB是針對於記憶體頁表的一個緩衝硬體。
也就是說ARM的MMU不僅使用TLB進行地址轉換,還能夠對記憶體中提供的頁表進行解析並地址轉換,而TLB中儲存的是CPU最常用的一些地址。TLB速度快,這樣可以加快地址轉換效率。
如果都找不到該VA的頁表資訊,MMU會向CPU發出異常(根據data還是instruct不同,發出data abort或者instruct abort),異常處理函數中進行頁表填充。
這也就讓我明白了為什麼在核心初始化的create_mapping函數(記憶體映射的關鍵函數,以後會講到)以及缺頁異常處理函數do_page_fault中看到的都是對記憶體頁表的更新,而沒有操作TLB。因為ARM的MMU本身就會使用記憶體頁表。
並且ARM直接操作TLB比較複雜,不如操作記憶體頁表,讓MMU根據記憶體頁表自己去更新TLB。
當然了,ARM的MMU所能操作的記憶體頁表也是有固定格式的,這就是我們的下一個問題了。
二 看核心代碼,ARM LINUX使用的二級頁表映射,那麼ARM的MMU硬體如何完成VA到PA的轉換。
ARM的MMU使用記憶體頁表如何完成地址轉換,手冊一張圖將MMU操作頁表的幾種方式列了出來,如下:
如果這張圖能夠完全看懂,ARM的MMU硬體的地址轉換就算完全明白了。可以看出ARM的MMU完成地址轉換的方式有好多種,總體分為2種,section-mapping和page-mapping。linux的二級頁表方式屬於page-mapping,不過2種方式的映射linux核心都用到了,這個以後再說。
我們交給CPU的記憶體頁表(寫入CP15的C0)是一級頁表(也可以稱為頁目錄)地址,該頁表總共有4096個索引,每個索引佔4 bytes,單個表項可以映射1MB的地址空間,這樣16KB大小的頁表就可以囊括32位CPU可以定址到的最大4GB空間。
可以想象,查詢這4096個索引,僅需32位虛擬位址的高12位,CPU首先擷取頁目錄基地址(TTB),加上待轉換虛擬位址的高12位,即擷取了該虛擬位址的頁目錄項。這個過程對於section-mapping和page-mapping都是一樣的,那如何區分映射方式呢,關鍵在與頁目錄項的最低2bit,如下:
MMU根據頁目錄項最低2bit來判斷接下來該如何操作,全0,無效頁目錄,MMU會向CPU發出缺頁異常。page-mapping又會細分為coarse page table(粗頁表)和fine page table(細頁表),區別在於二級頁表映射的是64K/4K頁還是1K頁,linux核心採用的是4K頁,因此本文章著重說明粗頁表中的4K頁。
接下來來看section-mapping和page-mapping的具體虛真實位址轉換原理。
1 section-mapping
一圖流如下:
這一張圖很清晰的說明了section-mapping方式的工作原理,根據高12位索引和TTB相加擷取的頁目錄項,MMU發現低2位為10,是section-mapping,取該頁目錄項的高12位與虛擬位址的低20位拼接,就擷取到了物理地址,完成轉換。
2 page-mapping
一圖流如下:
這張圖說明白了page-mapping方式中4K頁的工作原理,是一個二級頁表方式,可以細分為5步:
(1)MMU由CP15的C0取出TTB(頁目錄)基址,與VA(虛擬位址)高12位相加,擷取該VA在頁目錄中的對應頁目錄項值。
(2)MMU擷取頁目錄項最低2bit,是01,說明本次映射的1MB資料為4k小頁的page-mapping。
(3)MMU擷取頁目錄項的高22位(頁表是256X4=1K,所以頁表基址是1K對齊的)是頁表基地址,與VA的中間8位相加,即該VA的對應頁表項地址,從而擷取VA對應的頁表項值(page table entry)
(4)MMU擷取頁表項值的高20位,這就是該4K頁對應的物理地址了,與VA低12位相加(也就是4K頁內的位移),這就是VA對應的物理地址了
(5)MMU訪問該物理地址,進行CPU給出的讀寫操作
上面說明了2種映射方式的虛真實位址轉換邏輯,可以看出,不管section-mapping還是page-mapping,在一級頁表中都是完成1MB地址的映射,而page-mapping的第二級頁表項中完成4K頁的映射。
因此不管第一級頁表項還是第二級頁表項中除了儲存物理地址,還會有很多bit是空餘的,這些空餘的bit完成了對所映射地址的存取權限以及操作屬性的控制,主要包括AP位(access permission)和cache屬性位,對於section-mapping,其控制位在第一級頁表中(因為它只有一級啊),section-mapping的第一級頁表項位定義如下:
對於page-mapping,其控制位主要在第二級頁表項中,定義如下:
對於這些bit這裡不詳細說了,待後續遇到具體問題時在來分析。
到這裡,對於ARM的MMU在虛真實位址轉換的工作原理上已經都解釋清楚了,有了這些硬體基礎,再去學習linux核心的頁表機制就會更加輕鬆一些。
接下來,咱們就跳進linux的代碼中去分析分析。