Linux內存管理之伙伴系統算法


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++;

         

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM