天天看點

盡力說透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相連的子產品,其功能就是是把虛拟位址轉化成實體位址,我們搞個圖來看看:

盡力說透linux記憶體管理

圖相當明顯了,功能上沒什麼好說的。但具體到這個子產品是怎麼工作的,怎麼配置的,那就有的說了,再搞張圖來看看:

盡力說透linux記憶體管理

上圖是個兩級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的中介,它到底做了哪些具體的事呢?大體說來可以描述如下:

盡力說透linux記憶體管理

從上面的圖中可以看出,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項等等。

盜個圖來看看:

盡力說透linux記憶體管理

圖中隻能是示意圖,是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技術

未完待續

繼續閱讀