盡力說透linux內存管理

前言廢話:
linux內存管理涉及的原理知識太多了,也是學習linux系統軟硬件繞不開的部分,筆者水平有限,只能隨心列出一點點理解,希望能幫助到衆多學習linux的技術人員。

我們知道處理器core序列化執行指令,第一步是讀指令,從哪裏讀呢,當然是支持隨機訪問的ram存儲器(norflash也可以,這裏不說)。
當然啦,要讀ram,就需要一個叫做地址的東西去規定從ram的哪一個單元去讀,這裏拿32位core總線來舉例簡單說明硬件原理,從硬件上說就是在某幾個時鐘週期內給這32位地址總線上加上不一樣的電平,比如這32位地址線的狀態是10000000 00000000 00000000 00000000,就代表要操作第0x80000000的存儲單元,然後再給用電平邏輯告訴ram是要讀,然後就用數據總線輸出這個單元的數據。

處理器core執行指令幹嘛呢?當然是處理數據,對於cpu而言,直接的數據來源可能是ram,也可能是某外設(統一編地址獨立編址,這裏不說),都是需要一個地址來表徵要操作哪個單元。
綜上所述,地址很重要。
正題:

1.三類地址:邏輯地址 虛擬地址 物理地址

物理地址:當存儲器與cpucore總線接好之後,每一個存儲單元的地址在特定的配置下是唯一的,不變的,每個存儲單元都有一個地址,這個地址就是物理地址。

邏輯、虛擬地址:linux系統中,虛擬地址是理論地址,是理論上能操作的地址,有一個範圍,比如32位系統,就是0~0xffffffff,並且在linux系統中,邏輯地址與虛擬地址一致,舉例,邏輯地址的0x88888888就是虛擬地址的0x88888888,可這個邏輯、虛擬的0x88888888可不一定對應到物理地址的0x88888888,可能一會是0x02019888一會是0x12019888,linux內存管理的魅力就在這裏。那乾脆一個名字不行麼,不行,邏輯地址只是說和虛擬地址在linux系統中一致,他它兩在概念上差別可大了,這裏我們不多說。

使用了虛擬地址之後,整個軟件上層的設計變得簡單多了,代碼的編譯連接,操作系統內核、應用空間的地址規劃,應用程序的執行地址空間等等都統一的使用虛擬地址,與硬件平臺地址的耦合性大大降低,使得軟件的開發移植變得相當容易,通用性大大增強。

一個現實的問題是,不是所有使用32位core的系統都要配全4G地址的存儲和設備,在linux早期很多系統只有幾百k 大小的ram,那虛擬地址和物理地址一定要產生某些聯繫纔行,我們接下來就來看看linux下這個邏輯、虛擬地址(以下統稱虛擬地址)是怎樣與物理地址產生關聯的。

2.MMU
MMU是一個與core相連的模塊,其功能就是是把虛擬地址轉化成物理地址,我們搞個圖來看看:
在這裏插入圖片描述
圖相當明顯了,功能上沒什麼好說的。但具體到這個模塊是怎麼工作的,怎麼配置的,那就有的說了,再搞張圖來看看:
在這裏插入圖片描述
上圖是個兩級mmu的映射表示,20-31 共12bits一級,對應爲4096個項,第二級12-19共8bits共256項,最後0-11共12bits是OFFSET。
舉例:給一個虛擬地址1000 0000 0001 0001 0001 0000 0000 0100對應的物理地址是啥呢,我們就要去查表,首先查20-31bit 一級映射1000 0000 0001對應的物理地址的第20-31bit表的第1000 0000 0001項裏面的內容是0000 0000 0001 ,物理地址12bits就有了,以此類推,二級0001 0001項的內容是0000 0001則物理地址的中間8bit就有了,然後最後再來個0000 0000 0100的OFFSET,整個物理地址就出來了:0000 0000 0001 0000 0001 0000 0000 0100,虛擬地址0x80111008,經MMU轉化之後變成0x00111008並且值小了許多。linux虛擬地址0-4G而實際運行在物理內存只有幾百M的的系統上的原因了。
MMU硬件模塊作爲CORE和MEM的中介,它到底做了哪些具體的事呢?大體說來可以描述如下:
在這裏插入圖片描述
從上面的圖中可以看出,core訪問實際的物理存儲需要經過mmu,mmu要從物理存儲中讀取頁表,計算物理地址,然後core再用物理地址訪問真正需要的目的地,這麼幾個來回的訪問,直觀上看,訪問速度好像慢了好幾倍啊,其實並無多大影響,接着看:
這裏就不得不介紹cache了,不同的隨機存儲設備,訪問速度是不一樣的,core的主頻往往比存儲設備的主頻高的多,幾倍甚至幾十倍,數據吞吐的關鍵是外部存儲的速度,內存訪問的話,就是DDR的速度,DDR時鐘週期是N,core是M,則不論M多小,訪問一次ddr都需要至少N時間,cache的訪問速度可以與cpu相當,在mmu從物理內存中讀取頁表的時候,可以直接從cache中讀,速度極快,一個ddr的訪問週期裏面,core ,mmu,cache已經把虛擬地址,物理地址的事給做完了,並未增加多少時間,並不會需要n*N的時間,當然啦,cache大小有限,會出現miss的情況,miss了就真的需要花多餘的時間去DDR中讀頁表了。
那麼上圖中操作步驟就可以描述爲:
1—CPU內核(圖中的ARM)發出VA請求讀數據,TLB(translation lookaside buffer)接收到該地址,那爲什麼是TLB先接收到該地址呢?因爲TLB是MMU中的一塊高速緩存(也是一種cache,是CPU內核和物理內存之間的cache),它緩存最近查找過的VA對應的頁表項,如果TLB裏緩存了當前VA的頁表項就不必做translation table walk了,否則就去物理內存中讀出頁表項保存在TLB中,TLB緩存可以減少訪問物理內存的次數。
2—頁表項中不僅保存着物理頁面的基地址,還保存着權限和是否允許cache的標誌。MMU首先檢查權限位,如果沒有訪問權限,就引發一個異常給CPU內核。然後檢查是否允許cache,如果允許cache就啓動cache和CPU內核互操作。
3— 如果不允許cache,那直接發出PA從物理內存中讀取數據到CPU內核。
4— 如果允許cache,則以VA爲索引到cache中查找是否緩存了要讀取的數據,如果cache中已經緩存了該數據(稱爲cache hit)則直接返回給CPU內核,如果cache中沒有緩存該數據(稱爲cache miss),則發出PA從物理內存中讀取數據並緩存到cache中,同時返回給CPU內核。但是cache並不是只去讀取Core所需要的數據,而是把相鄰的數據都去上來緩存,這稱爲一個cache line。

拓展:現在有6G內存,12G內存是如何處理的呢?
----用64bit的core,當然32bit的core也有過一些曲線的方法來訪問>4G的內存,這裏就不討論了。

3.linux系統三個數據集:頁目錄DIRECTORY,頁表TABLE,偏移量OFFSET
32位系統中,
頁目錄:1024項,每項32bits,每項大小範圍二進制10bits即0~1023也即1024個項,每項裏面的內容是啥呢?是物理內存中一個頁表的地址,注意是物理地址,也就是說MMU硬件上去訪問內存,從內存中拿頁表的地址,使用的是物理地址。另外這裏的1024不準確,不同體系的cpu可能不一樣,arm會有4096項,甚至將來的8192項等等。

盜個圖來看看:

圖中只能是示意圖,是intel X86 32位的情景下的圖,讀者可能會發現,與上面的mmu映射有點像哦.directory,table到底是什麼樣的數據結構,內核軟件上如何實現對他們的管理的呢,只能深入代碼來看了。

4.linux存儲系統初始化
我在剛開始接觸kernel的時候,我很崇拜和迷惑,因爲2000多萬行的代碼確實容易讓人迷惑,忽視了cpu執行代碼的本質邏輯。本質上說,在cpu看來,內核代碼和你寫的printf(“hello world”)沒有本質區別,都是要告訴我指令在哪裏,數據往那裏存,執行什麼運算,就這麼簡單。從代碼的角度看,一個段代碼,有數據段,代碼斷,堆棧段,有全局變量,有局部變量,內核代碼量再大功能再天花亂墜,這個本質沒有變。所以不要看到kernel就覺得,好複雜啊,不知道從哪入手啊,知道上面的本質之後,是不是突然覺得,也就是代碼量大一點,功能多一點而已。
挑出內存管理着一塊的內核代碼來閱讀:
首先解決第一個問題,內核image如何存放,cpu如何從ddr中取得內核的第一條指令,代碼如何指定內核的entry地址的,又是怎麼安排一個語句爲第一條指令的。下
我們知道,bootloader在把內核的image,ramdisk還有設備樹文件dtb從flash讀到內存之後,把dtb和ramdisk的分區或者地址寫在某個固定的地址上或者寄存器中,然後跳轉到kernel entry開始執行kernel。
(等待繼續)

5.內存分配管理函數(代碼)
6.內存碎片的產生以及mem pool技術
未完待續

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章