linux內(nèi)核解析之--內(nèi)存管理
本文主要介紹了linux內(nèi)核的內(nèi)存管理機制。什么是內(nèi)存管理機制?內(nèi)存管理主要負責完成當進程請求內(nèi)存時給進程分配可用的內(nèi)存,當進程釋放內(nèi)存時,回收相應的內(nèi)存,同時負責跟蹤系統(tǒng)中相應內(nèi)存的使用狀態(tài)。
Linux采用頁式內(nèi)存管理,頁是物理內(nèi)存管理的基本單位。但嚴格來說Linux采用的是段頁式內(nèi)存管理,既分段也分頁。內(nèi)存映射的時候,先確定對應的段,確定段基地址,段內(nèi)分頁,再找到對應的頁表項,確定頁基地址,再由邏輯地址的低位確定的頁偏移量就能找到最終的物理地址。但Linux中的所有段地址都是0,即所有的段是相同的,之所以有段的概念是因為Linux為了符合硬件體系。所以Linux實際采用的是頁式內(nèi)存管理,但段的概念在內(nèi)核中確實存在。
1、物理內(nèi)存的管理
Linux中首先將內(nèi)存分為若干個節(jié)點,每個節(jié)點下面又可分為1~3個區(qū),每個區(qū)下面會有若干個頁。
(1)節(jié)點
內(nèi)存節(jié)點主要是依據(jù)CPU訪問代價不同而劃分的。一個CPU對應一個節(jié)點。內(nèi)核數(shù)組node_data[]形式組織節(jié)點,存儲的為struct page_data_t指針來描述內(nèi)存分區(qū)。
(2)區(qū)
內(nèi)核以struct_zone來描述內(nèi)存分區(qū)。內(nèi)核將所有的物理頁分為3個區(qū):ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM。 ZONE_DMA區(qū)中包含的頁可以用來進行DMA操作,即直接內(nèi)存訪問操作,通常為物理內(nèi)存的起始16M。ZONE_NORMAL區(qū)包含的頁是可以進行正常的內(nèi)存映射的頁物理內(nèi)存為16~896M。ZONE_HIGHMEM區(qū)稱為“高端內(nèi)存”,該區(qū)所包含的頁不可以進行永久映射,即不可以永久映射到內(nèi)核地址,物理內(nèi)存896M以后的。
高端內(nèi)存的邊界為896M的原因:32為Linux系統(tǒng)中虛擬內(nèi)存空間為0-4G,3G-4G為內(nèi)核態(tài)。為了應對內(nèi)核映射超過1G,Linux采取的策略:內(nèi)核地址空間的896M采用固定映射,映射方法:虛擬地址-3G=物理地址,只能映射896M,即3G~3G+896M,剩余的128M(3G+896M~4G)采用動態(tài)映射。 Linux下以struct zone結(jié)構(gòu)體來表示一個區(qū),在該結(jié)構(gòu)體中變量struct page *zone_mem_map用來管理該區(qū)下的內(nèi)存映射表。
(3)頁
每一個物理頁框都使用一個數(shù)據(jù)結(jié)構(gòu)struct page來描述,該結(jié)構(gòu)體中的lru變量構(gòu)建用于LRU頁面置換的鏈表。在頁框空閑情況下,該成員變量用于構(gòu)建伙伴算法、鏈表同等大小的空閑內(nèi)存塊。大多數(shù)32bit的操作系統(tǒng)的頁大小為4KB。
2、伙伴算法
Linux采用的是伙伴(Buddy)算法對物理內(nèi)存進行管理?;锇闄C制是操作系統(tǒng)的一種動態(tài)存儲管理算法,該算法通過不斷平分較大的空閑內(nèi)存塊來獲得較小的空閑內(nèi)存塊,直到獲得所需的內(nèi)存塊。當內(nèi)存釋放時,該算法盡可能的合并空閑塊。該算法要求內(nèi)存塊的分配和合并都是以2的冪次方為單位。 在“區(qū)”內(nèi)存結(jié)構(gòu)體struct zone中有一struct free_area類型的數(shù)組free_area[],數(shù)組最大為12個元素。數(shù)組的下標k對應著固定大小2^k個頁框空閑內(nèi)存區(qū)域的雙向鏈表頭。當需要空閑塊為4(即2^2)個頁框時則查找free_area[2],如果沒有合適的,則查找free_area[3],直到找到合適的。
3、slab分配器
Linux中引入slab是為了減少對伙伴算法的調(diào)用,采用slab分配器來減少頻繁分配和釋放內(nèi)存數(shù)據(jù)結(jié)構(gòu)的開銷,同時減少了碎片的產(chǎn)生。slab分配機制是基于伙伴算法之上實現(xiàn)。 slab是基于一組對象緩存,把不同對象劃分為caches(物理內(nèi)存),每個cache保存一種類型的對象,每個cache由一個或者多個slab組成,每個slab包含一個或者多個page組成。
每個slab處于3中狀態(tài)之一,即full、partial和empty(分別是滿、部分滿、空),其中滿狀態(tài)的slab沒有任何可分配的空閑對象。當請求空閑對象時則從部分滿和空的slab中分配。
Linux內(nèi)核中的cache以結(jié)構(gòu)體kmem_cache_s來表示,結(jié)構(gòu)體中變量lists中存儲的為三個鏈表分別對應于slab的三種狀態(tài)。 總結(jié)來說,當為一對象申請內(nèi)存時,首先查找到該對象的cache,然后查找cache中的slab列表,分配空閑內(nèi)存。當釋放該對象內(nèi)存時,則返回給該對象對應的slab。這樣伙伴算法就不需要頻繁的進行分配和合并操作。
4、虛擬內(nèi)存
(1)邏輯地址->線性地址->物理地址的轉(zhuǎn)換過程
邏輯地址即程序指令的地址,線性地址指頁式轉(zhuǎn)換前的地址(虛擬地址),物理地址則是物理內(nèi)存中的地址。
一個邏輯地址由兩部份組成,段標識符: 段內(nèi)偏移量。段基址確定它所在的段居于整個存儲空間的位置,偏移量確定它在段內(nèi)的位置。Linux中由于段基址都是0,所以邏輯地址和線性地址相同。線性地址再通過MMU進行轉(zhuǎn)換到物理地址,這個過程下面重點講下。 Linux也是內(nèi)存管理使用三級表結(jié)構(gòu):頁目錄、頁中間目錄、頁表。一個活動任務都有一個頁目錄,大小一般為一頁,頁目錄必須在內(nèi)存中。頁中間目錄可以跨越多個頁。頁表同樣可以跨越多個頁,對應具體的頁框。具體過程如下圖:
(2)頁面置換算法
Linux中頁結(jié)構(gòu)體的組織方式為雙向循環(huán)鏈表。Linux中頁面置換算法基于時鐘算法機制實現(xiàn),頁結(jié)構(gòu)體page中有一變量count專門用來計算頁面被引用的次數(shù)。每當頁面被訪問一次時,count加1。在Linux后臺,Linux周期性地掃描全局頁池,并且當它在內(nèi)存中的所有頁間循環(huán)時,將掃描的每一頁的count減1。age越大則使用頻率越高。最終內(nèi)核通過最近未使用(LRU)算法進行頁面置換。
5、高速緩存
Linux使用了一系列的高速緩存相關的內(nèi)存管理技術來提高性能。此處的高速緩存并非
是物理緩存,而是軟件方法。Linux中主要包括以下幾個緩存:
(1)Buffer Cache,包括了用于塊設備驅(qū)動程序的數(shù)據(jù)緩沖區(qū)。這些緩存區(qū)固定(一般512B),包括從塊設備要讀取的數(shù)據(jù)和要寫入塊設備的數(shù)據(jù)。操作時先查看緩沖區(qū)。
(2)Page Cache,用來加快對磁盤上映像和數(shù)據(jù)的訪問。用來緩存文件的邏輯內(nèi)容,一次緩存一頁。
(3)Swap Cache,只有改動過的(或臟)頁才存在交換文件中,只要交換文件沒有再次修改,下次這些頁需要交換出時就不需要再寫到交換文件中。
(4)Hardware Cache,常見方法是在處理器中PTE的高速緩存。這種情下處理器不需要直接讀取頁表,需要時把頁表放在緩存區(qū)中。CPU有轉(zhuǎn)換表緩沖區(qū)(TLB),來快速查找置換頁表。