struct pglist_data {
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[GFP_ZONETYPES];
...
}
GFP_ZONETYPES是一個宏,在2.6.8的時候它如下定義:
#define GFP_ZONEMASK 0x07
/* #define GFP_ZONETYPES (GFP_ZONEMASK + 1) */
#define GFP_ZONETYPES ((GFP_ZONEMASK + 1) / 2 + 1)
也就是說每一個numa的node擁有算了一下5條zone連結清單,就這還算比較少了。
可是在高一點版本的核心中,這個本來已經很低的zonelists數量變成了2(支援numa)或者1(不支援numa),也許很多人會說,通過GFP标志已經無法控制在哪個zone中以及之下配置設定記憶體了,然而看一下get_page_from_freelist這個函數多了很多參數,其中一個high_zoneidx所起到的作用就是原來那麼多zonelist的作用,由此可見,高版本的核心絲毫沒有降低功能,倒是少維護很多連結清單。以下首先說一下早期的核心中的多條zonelist連結清單的起因以及為何那麼設計。
在早期的核心中,每一個node擁有好幾條zonelist,每一條代表一個起始zone開始的以及其下的所有的zone,但是這隻是實際上的做法,而理論上這個zonelist的數量會更多,這些zonelist代表了一個優先級序列,說明了記憶體配置設定在zone中嘗試的順序,在實作中,linux是用位掩碼來實作的。如果我們有三個zone,那麼就應該用3位掩碼中的每一個位來表示不同的zone掩碼,設如下:
0x01表示dma
0x02表示normal
0x04表示highmem
這樣3個位就有8*2...個順序,分别是(o為空):
1.o;
2.dma;
3.normal->o;
4.normal->dma;
5.highmem->o;
6.highmem->o->dma;
7.highmem->normal->o;
8.highmem->normal->dma;
9-16.前面8個方向反過來。
17-xx.兩兩反向,保持一個正向...
可是為何核心僅僅保留了3個左右的順序呢?首先是三個原則在起作用,第一個就是一緻的順序管理起來更高效而且更不容易沖突,類似單行道的作用,雖然靈活性不夠!第二個原則就是核心的記憶體管理是一個管理機構,但凡核心的管理機構,采取的原則都是平等至上的!第三個原則就是連續性管理原則,按照一定的順序依次編排,這個順序一般都是從易到難,從受影響最小的地方開始,不到萬不得已不會驚動其它。有了這三個原則的話,首先我們看一下為何沒有反向順序的zonelist,如果有的話就增加了管理負擔,因為自動記憶體置換程式(kswapd)和手動置換程式(try_to_free...)就都要在兩個方向進行掃描和管理,由于存在多條路徑掃描和配置設定,這就很不容易了解到各個zone的記憶體使用情況,進而不知道要将掃描主力放在哪個zone。那麼為何不存在跳躍的zonelist,比如跳過normal而僅僅在highmem和dma中配置設定,這是因為之是以存在一個zonelist而不是一個zone是因為在該zone中配置設定失敗之後有一個後備的可配置設定zone,由于配置設定使用的zonelist使用的順序必然是從容易配置設定的zone到難配置設定的zone排列的,那麼dma中配置設定記憶體的代價會比normal中配置設定的代價更大。在linux作業系統的記憶體管理中,主要就是管理核心記憶體(包括驅動的記憶體)和程序記憶體,在大記憶體的機器上,大量的實體記憶體無法一一映射進核心位址空間的前HIGH兆,是以實際上對于大多數核心記憶體管理來講,這些大量的記憶體對之意義不大,是以它們更多的被用于程序記憶體,程序記憶體是可以随意映射進自己的位址空間的,不要求映射的方式--比如線性映射,也不要求連續性。是以程序記憶體優先從highmem這個zone中配置設定,如果不行的話,則最好先在normal中看看,然後再往dma走。對于核心記憶體管理而言,在系統初始化的時候,一些核心的資料和代碼已經映射完畢了,需要記憶體的大戶都是驅動或者子產品或者就是諸如程序管理和網絡協定棧的動态部分,比如新申請了一個task_t結構體,或者新配置設定了一個sk_buff結構體等等,這些記憶體一般從normal區開始配置設定,這是因為使用normal區的記憶體可以線性映射進系統核心,操作起來更高效。由于沒有誰擁有特權,是以大家都遵守一樣的原則,從它所可以觸及的最高zone(gfp_flags決定)依次嘗試到最低的zone,如果不行則手工調用try_to_free...從該zonelist的開始zone開始釋放一些記憶體,這樣做真的是很簡單很高效!核心之是以不通知使用者程序和驅動就直接在某個zone為之配置設定了記憶體是因為zone是作業系統記憶體管理子產品最低層的概念,程序或者驅動甚至都不應該知道有zone的存在,是以核心才可以采取遵循以上三個原則的政策将實體記憶體分為若幹個zone,然後按照從高到低的順序依次嘗試配置設定記憶體,直到成功。
後期的核心将上述機制的實作改變了,每一個pglist_data在沒有numa的情況下隻有一條zonelist,按照上述的三個原則,其實沒有必要搞那麼多連結清單的,由于不存在跳躍,不存在逆向,是以隻需要一個連結清單和一個上限值即可,每次配置設定記憶體的時候從這個上限值代表的zone往下開始嘗試即可,這樣就可以省去一些空間和管理費用,将事先設定好N條zonelist轉為直到實際上配置設定記憶體的時候再确定上限:
struct page *__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
struct zonelist *zonelist, ...)
{ //參數中的zonelist其實就是那唯一的一條zonelist(沒有numa的情況下)
enum zone_type high_zoneidx = gfp_zone(gfp_mask);
如此一來,high_zoneidx這個變量就可以作為一個限制值,在get_page_from_freelist的時候設定一個閥值,雖然__alloc_pages_nodemask函數增加了一些分量,但是時間一點也不損耗多少,取消了幾個連結清單,運作時增加了幾個指令周期,這也許(很可能)是值得的。
本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271159