Linux內核內存管理的一項重要工做就是如何在頻繁申請釋放內存的狀況下,避免碎片的產生。Linux採用夥伴系統解決外部碎片的問題,採用slab解決內部碎片的問題,在這裏咱們先討論外部碎片問題。避免外部碎片的方法有兩種:一種是以前介紹過的利用非連續內存的分配;另一種則是用一種有效的方法來監視內存,保證在內核只要申請一小塊內存的狀況下,不會從大塊的連續空閒內存中截取一段過來,從而保證了大塊內存的連續性和完整性。顯然,前者不能成爲解決問題的廣泛方法,一來用來映射非連續內存線性地址空間有限,二來每次映射都要改寫內核的頁表,進而就要刷新TLB,這使得分配的速度大打折扣,這對於要頻繁申請內存的內核顯然是沒法忍受的。所以Linux採用後者來解決外部碎片的問題,也就是著名的夥伴系統。數組
夥伴系統的宗旨就是用最小的內存塊來知足內核的對於內存的請求。在最初,只有一個塊,也就是整個內存,假如爲1M大小,而容許的最小塊爲64K,那麼當咱們申請一塊200K大小的內存時,就要先將1M的塊分裂成兩等分,各爲512K,這兩分之間的關係就稱爲夥伴,而後再將第一個512K的內存塊分裂成兩等分,各位256K,將第一個256K的內存塊分配給內存,這樣就是一個分配的過程。下面咱們結合示意圖來了解夥伴系統分配和回收內存塊的過程。緩存
1 初始化時,系統擁有1M的連續內存,容許的最小的內存塊爲64K,圖中白色的部分爲空閒的內存塊,着色的表明分配出去了得內存塊。數據結構
2 程序A申請一塊大小爲34K的內存,對應的order爲0,即2^0=1個最小內存塊code
2.1 系統中不存在order 0(64K)的內存塊,所以order 4(1M)的內存塊分裂成兩個order 3的內存塊(512K)blog
2.2 仍然沒有order 0的內存塊,所以order 3的內存塊分裂成兩個order 2的內存塊(256K)內存
2.3 仍然沒有order 0的內存塊,所以order 2的內存塊分裂成兩個order 1的內存塊(128K)內存管理
2.4 仍然沒有order 0的內存塊,所以order 1的內存塊分裂成兩個order 0的內存塊(64K)sed
2.5 找到了order 0的內存塊,將其中的一個分配給程序A,如今夥伴系統的內存爲一個order 0的內存塊,一個order
1的內存塊,一個order 2的內存塊以及一個order 3的內存塊分頁
3 程序B申請一塊大小爲66K的內存,對應的order爲1,即2^1=2個最小內存塊,因爲系統中正好存在一個order 1的內存塊,因此直接用來分配請求
4 程序C申請一塊大小爲35K的內存,對應的order爲0,一樣因爲系統中正好存在一個order 0的內存塊,直接用來分配
5 程序D申請一塊大小爲67K的內存,對應的order爲1
5.1 系統中不存在order 1的內存塊,因而將order 2的內存塊分裂成兩塊order 1的內存塊
5.2 找到order 1的內存塊,進行分配
6 程序B釋放了它申請的內存,即一個order 1的內存塊
7 程序D釋放了它申請的內存
7.1 一個order 1的內存塊回收到內存當中
7.2因爲該內存塊的夥伴也是空閒的,所以兩個order 1的內存塊合併成一個order 2的內存塊
8 程序A釋放了它申請的內存,即一個order 0的內存塊
9 程序C釋放了它申請的內存
9.1 一個order 0的內存塊被釋放
9.2 兩個order 0夥伴塊都是空閒的,進行合併,生成一個order 1的內存塊m
9.3 兩個order 1夥伴塊都是空閒的,進行合併,生成一個order 2的內存塊
9.4 兩個order 2夥伴塊都是空閒的,進行合併,生成一個order 3的內存塊
9.5 兩個order 3夥伴塊都是空閒的,進行合併,生成一個order 4的內存塊
在前面的文章中已經簡單的介紹過struct zone這個結構,對於每一個管理區都有本身的struct zone,而struct zone中的struct free_area則是用來描述該管理區夥伴系統的空閒內存塊的
<mmzone.h>
struct zone { ... ... struct free_area free_area[MAX_ORDER]; ... ... }
<mmzone.h>
struct free_area { struct list_head free_list[MIGRATE_TYPES]; unsigned long nr_free; };
free_area共有MAX_ORDER個元素,其中第order個元素記錄了2^order的空閒塊,這些空閒塊在free_list中以雙向鏈表的形式組織起來,對於同等大小的空閒塊,其類型不一樣,將組織在不一樣的free_list中,nr_free記錄了該free_area中總共的空閒內存塊的數量。MAX_ORDER的默認值爲11,這意味着最大內存塊的大小爲2^10=1024個頁框。對於同等大小的內存塊,每一個內存塊的起始頁框用於鏈表的節點進行相連,這些節點對應的着struct page中的lru域
struct page { ... ... struct list_head lru; /* Pageout list, eg. active_list * protected by zone->lru_lock ! */ ... }
鏈接示意圖以下:
在2.6.24以前的內核版本中,free_area結構中只有一個free_list數組,而從2.6.24開始,free_area結構中存有MIGRATE_TYPES個free_list,這些數組是根據頁框的移動性來劃分的,爲何要進行這樣的劃分呢?實際上也是爲了減小碎片而提出的,咱們考慮下面的狀況:
圖中一共有32個頁,只分配出了4個頁框,可是可以分配的最大連續內存也只有8個頁框(由於夥伴系統分配出去的內存必須是2的整數次冪個頁框),內核解決這種問題的辦法就是將不一樣類型的頁進行分組。分配出去的頁面可分爲三種類型:
假如上圖中大部分頁都是可移動頁,而分配出去的四個頁都是不可移動頁,因爲不可移動頁插在了其餘類型頁的中間,就致使了沒法從本來空閒的連續內存區中分配較大的內存塊。考慮下圖的狀況:
將可回收頁和不可移動頁分開,這樣雖然在不可移動頁的區域當中沒法分配大塊的連續內存,可是可回收頁的區域卻沒有受其影響,能夠分配大塊的連續內存。
內核對於遷移類型的定義以下:
<mmzone.h>
#define MIGRATE_UNMOVABLE 0 #define MIGRATE_RECLAIMABLE 1 #define MIGRATE_MOVABLE 2 #define MIGRATE_PCPTYPES 3 /* the number of types on the pcp lists */ #define MIGRATE_RESERVE 3 #define MIGRATE_ISOLATE 4 /* can't allocate from here */ #define MIGRATE_TYPES 5
前三種類型已經介紹過
MIGRATE_PCPTYPES是per_cpu_pageset,即用來表示每CPU頁框高速緩存的數據結構中的鏈表的遷移類型數目
MIGRATE_RESERVE是在前三種的列表中都沒用可知足分配的內存塊時,就能夠從MIGRATE_RESERVE分配
MIGRATE_ISOLATE用於跨越NUMA節點移動物理內存頁,在大型系統上,它有益於將物理內存頁移動到接近因而用該頁最頻繁地CPU
MIGRATE_TYPES表示遷移類型的數目
當一個指定的遷移類型所對應的鏈表中沒有空閒塊時,將會按如下定義的順序到其餘遷移類型的鏈表中尋找
static int fallbacks[MIGRATE_TYPES][MIGRATE_TYPES-1] = { [MIGRATE_UNMOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE, MIGRATE_RESERVE }, [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE, MIGRATE_MOVABLE, MIGRATE_RESERVE }, [MIGRATE_MOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE }, [MIGRATE_RESERVE] = { MIGRATE_RESERVE, MIGRATE_RESERVE, MIGRATE_RESERVE }, /* Never used */ };