1. 程式人生 > >實體記憶體分配與回收(2)

實體記憶體分配與回收(2)

1.物理頁面的分配
函式__get_free_pages用於物理頁塊的分配,其定義如下:

unsigned long __get_free_pages(int gfp_mask, unsigned long order);
其中gfp_mask是分配標誌,表示對所分配記憶體期間的特殊要求。常用的標誌為GFP_KERNEL和GFP_ATOMIC,前者表示在分配記憶體期間可以睡眠,在程序中使用;後者表示不可睡眠,在中斷處理程式中使用。
order是指數,所請求的頁塊大小為2的order次冪個物理頁面,即頁塊在free_area陣列中的索引。

該函式所作工作如下:
(1)檢查所請求的頁塊是否能夠滿足

if(order >= 10) goto nopage;

(2)檢查系統中空閒物理頁的總數是否以低於允許的界限

if(nr_free_pages > freepages.min){
if(!low_on_memory) goto ok_to_allocate
if(nr_free_pages >= freepages.high) {
low_on_memory = 0 ;
goto ok_to_allocate;
}
}

因為實體記憶體是十分緊張的系統資源,很容易被用完。而一旦記憶體被用完,當出現特殊情況時系統將無法處理,因此必須留下足夠的記憶體以備急需。另外,當系統中空閒記憶體數量太少時,要喚醒核心交換守護程序,讓其將核心中的某些頁交換到外存,從而保證系統中有足夠的空閒頁塊。為此,Linux系統定義了一個結構變數freepages,

struct freepages_v1 {
unsigned int min; //系統中的空閒物理頁面絕對不能少於該值
unsigned int low; //系統中的空閒物理頁面少於該值時加強交換
unsigned int high;//系統中的空閒物理頁面少於該值時啟動後臺交換
}freepages_t;
freepages_t freepages;

在系統初始化時,變數freepages被賦予了初值,其各個界限值的大小是根據實際實體記憶體計算出來的。
全域性變數nr_free_pages中記錄的是系統中當前空閒的物理頁面。
如果空閒物理頁數大於freepages.min的界限,則正常分配;否則,換頁。
(3)正常分配。從free_area陣列的第order項開始。
a.如果該連結串列中有滿足要求的頁塊,則執行以下操作:
a)將其從連結串列中摘下;
b)將free_area陣列的點陣圖中該頁塊所對應的位取反,表示頁塊已用;
c)修改全域性變數nr_free_pages(減去分配出去的頁數)
d)根據該頁塊在mem_map陣列中的位置,算出其初始實體地址,返回。

b.如果該連結串列中沒有滿足的頁塊,則在free_area陣列中順序向上查詢。其結果有以下兩種情況:
a)整個free_area陣列中都沒有滿足要求的頁塊,此次無法分配,返回。
b)找到一個滿足要求的頁塊,則執行以下操作:
(a)將其從連結串列中摘下;
(b)將free_area陣列的點陣圖中該頁塊所對應的位取反,表示頁塊已用;
(c)修改全域性變數nr_free_pages(減去分配出去的頁數);
(d)因為頁塊比申請的頁塊要大,所以要將他分成適當大小的塊。因為所有的頁塊都有2的冪次的頁陣列成,所以這個分割的過程比較簡單,只需要將它平分就可以,其方法如下:
i.將其平分為兩個夥伴,將小夥伴加入free_pages陣列中相應的連結串列,修改點陣圖中相應的位;
ii.如果大夥伴仍比深情的頁塊大,則轉i,繼續劃分。
iii.大夥伴的大小正是所要的大小,修改點陣圖中相應的位,根據其在mem_map陣列中的位置,算出它的其實實體地址,返回。
(4)換頁。通過下列語句呼叫函式try_to_free_pages(),啟動換頁程式。

try_to_free_pages(gfp_mask)

該函式所作的工作十分簡單,喚醒核心守護程序kswapd:

wake_up_process(kswapd_process);

其中kswapd_process是指向核心交換守護程序kswapd的指標。(kewapd將在交換機制提到)

2.物理頁面的回收
分配頁塊的過程將大的頁塊分為小的頁塊,將會使記憶體更為零散。頁回收的過程與頁分配的過程正好相反,只要可能,就把小頁塊合併成大頁塊。
函式free_pages用於頁塊的回收,其定義如下:

void free_pages(unsigned long addr, unsigned long order)

其中addr是要回收的頁塊的首地址;order指出要回收的頁塊的大小為2的order次冪個物理頁。
該函式所作工作如下:
(1)根據頁塊的首地址addr算出該頁塊的第一頁在mem_map陣列中的索引。
(2)如果該頁核心在使用,則不允許收回。
(3)將頁塊第一頁對應的page結構中的_count域減1,表示引用該頁的程序數減1個。如果_count域的值不是0,則說明還有別的程序使用該頁塊,因此不能回收它,簡單的返回。
(4)清除頁塊第一頁對應的page結構中flags域的PG_referenced位,表示該頁塊不再被引用。
(5)調整全域性變數nr_free_pages,將其值加上回收的物理頁數。
(6)將頁塊加入到陣列free_area的相應連結串列中。
要加入連結串列由order引數指定,即將頁塊加入到free_area[order]連結串列中。檢查free_area[order]的點陣圖map,看該頁塊的夥伴是否已在連結串列中(在點陣圖中,兩夥伴使用同一位,實則就是每一個頁塊用一位表示其狀態,當夥伴拆開後分成兩個頁塊就由各自頁塊的map表示其各自的狀態)。
a)其夥伴不在連結串列中,說明該頁塊的夥伴還在使用,不需要合併。此時只需要將點陣圖中該頁塊相應的位取反,表示頁塊已經自由,將其加入到free_area[order]連結串列的頭部。
b)其夥伴在連結串列中,說明頁塊及其夥伴均獲取自由,可以將它們合併成更大的頁塊。將頁塊的夥伴從連結串列中摘下,將他們在點陣圖中對應的位取反,表示頁塊已不可用;計算新的大頁塊在mem_map陣列中的索引(頁塊索引和夥伴索引的小索引);order++,轉加入過程,將大頁塊加入到陣列free_area的相應連結串列中。