本文轉載自 嵌入式Linux中文站
連結:http://mp.weixin.qq.com/s/nbv2dY5b3fCPnsC9fU35mQ
講解複雜繁瑣的機制原理,最通俗的方法就是用模型架構的方式向讀者呈現,先要在整體上瞭解大方向大架構,再根據大方向大架構來進行分支深入,猶如毛主席那句話“戰略上蔑視敵人,戰術上重視敵人”。下麵我也以這種方式把各個大模型方式向大家畫出,並作出簡略解述。
一. 地址劃分。
1. CPU地址。
CPU地址是指CPU的地址匯流排能定址的範圍,32bit-CPU定址範圍為4G, 這個地址是虛擬的,實際上外部物理記憶體是不會使用這麼大的記憶體。
CPU虛擬地址的4G空間,通常劃分為兩部分,一部分為核心虛擬地址,通常為3G-4G之間,另一部分為使用者虛擬地址,通常為0G-3G之間,顯然,使用者行程能使用的虛擬地址範圍遠大於內核可以使用的虛擬地址空間,但是,物理記憶體只有侷限性的幾M,幾G,核心虛擬地址如何使用物理記憶體,使用者空間如何使用物理記憶體,這些問題正是linux記憶體管理的關鍵。
2. 物理記憶體
物理記憶體是指外部儲存資料的裝置,有可以被CPU定址到的地址匯流排,受到CPU的Cache 和TLB/MMU管理定址。
需要澄清一個概念:任何程式碼是在CPU上執行的,而不是在物理記憶體上,物理記憶體是個裝置,用於存放使用者行程空間的可執行程式碼或者核心關鍵資料結構,這些程式碼或結構終將是要受到CPU透過MMU定址,Cache命中指令資料來獲取的。
NUMA的全稱是非一致性記憶體訪問,它通常是多核訪問的概念,每一個CPU核都會有一個節點對應使用一部分物理記憶體,對這些節點的管理附加這些資料結構:perCPU變數,list表串聯各節點遍歷,zone的劃分,zonelist的管理等等。為了使問題更加簡單化,我們只分析UMA的一個節點的情況,當然它也包含NUMA的一些資料結構特徵,這個後面會有所簡述。
下圖是NUMA的一個簡略圖抽象如圖2-1所示。
圖2-1 NUMA多核物理記憶體zone示意圖
3. 核心虛擬地址空間劃分。
如果讀者僅僅瞭解一些皮毛,必然認為內核的虛擬地址空間僅有邏輯地址這一說,其實這隻是記憶體核心虛擬地址劃分的一個特例,並非全部的完整表述,現在我劃出完整的圖形,並且改變改變對核心虛擬地址空間名稱的叫法,如圖2-2
圖2-2 核心虛擬地址空間劃分及其對物理記憶體的對映
下麵來改改名字咯,直接對映的地址我們可以叫為核心物理直接對映地址或者邏輯地址。linux原則上只能使用虛擬空間1G中的896M,剩下的128M留作它用,所以直接對映之外的物理記憶體稱為高階記憶體。128M之間的空間又劃分為多個gap安全間隙,虛擬地址,固定對映和持久對映,註意這裡的虛擬地址叫法通常和前述的核心虛擬地址有些混雜,後者是指CPU核心虛擬地址,是更廣的概念。由於直接對映的部分有了名字叫邏輯地址,那麼這裡的虛擬地址空間常專指這個部分。
虛擬地址有以下用途,使用vm_struct結構體經核心管理高階記憶體,它可以使用kmap方式獲取高階物理記憶體的空間;也可以不對映物理高階記憶體,將這段地址直接作為外部物理裝置的ioremap地址,從而可以直接操縱裝置,當然這也將外部裝置地址空間暴露出來並且容易造成幹擾,所以通常不能直接訪問ioremap對映的地址而是用readb/writeb讀寫,而且要做好最佳化屏障設定並且用iounmap釋放,因為映射了的裝置常具有’邊際效應’.
如果沒有高階記憶體,(當然32bit的嵌入式系統通常不會使用高階記憶體,至少我見過的那麼多關於ARM,powerPC,MIPS32的嵌入式應用都是沒有使用高階記憶體的), 那麼固定對映和持久對映也多半不會用到。固定對映可以指定長期持有物理記憶體某些地址頁的佔用,這個對映關係可以在初始階段進行配置,而持久對映在啟用時就建立了同高端記憶體物理頁的對映關係,它在其他階段都不會被解除。
強調的是,我這裡不關心高階記憶體,內核的直接對映邏輯地址就可以涵蓋全部物理記憶體。
4. 使用者虛擬地址空間的劃分
使用者虛擬地址空間圖構並不複雜,複雜的是它在虛擬記憶體空間中的應用,如何對映檔案,如何組織區間對映,關聯的行程是誰,對應的記憶體結構體實體是什麼等等問題才是使用者虛擬對映最難的地方,下麵僅僅劃出圖示,對使用者虛擬記憶體空間可以先有一個大瞭解,如圖2-3。
圖2-3使用者空間虛擬記憶體佈局
既然使用者空間是虛擬的,那麼它是怎麼訪問物理記憶體的呢,當然就是PGD,PUD,PMD,PTE,OFFSET及其TLB快表查詢了,上層目錄入口PUD和中間目錄入口一般不考慮,考慮二級目錄就可以了。從網上摘的圖2-4:
圖2-4 使用者行程空間訪問物理記憶體的方法
二. 夥伴系統
夥伴系統是按階管理外界物理記憶體的方法,最大有11階,每一階有一個或者多個頁合併的集合併使用指標串聯起來,同時在同一階中的一個或多個頁集合中形成各自的夥伴,要強調的是各個階的夥伴都是等頁個數的,用下圖2-5是比較好理解的。
圖2-5 夥伴系統在記憶體中的大致模型
當核心申請一段按頁卻並非按照階數分配的記憶體時候,通常會使用夥伴系統原理將其按照該申請空間的最大階數分配,多出來的頁按照夥伴系統演演算法歸併到其他階的連結串列當中形成其他階的新夥伴。釋放該記憶體空間的時候,釋放的空間會嘗試找到能以它為夥伴的那個階進行連線,如果大小超過,則劈開,多餘的再尋找其他可以以它為夥伴的階。夠拗口的,但還是很容易理解的,後面會有原始碼呈現出來以實體詳細分析。
三. 反碎片技術:
反碎片機制其實還在夥伴系統之前,它主要是將各個zone區域的物理記憶體分成可回收reclaimable但不可移動unmovable,可移動movable,不可移動unmovable. 這些標記按照一定得list串聯起來管理,當外部條件申請物理記憶體導致許多碎片的時候,它可以按照這些資料結構的標誌,來從新組織歸類物理記憶體,從而減少碎片頁或者孤獨頁。反碎片技術在嵌入式系統當中少用,絕大部分由夥伴系統佔據江山了,因此不會對此做具體分析,簡略過之。
四. Slab分配機制。
眾所周知,作業系統使用夥伴系統管理記憶體,不僅會造成大量的記憶體碎片,同時處理效率也較低下。SLAB是一種記憶體管理機制,其擁有較高的處理效率,同時也有效的避免記憶體碎片的產生,其核心思想是預分配。其按照SIZE對記憶體進行分類管理的,當申請一塊大小為SIZE的記憶體時,分配器就從SIZE集合中分配一個記憶體塊(BLOCK)出去,當釋放一個大小為SIZE的記憶體時,則將該記憶體塊放回到原有集合,而不是釋放給作業系統。當又要申請相同大小的記憶體時,可以復用之前被回收的記憶體塊(BLOCK),從而避免了記憶體碎片的產生。[註:因SLAB處理過程的細節較多,在此只是做一個原理上的講解]
2 總體結構
圖1 SLAB記憶體結構
3 處理流程
如圖1中所示:SLAB管理機制將記憶體大體上分為SLAB頭、SLOT陣列、PAGES陣列、可分配空間、被浪費空間等模組進行分別管理,其中各模組的功能和作用:
SLAB頭:包含SLAB管理的彙總資訊,如最小分配單元(min_size)、最小分配單元對應的位移(min_shift)、頁陣列地址(pages)、空閑頁連結串列(free)、可分配空間的起始地址(start)、記憶體塊結束地址(end)等等資訊(如程式碼1所示),在記憶體的管理過程中,記憶體的分配、回收、定位等等操作都依賴於這些資料。
SLOT陣列:SLOT陣列各成員分別負責固定大小的記憶體塊(BLOCK)的分配和回收。在nginx中SLOT[0]~SLOT[7]分別負責區間在[1~8]、[9~16]、[17~32]、[33~64]、[65~128]、[129~256]、[257~512]、[513~1024]位元組大小記憶體的分配,但為方便記憶體塊(BLOCK)的分配和回收,每個記憶體塊(BLOCK)的大小為各區間的上限(8、16、32、64、128、256、512、1024)。比如說:假如應用行程請求申請5個位元組的空間,因5處在[1~8]的區間內,因此由SLOT[0]負責該記憶體的分配,但區間[1~8]的上限為8,因此即使申請5個位元組,卻依然分配8位元組給應用行程。以此類推:假如申請12位元組,12處於區間[9~16]之間,取上限16,因此由SLOT[1]分配16個位元組給應用行程;假如申請50位元組,50處於區間[33~64]之間,取上限64,因此由SLOT[2]分配64個位元組給應用行程;假如申請84位元組,84處於區間[65~128]之間,取上限128,因此由SLOT[3]分配128個位元組;…;假如申請722位元組,722處於區間[513~1024]之間,取上限1024,因此由SLOT[7]分配1024位元組。
PAGES陣列:PAGES陣列各成員分別負責可分配空間中各頁的查詢、分配和回收,其處理流程可參考3.2節的說明。
可分配空間:SLAB在邏輯上將可分配空間劃分成M個記憶體頁,每頁大小為4K。每頁記憶體與PAGES陣列成員一一對應,由PAGES陣列各成員負責各記憶體頁的分配和回收。
被浪費空間:按照每頁4K的大小對空間進行劃分時,滿足4K的空間,將作為可分配空間被PAGES陣列進行管理,而最後剩餘的不足4K的記憶體將會被捨棄,也就是被浪費了!