夥伴系統演算法(ZZ)

來源:互聯網
上載者:User

講了這麼多了,很多人肯定會一頭霧水,前邊提到的都是些資料結構或者是些概念性的東西,真正對動態網頁面的管理機制在哪裡?換句話說,如何將每個節點,每個區中的頁框分配給進程?要理清這個思路,我們首先必須學習一種演算法 —— 夥伴系統演算法。

核心要分配一組連續的頁框,必須建立一種健壯、高效的分配策略。為此,必須解決著名的外部片段(external fragmentation)問題。頻繁地請求和釋放不同大小的一組連續頁框,必然導致在已指派頁框的塊內分散了許多小塊的空閑頁框。由此帶來的問題是,即使有足夠的空閑頁框可以滿足請求,但要分配一個大塊的連續頁框就可能無法滿足。

Linux 採用夥伴系統(buddy system)演算法來解決外片段問題。把所有的空閑頁框分組為11個塊鏈表,每個塊鏈表分別包含大小為1, 2, 4, 8, 16, 32, 64, 128, 256,512和1024 個連續的頁框。對1024 個頁框的最大請求對應著4MB 大小的連續RAM塊。每個塊的第一個頁框的物理地址是該塊大小的整數倍。例如,大小為16 個頁框的塊,其起始地址是16 × 212(212 = 4096,這是一個常規頁的大小)的倍數。

我們通過一個簡單的例子來說明該演算法的工作原理。

假設要請求一個256 個頁框的塊(即1MB)。演算法先在256 個頁框的鏈表中檢查是否有一個空閑塊。如果沒有這樣的塊,演算法會尋找下一個更大的頁塊,也就是,在512 個頁框的鏈表中找一個空閑塊。如果存在這樣的塊,核心就把256 的頁框分成兩等份,一半用作滿足請求,另一半插入到256 個頁框的鏈表中。如果在512 個頁框的塊鏈表中也沒找到空閑塊,就繼續找更大的塊 —— 1024個頁框的塊。如果這樣的塊存在,核心把1024個頁框塊的256 個頁框用作請求,然後從剩餘的768 個頁框中拿512個插入到512個頁框的鏈表中,再把最後的256個插入到256個頁框的鏈表中。如果1024個頁框的鏈表還是空的,演算法就放棄並發出錯訊號。

以上過程的逆過程就是頁框塊的釋放過程,也是該演算法名字的由來。核心試圖把大小為b的一對空閑夥伴塊合并為一個大小為2b的單獨塊。滿足以下條件的兩個塊稱為夥伴:
• 兩個塊具有相同的大小,記作b。
• 它們的物理地址是連續的。
• 第一塊的第一個頁框的物理地址是2×b×212的倍數。

該演算法是迭代的,如果它成功合并所釋放的塊,它會試圖合并2b 的塊,以再次試圖形成更大的塊。

看暈了吧?如果實在理解不了就自己拿筆畫一畫,這個演算法的原理還是比較簡單的,下面我們來看看Linux具體是怎麼實現的:

1 資料結構
Linux 2.6 為每個管理區使用不同的夥伴系統。因此,在80x86 結構中,有三種夥伴系統:第一種處理適合ISA DMA 的頁框,第二種處理“常規”頁框,第三種處理高端記憶體頁框。每個夥伴系統使用的主要資料結構如下:
(1)前面介紹過的mem_map數組。實際上,每個管理區都關係到mem_map元素的子集。子集中的第一個元素和元素的個數分別由管理區描述符的zone_mem_map和size欄位指定。
(2)包含有11個元素、元素類型為free_area的一個數組,每個元素對應一種塊大小。該數組存放在管理區描述符zone_t的free_area欄位中。

 

,我們考慮管理區描述符中free_area數組的第k個元素,它標識所有大小為2k的空閑塊。這個元素的free_list欄位是雙向迴圈鏈表的頭,這個雙向迴圈鏈表集中了大小為2k頁的空閑塊對應的頁描述符。更精確地說,該鏈表包含每個空閑頁框塊(大小為2k)的起始頁框的頁描述符;指向鏈表中相鄰元素的指標存放在頁描述符page的lru欄位中。

除了鏈表頭外,free_area數組的第k個元素同樣包含欄位nr_free,它指定了大小為2k頁的空閑塊的個數。當然,如果沒有大小為2k 的空閑頁框塊,則nr_free等於0且free_list為空白(free_list的兩個指標next和prev都指向它自己的free_list欄位)。

最後,一個2k的空閑頁塊的第一個頁的描述符的private欄位存放了塊的order,也就是數字k。正是由於這個欄位,當頁塊被釋放時,核心可以確定這個塊的夥伴是否也空閑。如果是的話,它可以把兩個塊結合成大小為2k+1頁的單一塊。

2 塊分配
核心使用__rmqueue()函數來在管理區中找到一個空閑塊。該函數需要兩個參數:管理區描述符的地址zone和order,order表示請求的空閑頁塊大小的對數值(0 表示一個單頁塊,1 表示一個兩頁塊,2表示四個頁塊)。如果頁框被成功分配,__rmqueue()函數就返回第一個被分配頁框的頁描述符。否則,函數返回NULL。

在__rmqueue()函數中,從所請求order的鏈表開始,它掃描每個可用塊鏈表進行迴圈搜尋,如果需要搜尋更大的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;
}
return NULL;

如果直到迴圈結束還沒有找到合適的空閑塊,那麼__rmqueue()就返回NULL。否則,找到了一個合適的空閑塊,在這種情況下,從鏈表中刪除它的第一個頁框描述符,並減少管理區描述符中的free_pages的值:
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迴圈。這幾行代碼蘊含的原理如下:當為了滿足2h 個頁框的請求而有必要使用2k個頁框的塊時(h < k),程式就分配前面的2h 個頁框,而把後面2k - 2h 個頁框迴圈再分配給free_area鏈表中下標在h到k之間的元素:
    size = 1 << curr_order;
    while (curr_order > order) {
        area--;
        curr_order--;
        size >>= 1;
        buddy = page + size;
        /* insert buddy as first element in the list */
        list_add(&buddy->lru, &area->free_list);
        area->nr_free++;
        buddy->private = curr_order;
        SetPagePrivate(buddy);
    }
    return page;

因為__rmqueue()函數已經找到了合適的空閑塊,所以它返回所分配的第一個頁框對應的頁描述符的地址page。

3 塊釋放
__free_pages_bulk()函數按照夥伴系統的策略釋放頁框。它使用3個基本輸入參數:
page:被釋放塊中所包含的第一個頁框描述符的地址。
zone:管理區描述符的地址。
order:塊大小的對數。

__free_pages_bulk()首先聲明和初始化一些局部變數:
struct page * base = zone->zone_mem_map;
unsigned long buddy_idx, page_idx = page - base;
struct page * buddy, * coalesced;
int order_size = 1 << order;

page_idx局部變數包含塊中第一個頁框的下標,這是相對於管理區中的第一個頁框而言的。order_size 局部變數用於增加管理區中空閑頁框的計數器:
zone->free_pages += order_size;

現在函數開始執行迴圈,最多迴圈 (10-order) 次,每次都盡量把一個塊和它的夥伴進行合并。函數以最小的塊開始,然後向上移動到頂部:
while (order < 10) {
    buddy_idx = page_idx ^ (1 << order);
    buddy = base + buddy_idx;
    if (!page_is_buddy(buddy, order))
        break;
    list_del(&buddy->lru);
    zone->free_area[order].nr_free--;
    ClearPagePrivate(buddy);
    buddy->private = 0;
    page_idx &= buddy_idx;   /* 合并 */
    order++;
}

這個迴圈我看了半天沒有看懂,後來舉個例子,再畫個圖才漸漸明白。比如,我們這裡order是4,那麼order_size的值為24,也就是16,表明要釋放16個連續的page。page_idx為這個連續16個page的老大的mem_map數組的下標。進入迴圈後,函數首先尋找該塊的夥伴,即mem_map數組中page_idx-16或page_idx+16的下標buddy_idx,進一步說明一下,就是為了在下標為16的free_area中找到一個閒置塊,並且這個塊與page所帶的那個擁有16個page的塊相鄰。

尤其要注意:buddy_idx = page_idx ^ (1 << order)這行代碼。這行代碼很巧妙,短小精幹。因為order一來就等於4,所以迴圈從4開始的,即第一個迴圈為buddy_idx = page_idx ^ (1<<4),即buddy_idx = page_idx ^ 10000。如果page_idx第5位為1,比如是20號頁框(10100),那麼在異或以後,buddy_idx為4號頁框(00100)。如果page_idx第5位為0,比如是第40號頁框(101000),那麼在異或以後,buddy_idx為56號頁框(111000)。

為什麼要做這麼一個運算呢?想想我們的目的是什麼。__free_pages_bulk是將以其參數page為首的2^order個頁面找到一個夥伴,並與其合并。在mem_map數組中,這個夥伴的老大要麼是在這個page的前2^order,要麼就是後2^order。如果單單是加或者減,那麼就會忽略前面的或者後面的夥伴。大家不妨對照好好的琢磨一下。至於為啥不既加又減呢,我估計Linux的開發人員們沒這麼做是因為效能的問題吧,各個資料上也說了這裡主要是盡量合并而已,我們就不去管他了。

找到夥伴以後,把該夥伴的老大page的地址賦給buddy:
buddy = base + buddy_idx;

現在函數調用page_is_buddy()來檢查buddy是否是真正的值得信賴的夥伴,也就是大小為order_size的空閑頁框塊的第一個頁。
int page_is_buddy(struct page *page, int order)
{
    if (PagePrivate(buddy) && page->private == order &&
          !PageReserved(buddy) && page_count(page) ==0)
        return 1;
    return 0;
}

正如所見,要想成為夥伴,必須滿足以下四個條件:

(1)buddy的第一個頁必須為空白閑(_count欄位等於-1);

(2)它必須屬於動態記憶體(PG_reserved 位清零);

(3)它的private 欄位必須有意義(PG_private 位置位);

(4)它的private欄位必須存放將要被釋放的塊的order。

如果所有這些條件都符合,就說明有新的夥伴存在啦,那麼夥伴塊就要跟我page結合,先必須得脫離原來的free_list,執行page_idx &= buddy_idx合并(注意,這行代碼與前邊的buddy_idx = page_idx ^ (1 << order)是緊密結合的),並再執行一次迴圈以尋找兩倍大小的夥伴塊。

如果page_is_buddy()中至少有一個條件沒有被滿足,則該函數跳出迴圈,因為獲得的空閑塊不能再和其他空閑塊合并。函數將它插入適當的鏈表並以塊大小的order 更新第一個頁框的private 欄位。
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++;

 

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/yunsongice/archive/2010/01/22/5225155.aspx

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.