1.伙伴系統算法的提出
內核應該為分配一組連續的頁框而建立一種健壯、高效的分配策略。為此,必須解決著名的內存,也就是所謂的外鎖片問題(external fragmentation)。頻繁的請求和釋放不同大小的一組連續頁框,必然導致在已分配的塊內分散了許多小塊的空閑頁框。由此帶來的問題時,即使有足夠的空閑頁框可以滿足請求,但要分配一個大塊的連續頁框無法滿足。
從本質上來說,避免外碎片的方法有兩種:
(1)利用分頁單元把一組非連續的空閑頁框映射到連續的線性地址空間;
(2)開發一中適當的技術來記錄現存的空閑連續頁框快的情況,以盡量滿足對小塊的請求而分割大的空閑塊。
Linux內核中引入了伙伴系統算法(buddy system)。把所有的空閑頁框分組為11個塊鏈表,每個塊鏈表分別包含大小為1,2,4,8,16,32,64,128,256,512和1024個連續頁框的頁框塊。最大可以申請1024個連續頁框,對應4MB大小的連續內存。每個頁框塊的第一個頁框的物理地址是該塊大小的整數倍。例如,大小為16個頁框的塊,其起始地址是16*2^12(2^12=4096)的整數倍。
假設要申請一個256個頁框的塊,先從256個頁框的鏈表中查找空閑塊,如果沒有,就去512個頁框的鏈表中找,找到了則將頁框塊分為2個256個頁框的塊,一個分配給應用,另外一個移到256個頁框的鏈表中。如果512個頁框的鏈表中仍沒有空閑塊,繼續向1024個頁框的鏈表查找。如果1024塊存在,則將其中的256頁框作為請求返回,剩余的768分成256塊和512塊分別插到相應的鏈表中。如果仍然沒有,則返回錯誤。
頁框塊在釋放時,會主動大小為相同的一個空閑伙伴塊合成為2倍大小的單獨塊較大的頁框塊。兩個塊稱為伙伴需要滿足一下條件:
(1)兩個塊具有相同的大小
(2)它們的物理地址是連連續的。
(3)第一塊的第一個頁框的物理地址是2*b*2^12的倍數。
2.數據結構
包含一個11元素、元素類型為free_area的一個數組,每個元素對應一塊大小。
free_area每個元素中有一個free_list,表示雙向循環鏈表的頭,這個雙向循環鏈表集中了大小為2^k頁的空閑塊對應的頁描述符。該鏈表包含每個空閑頁框塊(大小為2^k)的起始頁框的頁描述符。指向鏈表中相鄰元素的指針存放在頁描述符的lru字段中。
free_area每個元素還包含一個nr_free字段,它指定了大小為2^k的頁框塊個數。當然,如果沒有大小為2^k的空閑頁框塊,則nr_free等於0且free_list為空。
一個空閑塊的第一個頁的描述符的private字段存放了塊的order,也就是k。正式由於這個字段,當頁框被釋放時,內核可以確定這個塊的伙伴是否也空閑。如果是的話就可以把兩個塊合成一個2^(i+1)的塊。
3.實現
列舉了書上的少量代碼。
(1)分配塊
__rmqueue()用來在管理區找到一個空閑塊。需要兩個參數:管理區描述符的地址和order。
struct free_area *area;
unsigned int current_order;
for(current_order=order;current_order<11;++current_order){
area=zone->free_area+current_order;//到相應的數組中
if(!list_empty(&area->free_list)) goto block_found;//進入相應的鏈表,將相應的page(描述符)從freelist中去掉
}
block_found:
page=list_entry(area->free_list.next,struct page,lru);//找到鏈表的第一個節點
list_del(&page-lru);
ClearPagePrivate(page);
page->private=0;
area->nr_free--;//相應區域的空閑頁框塊減少
zone->free_pages -= 1UL<<order;//管理區內的頁框更新
如果從curr_order鏈表中找到的塊大於order,就執行一個while循環將剩余的塊插到相應的free_list中去。例如申請256,找到1024,則把剩下的塊插到256和512的free_list中去。
size = 1<< current_order;
while(curr_order>order){
area--;
curr_order--;
size>>=1;
buddy=page+size;
list_add(&buddy_lru,&area_free_list);
area->nr_free++;
buddy->private=current_order;
setPagePrivate(buddy);
}
(2)釋放塊
__free_pages_bulk函數按照伙伴系統的策略釋放頁框。三個參數:page(被釋放塊的第一個頁框描述符地址),zone(管理區描述符地址),order(塊大小的對數)。
struct page*base = zone->zone_mem_map;
unsigned long buddy_idx,page_idx=page-base;
struct page* buddy,*coalesced;
int order_size=1<<order;
while(order<10){
buddy_idx=page_idx^(1<<order);//得到伙伴塊的索引
buddy=base+buddy_idx;
if(!page_is_buddy(buddy,order))break;//判斷符不符合buddy的條件
list_del(&buddy->lru);//滿足從鏈表中刪除去合成新的頁框塊
zone->free_area[order].nr_free--;
ClearPagePrivate(page);
page->private=0;
page_idx &= buddy_idx;
order++;
}
//合成
coalesced = base+page_idx;
coalesced ->private=order;
SetPagePrivate(coalesced );
list_add(&coalesced->lru,&zone->free_area[order].free_list);
zone->free_area[order].nr_free++;