1. 程式人生 > >深入淺出Linux核心記憶體管理基礎

深入淺出Linux核心記憶體管理基礎

1 背景

記憶體管理是Linux核心通過軟硬體協作來管理記憶體的分配及回收的一種方法。在Linux系統上電自檢(POST,Power-On-Self-Test)階段使用臨時記憶體,而系統啟動後正常執行階段的記憶體使用又有兩大類:(1)固定的記憶體分配,是永久的不變的,主要給核心程式碼及資料使用;(2)動態記憶體分配,主要源於程序的建立和空間的擴張。本文主要針對作業系統正常執行時對動態記憶體的管理。

我們知道,Linux記憶體可以劃分為兩大類:實體記憶體和虛擬記憶體。實體記憶體是系統中由RAM晶片控制得可用記憶體。虛擬記憶體的產生主要來自於作業系統多程序應用程式透明訪問記憶體的需求。虛擬記憶體依靠透明地使用磁碟空間,支援程序訪問比系統實體記憶體大得多的記憶體空間,因此容量大且價格低廉的磁碟空間(磁碟本身不是記憶體,這也是“虛擬”概念的來源之一)可作為實體記憶體的擴充。

Linux核心的記憶體管理通過分頁機制維護虛擬地址和實體地址的對映關係。

注意,為防止概念混淆,在闡述虛擬地址和實體地址的轉換關係時,本文使用page(頁)專指虛擬記憶體管理單元virtual page,而page Frame(頁框與頁幀是同一個概念)專指實體地址記憶體單元physical page。詳見 http://blog.csdn.net/acs713/article/details/70036359(理解pages和page Frames的區別)

另請注意: 頁框的狀態資訊儲存在一個structpage的頁描述符中.如實在還有不解,請參閱程式碼。歡迎指正。

1.1 虛擬記憶體的劃分:使用者空間與核心空間

       從Linux作業系統層次上,可將Linux虛擬記憶體劃分為使用者空間記憶體和核心空間記憶體。

       虛擬地址空間(使用者空間+核心空間)的大小取決於處理器體系結構的字長。32位的CPU,最大定址範圍為2^32 - 1也就是4G的線性地址空間。Linux簡化了分段機制,使得虛擬地址與線性地址總是一致的。Linux一般把這個4G的地址空間劃分為兩個部分:其中 0~3G為使用者程式地址空間,虛地址0x00000000到0xBFFFFFFF,供各個程序使用;3G~4G為核心的地址空間,虛擬地址 0xC0000000到0xFFFFFFFF, 供核心使用。(注意,ARM架構不是3G/1G劃分的,而是2G/2G劃分。這裡以3G/1G劃分作講解)。使用者程序通常情況下只能訪問使用者空間(

0~3G)的虛擬地址,不能訪問核心空間的虛擬地址。例外情況只有使用者程序進行系統呼叫(代表使用者程序在核心態執行)等時刻可以訪問到核心空間。如下圖所示:

     

       可以看出,每個程序(1,2,3......,n)都有自己的私有使用者空間(0-3GB),這個空間對系統中的其他程序是不可見的。最高的1GB核心空間則由則由所有程序以及核心共享。可見,核心最多定址1G的虛擬地址空間。使用者空間對應程序,所以每當程序切換,使用者空間就會跟著變化;而核心空間是由核心負責對映,它並不會跟著程序變化,是固定的。核心空間地址有自己對應的頁表,使用者程序各自有不同的頁表。每個程序的使用者空間都是完全獨立、互不相干的。

      Linux 核心採用了最簡單的對映方式來對映實體記憶體,即把實體地址+PAGE_OFFSET按照線性關係直接對映到核心空間。PAGE_OFFSET大小為 0xC0000000 (=3G).

      但是Linux核心並沒有把整個1G空間用於線性對映,而只映射了最多896M實體記憶體,預留了最高階的128M虛擬地址空間給IO 裝置和其他用途。有關此知識點的深入理解,讀者可隨後參考章節1.2.2記憶體區域(ZONE)的概念。

      Linux核心使用頁表(page tables)來建立使用者程序的虛擬記憶體與系統實體記憶體RAM ( physial page frames)的聯絡.頁表使得各個使用者程序可以使用統一的虛擬地址空間,即一塊連續的4G記憶體區域。頁表還可以將多個虛擬頁面(virtual pages)對映到物理RAM physical page frames,進而支援多個程序共享記憶體。 頁表也支援不增加RAM,直接使用硬碟交換空間來提高可用記憶體。

      細心的讀者可能會問,虛擬地址是通過MMU來對外產生實體地址呀,那頁表作為一個軟體實體,到底是起到什麼作用呢?簡單理解:整個查表過程硬體完成,但是頁表由作業系統維護。也就是說,頁表可以認為是對MMU的一個軟體配置,即它描述MMU的對映規則,即虛擬記憶體哪(幾)個頁對映到實體記憶體哪(幾)個頁幀。
頁表由一條條代表對映規則的記錄組成,每一條稱為一個頁表條目(Page Table Entry,即PTE),整個頁表儲存在片外記憶體,MMU通過查詢頁表確定一個虛擬地址應該對映到什麼實體地址,以及是否有許可權對映。(這裡參考http://blog.csdn.net/ipmux/article/details/19167605)。下面是直接引用,以幫助有興趣的朋友瞭解其詳:

如果MMU存在且啟用,CPU執行單元產生的地址訊號在傳送到記憶體晶片之前將被MMU截獲,這個地址訊號稱為虛擬地址(virtual address),簡稱VAMMU會負責把VA翻譯成另一個地址,然後發到記憶體晶片地址引腳上,即VA對映成PA,如下圖:

.

    所以實體地址①是通過CPU對外地址匯流排②傳給Memory Chip③使用的地址;而虛擬地址④是CPU內部執行單元⑤產生的,傳送給MMU⑥的地址。硬體上MMU⑥一般封裝於CPU晶片⑦內部,所以虛擬地址④一般只存在於CPU⑦內部,到了CPU外部地址匯流排引腳上②的訊號就是MMU轉換過的實體地址①。

    軟體上MMU對使用者程式不可見,在啟用MMU的平臺上(沒有MMU不必說,只有實體地址,不存在虛擬地址),使用者C程式中變數和函式背後的資料/指令地址等都是虛擬地址,這些虛擬記憶體地址從CPU執行單元⑤發出後,都會首先被MMU攔截並轉換成實體地址,然後再發送給記憶體。也就是說使用者程式執行*pA =100;"這條賦值語句時,假設debugger顯示指標pA的值為0x30004000(虛擬地址),但此時通過硬體工具(如邏輯分析儀)偵測到的CPU與外存晶片間匯流排訊號很可能是另外一個值,如0x8000(實體地址)。當然對一般程式設計師來說,只要上述語句執行後debugger顯示0x30004000位置處的記憶體值為100就行了,根本無需關心pA的實體地址是多少。但進行OS移植或驅動開發的系統程式設計師不同,他們必須清楚軟體如何在幕後輔助硬體MMU完成地址轉換。

作業系統和MMU

    實際上MMU是為滿足作業系統越來越複雜的記憶體管理而產生的。OSMMU的關係簡單說:

    a.系統初始化程式碼會在記憶體中生成頁表,然後把頁表地址設定給MMU對應暫存器,使MMU知道頁表在實體記憶體中的什麼位置,以便在需要時進行查詢。之後通過專用指令啟動MMU,以此為分界,之後程式中所有記憶體地址都變成虛地址,MMU硬體開始自動完成查表和虛實地址轉換。

    b.OS初始化後期,建立第一個使用者程序,這個過程中也需要建立頁表,把其地址賦給程序結構體中某指標成員變數。即每個程序都要有獨立的頁表。

    c.使用者建立新程序時,子程序拷貝一份父程序的頁表,之後隨著程式執行,頁表內容逐漸更新變化。

虛擬地址到實體地址的對映流程:

VAPA的對映過程就一目瞭然:MMU得到VA後先在TLB內查詢,若沒找到匹配的PTE條目就到外部頁表查詢,並置換進TLB;根據PTE條目中對訪問許可權的限定檢查該條VA指令是否符合,若不符合則不繼續,並丟擲exception異常;符合後根據VA的地址分段查詢頁表,保持offset(廣義)不變,組合出實體地址,傳送出去。

   在這個過程中,軟體的工作核心就是生成和配置頁表。

1.2 實體記憶體管理基礎概念

         Linux為了用統一的程式碼獲得最大的相容性,引入了以下實體記憶體管理概念:(1)實體記憶體結點(node);(2)實體記憶體區域(Zone);(3)實體記憶體頁框(page Frame)

1.2.1 實體記憶體節點Node

         一般來說,存在兩種實體記憶體管理模型:(1)UMA ,一致記憶體訪問模型;(2)NUMA,非一直記憶體訪問模型。

與UMA不同,NUMA模型下處理器訪問本地記憶體的速度要快於其他記憶體訪問本地記憶體的速度。

        Linux原始碼中,以struct pglist_data資料結構表示單個記憶體節點。對於NUMA模型,多個記憶體節點通過連結串列串接起來;而UMA模型只有一個記憶體節點。


1.2.2 實體記憶體區域Zone

         從理論上來講,一個頁框就是一個實體記憶體儲存單元,可用於做任何事情,如儲存核心資料或使用者資料,快取磁碟資料,等等。然而,實際中,由於不同計算機型別的硬體架構不同,頁幀的用途可能會有所限制。例如,在80X86架構下,Linux核心必須正確處理以下兩個硬體限制:

     (1)舊的ISA匯流排的DMA處理器只能訪問RAM的前16MB;

      (2)現代的32位計算機ram記憶體越來越大,因為線性地址空間過小,CPU沒辦法訪問所有記憶體。

      為了解決以上兩個限制,進而對物理頁面進行有效的管理,對於每個記憶體節點Node, Linux又把物理頁面劃分為三個區:

·專供DMA使用的ZONE_DMA區(小於16MB,其頁幀可以被舊的ISA匯流排訪問)

·常規的ZONE_NORMAL區(大於16MB小於896MB

·核心不能直接對映的區ZONE_HIGME區(大於896MB)。

     也就是說,Linux系統的實體記憶體被分配到幾個記憶體節點Node, 而每個節點又劃分為幾個記憶體區域Zone.

      high memory也是被核心管理的(有對應的page Frame結構),只是沒有對映到核心虛擬地址空間。當核心需要分配high memory時,通過kmap等從預留的地址空間中動態分配一個地址,然後對映到high memory,從而訪問這個物理頁。high memory對映到核心地址空間一般是暫時性的對映,不是永久對映。

1.2.3 實體記憶體頁框page Frames

       記憶體頁框(page frame) 是物理管理記憶體管理中的最小單位。Linux系統為實體記憶體的每個頁框建立一個struct page物件,並用全域性物件struct page *mem_map (陣列)來存放所有物理頁框物件的指標。

         對於一個應用程式(程序)來說,它看到的是虛擬記憶體,這根本看不到實體記憶體。對於同一程式執行起來的兩個程序,它們的虛擬空間佈局可能完全一樣,但他們真實使用的實體記憶體空間則不相同。就像廣州人民和上海人民都說中山路一樣,說法一樣,但所指的地址是不一樣的(地點的經緯度不一樣)。

       Linux核心記憶體管理模組的頁表page tables,可以實現虛擬地址到實體地址的轉換。

      在前面1.1節我已指出,在程序的核心空間,以ZONE_NORMAL為例,虛擬地址到實體地址的轉換是相當直接的。即把實體地址+PAGE_OFFSET按照線性關係直接對映到核心虛擬地址空間。PAGE_OFFSET大小為 0xC0000000 (=3G).(也就是在ZONE_NORMAL, 實體地址=核心虛擬地址-PAGE_OFFSET)。

       在程序的使用者空間,各個程序都實現了自己的頁表(page table),通過頁表對映來訪問實際的實體地址。

      早期, 對於32位的計算機而言,頁式管理是這麼進行的,邏輯地址格式如下:

    0 11位:頁內偏移OFFSET

    1221位:頁面表偏移PT

    2231位:頁面目錄偏移PGD

   定址過程如下:

    1)作業系統從暫存器CR3獲得當前page目錄指標(基地址);

2)基地址+page目錄偏移->page Table指標(基地址);

    3)Page Table指標+page table偏移->實體記憶體頁框(page Frame)基址;

    4)Page Frame基址+頁內偏移->具體實體記憶體單元 (specific Page Frame)。

   顯然,12位的頁內偏移可以定址4K,所以一張記憶體頁為4K;而總共可尋記憶體為4G2^10 * 2^10 * 2^12;因此在32位機器上記憶體上限一般為4G

      從核心2.6.11版本開始,Linux定義了四種類型的頁表(page tables):

(1)總目錄PGD(Page Global Directory),包含PUD的地址。

(2)上級目錄PUD(Page Upper Derectory),包含PMD的地址

(3)中間目錄PMD(Page Middle Derectory),包含PT的地址

(4)頁表PT(Page Table),Page Table Entry指向頁框

    其頁面定址模型可用下圖表示:

   

   上述模型沒有對線性地址按不同頁表型別分配不同的位數,原因在於不同的架構表現不同。那麼CR3是什麼?請參考如下暫存器描述:

      標誌暫存器 :EFLAGS

      指令指標:EIP

      機器狀態字:CR0

      CR1是未定義的控制暫存器,供將來的處理器使用。

      CR2是頁故障線性地址暫存器,儲存最後一次出現頁故障的全32位線性地址。

      CR3是頁目錄基址暫存器,儲存頁目錄表的實體地址,頁目錄表總是放在以4K位元組為單位的儲存器邊界上,因此,它的地址的低12位總為0,不起作用,即使寫上內容,也不會被理會。

2 記憶體管理模型深入

2.1 記憶體模型總覽

以NUMA模型為例,其記憶體管理組織架構大體如下圖所示:

對於每個記憶體區Zone,它採用夥伴系統演算法管理記憶體,其大致結構如下:

     什麼是Per-CPU page frame cache呢?

     由於核心經常性的會請求一個頁幀然後又釋放它,這會帶來系統性能問題。為了提高效能,每個記憶體區域zone定義了一個Per-CPU page frame cache。每個Per-CPU page frame cache包含了一些預分配的頁幀。

     在前面的總覽圖中可以看到,核心劃分為節點Node。每個節點關聯到系統中的一個處理器,在核心中表示為pg_data_t。示意圖如下:


      每個記憶體zone結構體都有一個成員struct free_area    free_area[MAX_ORDER], 它用於實現夥伴系統,每個陣列元素都表示某種固定長度的一些連續記憶體區,對於包含在每個區域中的空閒記憶體頁的管理,free_area是一個起點。

      每個記憶體zone結構體都有一個成員struct page *zone_mem_map,它指向記憶體區域zone的第一個頁幀。

       總之,memory node對應於結構pg_data_t,memory zone對應於結構zone,page frame對應於結構page,由zone_mem_map指向。

      從資料結構的角度,可以繪製出如下關係圖:

      也有網友按照架構分層的概念,提出瞭如下記憶體管理架構模型:

     詳見:http://blog.csdn.net/S_E_A_N/article/details/5829978

     此架構模型是我較為推崇的,下面按照其思想從下往上分析。

2.2 記憶體節點Node資料結構分析

     NUMA(Non-Uniform Memory Access)即非一致記憶體訪問。在多CPU系統中有可能出現給定CPU對不同記憶體單元訪問的時間不同。為了讓指定CPU總能最先使用訪問時間最短的記憶體,Linux把實體記憶體分成幾塊並以節點(node)標識。這樣一來,每個CPU都有最快訪問記憶體的節點,但並不等於只能訪問這個節點。下表中node_zonelists將其他節點的各管理區也鏈了進來,但均排在本節點管理區之後,以示其它節點優先順序低於本節點。

核心中用struct pglist_data結構體來存放節點資訊,各成員解釋如下:

型別

名字

說明

struct zone []

node_zones

節點中管理區描述符的陣列,其子元素為結點中各記憶體域(zone)的資料結構

struct zonelist []

node_zonelists

zone表,頁分配器獲取記憶體的入口

int

nr_zones

節點中管理區的個數

struct page *

node_mem_map

節點中頁描述符陣列。它是一個指向page例項陣列的指標,用於描述結點的所有實體記憶體頁,可把它看做一個數組,包含了結點中所有記憶體域的頁。

struct bootmem_data *

bdata

核心初始化階段記憶體管理入口

unsigned long

node_start_pfn

節點中第一個葉框下標或邏輯編號,系統中所有的頁幀是依次編號的,每個頁幀的號碼都是全域性唯一的(不只是結點內唯一)。

unsigned long

node_present_pages

屬於本節點的記憶體的頁框數(不包括洞)

unsigned long

node_spanned_pages

屬於本節點的記憶體的頁框數(包括洞)

int

node_id

節點識別符號,全域性結點ID,系統中的NUMA結點都從0開始編號

wait_queue_head_t

kswapd_wait

kswapd頁換出守護程序使用的等待佇列

struct task_struct *

kswapd

指標指向kswapd核心執行緒的程序描述符

int

kswapd_max_order

kswapd將要建立空閒塊大小取對數的值

2.3 記憶體域Zone資料結構分析

核心用struct zone來管理記憶體區,各成員解釋如下:

型別

名字

說明

unsigned long

pages_min

管理區中保留頁的數目

unsigned long

pages_low

回收頁框時使用的下界,同時也被管理區分配器作為閾值使用

unsigned long

pages_high

回收頁框時使用的上界,同時也被管理區分配器作為閾值使用

unsigned long []

lowmem_reserve

指明在處理記憶體不足的臨界情況下管理區必須保留的頁框數

struct per_cpu_pageset []

pageset

用於實現單一頁框的特殊快取記憶體

spinlock_t

lock

保護該描述符的自旋鎖

struct free_area []

free_area

標識出管理區中的空閒頁框

spinlock_t

lru_lock

活動以及非活動連結串列使用的自旋鎖

struct lru []

lru

活動以及非活動連結串列

unsigned long

flags

管理區標誌

atomic_long_t []

vm_stat

管理區內各型別記憶體統計

int

prev_priority

管理區優先順序,由回收頁框演算法使用

wait_queue_head_t *

wait_table

程序等待佇列的散列表,這些程序正在等待管理區中的某項。

unsigned long

wait_table_bits

等待佇列散列表陣列大小,置位2的order冪

struct pglist_data *

zone_pgdat

包含該管理區的節點指標

unsigned long

zone_start_pfn

管理區的起始頁框號

unsigned long

spanned_pages

管理區的頁框數,包括洞

unsigned long

present_pages

管理區的頁框數,不包括洞

const char *

name

指向管理區名字,一般為"DMA" "Normal" "HighMem"

2.4 記憶體頁資料結構分析

核心必須記錄每個頁框當前的狀態。例如,核心必須能區分哪些頁框包含的是屬於程序的頁,而哪些頁框包含的是核心程式碼或核心資料。類似地,核心還必須能夠確定動態記憶體中的頁框是否空閒。如果動態記憶體中的頁框不包含有用的資料,那麼這個頁框就是空閒的。在以下情況下頁框是不空閒的:包含使用者態程序的資料、某個軟體快取記憶體的資料、動態分配的核心資料結構、裝置驅動程式緩衝的資料、核心模組的程式碼等等。

頁框的狀態資訊儲存在一個型別為page的頁描述符中,其中的欄位如下表所示。所有的頁描述符存放在mem_map陣列中。因為每個描述符長度為32位元組,所以mem_map所需要的空間略小於整個記憶體的1%。virt_to_page(addr)巨集產生線性地址addr對應的頁描述符地址。pfn_to_page(pfn)巨集產生與頁框號pfn對應的頁描述符地址。

頁描述符,在核心中表示為struct page:

型別

名字

說明

unsigned long

flags

頁標誌,包括所在的節點及管理區編號(高位上)

atomic_t

_count

頁框的引用計數器

atomic_t

_mapcount

頁框中的頁表項數目,如果沒有則為-1

unsigned long

private

可用於正在使用頁的核心成分(例如,在緩衝頁的情況下它是一個緩衝器頭指標)。如果頁是空閒的,則該欄位由夥伴系統使用(存放order值)

struct address_space *

mapping

當頁被插入頁快取記憶體中時使用,或者當頁屬於匿名區時使用

pgoff_t

index

作為不同的含義被幾種核心成分使用。例如,它在頁磁碟映像或匿名區中標識存放在頁框中的資料的位置,或者它存放一個換出頁識別符號

struct list_head

lru

包含頁的最近最少使用(LRU)雙向連結串列的指標

下面這兩個欄位非常重要,需作重點描述:

_count

頁的引用計數器。如果該欄位為-1,則相應頁框空閒,並可被分配給任一程序或核心本身;如果該欄位的值大於或等於0,則說明頁框被分配給了一個或多個程序,或用於存放一些核心資料結構。page_count()函式返回_count加1後的值,也就是該頁的使用者的數目。

flags

包含多達32個用來描述頁框狀態的標誌,如下表。對於每個PG_xyz標誌,核心都定義了操作其值的一些巨集。通常,PageXyz巨集返回標誌的值,而SetPageXyz和ClearPageXyz巨集分別設定和清除相應的位。頁標誌32位分佈如下:

Page flags: | [SECTION] | [NODE] | ZONE | ... | FLAGS |

標誌名

含義

PG_locked

頁被鎖定。例如,在磁碟I/O操作中涉及的頁

PG_error

在傳輸頁時發生I/O錯誤

PG_referenced

剛剛訪問過的頁

PG_uptodate

在完成讀操作後置位,除非發生磁碟I/O錯誤

PG_dirty

頁已經被修改

PG_lru

頁在活動或非活動頁連結串列中

PG_active

頁在活動頁連結串列中

PG_slab

包含在slab中的頁框

PG_highmem

頁框屬於ZONE_HIGHMEM管理區

PG_checked

由一些檔案系統(如Ext3)使用的標誌

PG_reserved

頁框留給核心程式碼或沒有使用

PG_private

頁描述符的private欄位存放了有意義的資料

PG_writeback

正在使用writepage方法將頁寫到磁碟上

PG_nosave

系統掛起/喚醒時使用

PG_compound

通過擴充套件分頁機制處理頁框

PG_swapcache

頁屬於對換快取記憶體

PG_mappedtodisk

頁框中的所有資料對應於磁碟上分配的塊

PG_reclaim

為回收記憶體對頁已經做了寫入磁碟的標記

PG_nosave_free

系統掛起/恢復時使用

2.5 夥伴系統演算法減少外部碎片

   讀者可參考如上文章建立記憶體碎片的概念。這裡為了方便,複習如下:

   外部碎片是出於任何已分配區域或頁面外部的空閒儲存塊。這些儲存塊的總和可以滿足當前申請的長度要求,但是由於它們的地址不連續或其他原因,使得系統無法滿足當前申請。 造成問題:系統雖有足夠的記憶體,但卻都是分散的碎片,無法滿足對大塊“連續記憶體”的需求。為了避免“外部碎片”這種問題的發生,有兩種辦法:

(1)通過使用頁轉換電路,把那些不連續的未分配的頁幀轉換為線性地址;

(2)開發出一種演算法,追蹤現存的連續的未分配的頁幀,儘可能避免拆分一個大得多的記憶體塊去滿足較小的記憶體開闢請求。 即將大記憶體分成各種固定大小的塊,每次分配時,儘量使用最接近申請大小的那一塊,儘量避免為滿足小塊記憶體的申請而分拆大的塊。

    上述辦法(1)即是vmalloc的思想,但有時後要求申請到記憶體必須是連續,這就需要方法II。其實方法(1)還有一個缺點:它需要修改頁表,需要硬體的頻繁動作,這增加了記憶體申請和使用的時間。因此多數情況都採用方法(2)來申請記憶體。夥伴系統演算法即是方法(2)的一個使用演算法,使用它可減少外部碎片。

     夥伴系統特徵如下:

I     把所有的空閒頁框分組為MAX_ORDER(ICE為11)個塊連結串列,每個塊連結串列分別包含大小為2n(n為0~10的整數)個連續的頁框,即1、2、4、6、8、16、32、64、128、256、512和1024個連續的頁框。

II     每個塊的第一個頁框的實體地址是該塊大小的整數倍。

     前面提到了,記憶體區域zone資料結構,但是在zone中主要關心的是下面的這個結構體陣列:
struct zone
{
   ..........

   struct free_area free_area[MAX_ORDER];

   ..........

}
struct free_area定義如下:

struct free_area
{
  struct list_head free_list[MIGRATE_TYPES];
  unsigned long nr_free;  
};
     其中free_list用來連線不用的頁描述符,nr_free指定了當前區中空閒頁的數目,free_list用於連線空閒頁的連結串列。而在free_area陣列中MAX_ORDER定義為11,其陣列下標對應為該記憶體塊的階。也就是說,夥伴演算法把記憶體塊按大小分組管理,即將所有空閒頁幀分組為11個塊連結串列(block List),每個塊連結串列分別包含1,2,4,8,16,32,64,128,256,512,1024個連續的頁幀(也就是2的0次方,到2的10次方)。


一個簡單的圖示:

更詳細點的圖示:

下面詳細敘述夥伴系統的工作原理:     比如說我們要申請一個b階大小的頁塊,那麼系統會直接在b階塊中查詢看這個連結串列是否為空,如果不為空則說明恰好有這麼大的頁可以用於分配。如果該連結串列為空,則會在b+1階中尋找,如過b+1階連結串列不為空,則將b+1中頁塊一分為二,一半用於分配,另一半加入b階連結串列中。如果b+1階連結串列也為空那麼就繼續向上尋找,如果都沒找到空閒地址,就只能返回NULL。       上面的過程是分配空間的過程,在釋放頁的時候正好是分配的一個逆過程,核心會試圖將兩個b階的頁塊合併程一個2b階的大頁塊,如果可以合併就將這兩個頁塊稱為夥伴,滿足夥伴的的要求如下: 1)兩個塊具有相同的大小,記作b。 2)它們的實體地址是連續的。 3)第一塊的第一個頁的實體地址是2*b*PAGE_SIZE的倍數即第0塊和第1塊是夥伴,第2塊和第3塊是夥伴,但是第1塊和第2塊不是夥伴。這樣規定的目的是確保一對夥伴中的兩個塊可以合併成更高階的大塊。

2.6 頁框分配器

頁框分配器核心函式是__alloc_pages_internal()它主要呼叫的函式是__rmqueue()。簡單的說頁框分配器是對夥伴系統的一種封裝。我們常用的頁框分配函式又是對__alloc_pages_internal()的封裝,如:alloc_pages_node()。alloc_pages_node()返回給呼叫者的是頁框描述符。下面slab分配器使用的kmem_getpages()僅是將alloc_pages_node()返回的頁框描述符轉換成了線性地址。

2.7 SLAB機制管理記憶體,減少內部碎片

     內部碎片是處於區域內部或頁面內部的儲存塊。佔有這些區域或頁面的程序並不使用這個塊。而在程序佔有這塊儲存儲存塊時,系統無法利用它。直到程序釋放它,或程序結束時,系統才有可能利用這個儲存塊。造成原因一般為:系統為了一小段連續記憶體區的需要,不得已給它分配了一大塊連續記憶體,從而造成了記憶體浪費。

     為了滿足核心對這種小記憶體塊的需要,Linux系統採用了一種被稱為slab分配器的技術。Slab分配器的實現相當複雜,但原理不難,其核心思想就是“儲存池”的運用。記憶體片段(小塊記憶體)被看作物件,當被使用完後,並不直接釋放而是被快取到“儲存池”裡,留做下次使用,這無疑避免了頻繁建立與銷燬物件所帶來的額外負載。

slab分配器有三個目標:

I      減少系統分配小塊記憶體時產生的碎片;

II     把經常使用的物件快取起來,減少分配、初始化及釋放物件的時間開銷;

III   調整物件以更好的使用硬體快取記憶體。
    Slab技術不但避免了記憶體內部分片(下文將解釋)帶來的不便(引入Slab分配器的主要目的是為了減少對夥伴系統分配演算法的呼叫次數——頻繁分配和回收必然會導致記憶體碎片——難以找到大塊連續的可用記憶體),而且可以很好利用硬體快取提高訪問速度。

    Slab並非是脫離夥伴關係而獨立存在的一種記憶體分配方式,slab仍然是建立在頁面基礎之上。SLAB分配器的基本思想是先利用頁面分配器分配出單個或連續的物理頁面,然後在此基礎上將整塊頁面分割成多個相等的小記憶體單元,以滿足小記憶體空間分配的需要。換句話說,Slab將頁面(來自於夥伴關係管理的空閒頁框鏈)撕碎成眾多小記憶體塊以供分配,slab中的物件分配和銷燬使用kmem_cache_alloc()與kmem_cache_free()。
    常用的kmalloc建立在slab分配器的基礎之上。

SLAB機制的工作層次示意如下:


注:本文參考大量網友著作,並經過了本人的系統研究、總結或組合。

部分參考列表:

(1)http://blog.csdn.net/S_E_A_N/article/details/5829978

     

相關推薦

深入淺出Linux核心記憶體管理基礎

1 背景 記憶體管理是Linux核心通過軟硬體協作來管理記憶體的分配及回收的一種方法。在Linux系統上電自檢(POST,Power-On-Self-Test)階段使用臨時記憶體,而系統啟動後正常執行階段的記憶體使用又有兩大類:(1)固定的記憶體分配,是永久的不變的,主要

深入理解 Linux 核心---記憶體管理

核心中的函式以比較直接了當的方式獲得動態記憶體: __get_free_pages() 或 alloc_pages() 從分割槽頁框分配器獲得頁框。 kmem_cache_alloc() 或 kmalloc() 使用 slab 分配器為專用或通用物件分配塊。 vm

LINUX核心記憶體管理kmalloc,vmalloc

1 #include <linux/init.h> 2 #include <linux/thread_info.h> 3 #include <linux/module.h> 4 #include <linux/sched.h> 5

linux核心記憶體管理學習之二(實體記憶體管理--夥伴系統)

linux使用夥伴系統來管理實體記憶體頁。 一、夥伴系統原理 1. 夥伴關係 定義:由一個母實體分成的兩個各方面屬性一致的兩個子實體,這兩個子實體就處於夥伴關係。在作業系統分配記憶體的過程中,一個記憶體塊常常被分成兩個大小相等的記憶體塊,這兩個大小相等的記憶體塊就處於夥伴關

[linux]linux核心時間管理基礎

一,linux時間管理基礎http://blog.csdn.net/droidphone/article/details/7975694http://blog.csdn.net/smilingjames/article/details/6205540 linux所有時間基礎

Linux核心記憶體管理架構

記憶體管理子系統可能是linux核心中最為複雜的一個子系統,其支援的功能需求眾多,如頁面對映、頁面分配、頁面回收、頁面交換、冷熱頁面、緊急頁面、頁面碎片管理、頁面快取、頁面統計等,而且對效能也有很高的要求。本文從記憶體管理硬體架構、地址空間劃分和記憶體管理軟體架構三個方面入手

linux 核心 記憶體管理 slub演算法 (一) 原理

        核心管理頁面使用了2個演算法:夥伴演算法和slub演算法,夥伴演算法以頁為單位管理記憶體,但在大多數情況下,程式需要的並不是一整頁,而是幾個、幾十個位元組的小記憶體。於是需要另外一套系統來完成對小記憶體的管理,這就是slub系統。slub系統執行在夥伴系統之

linux高階記憶體管理之永久核心對映

與直接對映的實體記憶體末端、高階記憶體的始端所對應的線性地址存放在high_memory變數中,在x86體系結構上,高於896MB的所有實體記憶體的範圍大都是高階記憶體,它並不會永久地或自動地對映到核心地址空間,儘管x86處理器能夠定址物理RAM的範圍達到4GB(啟用PAE可以定址到64GB)。一旦這些頁被

Linux核心學習筆記九——核心記憶體管理方式

一 頁        核心把物理頁作為記憶體管理的基本單位;記憶體管理單元(MMU)把虛擬地址轉換為物理 地址,通常以頁為單位進行處理。MMU以頁大小為單位來管理系統中的也表。        32位系統:頁大小4KB        64位系統:頁大小8KB 核心

Linux 磁盤管理基礎

linux 基礎 運維學習 一、磁盤內部的相關知識1、前言: 一些關於磁盤的英文disk #磁盤 head #磁頭 sector #扇區 track #磁道 cylinder #柱面 units

Linux 磁盤管理基礎<二>

linux 運維基礎學習一、分區加密cryptsetup luksFormat/dev/vdb1 WARNING! ======== This will overwrite data on /dev/vdb1irrevocably. Are you sure? (Type uppercase

Linux 磁盤管理 基礎篇--創建基本文件分區

分區 文件系統 掛載 1 概述本文通過對講解了磁盤的基本概念,分區,創建文件系統,掛載,卸載等步驟進行講解。使得用戶能夠創建基本的磁盤並掛載使用空間。2 磁盤基本概念2.1 磁盤空間每個扇區大小是512byte,這是磁盤的最小單位每一個磁道上有多少個扇區就是扇區數 sectors每一圈叫做磁道(t

QEMU深入淺出: guest實體記憶體管理

原  文:http://blog.vmsplice.net/2016/01/qemu-internals-how-guest-physical-ram.html 作  者:Stefan Hajnoczi 領  域:Open source and virtuali

linux核心記憶體分配

核心中的記憶體分配通常通過kmalloc/kfree來進行,但是也有其它的方式來獲取記憶體,所有這些方式共同提供了核心中分配、釋放記憶體的介面。 一、kmalloc/kfree 類似於標準C中的malloc/free,kmalloc/kfree是核心中的用於常規記憶體分配的介面。 kma

.NET基礎之型別語法基礎記憶體管理基礎

轉自:http://www.cnblogs.com/edisonchou/p/4787775.html   型別語法基礎和記憶體管理基礎  Index : (1)型別語法、記憶體管理和垃圾回收基礎 (2)面向物件的實現和異常的處理 (3)字串

Linux 記憶體管理之vmalloc實現

vmalloc最小分配一個page.並且分配到的頁面不保證是連續的.因為vmalloc內部呼叫alloc_page多次分配單個頁面. vmalloc主要內容: 1. 從VMALLOC_START到VMALLOC_END查詢空閒的虛擬地址空間(hole) 2.根據分配的size,呼叫all

Linux核心電源管理綜述

資料:http://blog.csdn.net/bingqingsuimeng/article/category/1228414http://os.chinaunix.net/a2006/0519/1002/000001002210.shtmlhttp://www.ednchina.com/ART_44010

Linux 核心初級管理

Linux 核心初步管理     單核心體系設計,但充分借鑑了微核心設計體系的優點,微核心引入模組化機制;         核心組成部分:             kernel:核心核心,一般為bzImage,通常在/boot目錄下,名稱為vmlinuz-VERSION-RE

Linux系統記憶體管理之夥伴系統分析

 1.夥伴系統概念   夥伴系統是一種經典的記憶體管理方法。Linux夥伴系統的引入為核心提供了一種用於分配一組連續的頁而建立的一種高效的分配策略,並有效的解決了外碎片問題。  2.夥伴系統的組織結構 Linux中的記憶體管理的“頁”大小為4KB。把所有的空閒頁分組

不要盲目增加ip_conntrack_max-理解Linux核心記憶體

                1.由ip_conntrack引出的Linux記憶體對映有很多文章在討論關於ip_conntrack表爆滿之後丟棄資料包的問題,對此研究深入一些的知道Linux有個核心引數ip_conntrack_max,在擁有較大記憶體的機器中預設65536,於是瘋狂的增加這個引數,比如設定