国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

這篇具有很好參考價值的文章主要介紹了深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

提要:本系列文章主要參考MIT 6.828課程以及兩本書籍《深入理解Linux內(nèi)核》 《深入Linux內(nèi)核架構(gòu)》對Linux內(nèi)核內(nèi)容進行總結(jié)。
內(nèi)存管理的實現(xiàn)覆蓋了多個領(lǐng)域:

  1. 內(nèi)存中的物理內(nèi)存頁的管理
  2. 分配大塊內(nèi)存的伙伴系統(tǒng)
  3. 分配較小內(nèi)存的slab、slub、slob分配器
  4. 分配非連續(xù)內(nèi)存塊的vmalloc分配器
  5. 進程的地址空間

內(nèi)核初始化后,內(nèi)存管理的工作就交由伙伴系統(tǒng)來承擔,作為眾多內(nèi)存分配器的基礎(chǔ),我們必須要對其進行一個詳細的解釋。但是由于伙伴系統(tǒng)的復(fù)雜性,因此,本節(jié)會首先給出一個簡單的例子,然后由淺入深,逐步解析伙伴系統(tǒng)的細節(jié)。

伙伴系統(tǒng)簡介

伙伴系統(tǒng)將所有的空閑頁框分為了11個塊鏈表,每個塊鏈表分別包含大小為1,2,4,\(2^3\),\(2^4\),...,\(2^{10}\)個連續(xù)的頁框(每個頁框大小為4K),\(2^{n}\)中的n被稱為order(分配階),因此在代碼中這11個塊鏈表的表示就是一個長度為11的數(shù)組。考察表示Zone結(jié)構(gòu)的代碼,可以看到一個名為free_area的屬性,該屬性用于保存這11個塊鏈表。

struct zone {
    ...
    /*
    * 不同長度的空閑區(qū)域
    */
    struct free_area free_area[MAX_ORDER];
    ...
};

結(jié)合之前的知識,我們總結(jié)一下,Linux內(nèi)存管理的結(jié)構(gòu)形如下圖:

深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

當然,這還不是完整的,我們本節(jié)就會將其填充完整。最后借用《深入理解Linux內(nèi)核》中的一個例子簡單介紹一下該算法的工作原理進而結(jié)束簡介這一小節(jié)。

假設(shè)要請求一個256個頁框(2^8)的塊(即1MB)。

  1. 算法先在256個頁的鏈表中檢查是否有一個空閑塊。
  2. 如果沒有這樣的塊,算法會查找下一個更大的頁塊,也就是,在512個頁框的鏈表中找一個空閑塊。
    • 如果存在這樣的塊,內(nèi)核就把256的頁框分成兩等份,一半用作滿足請求,另一半插人到256個頁框的鏈表中。
  3. 如果在512個頁框的塊鏈表中也沒找到空閑塊,就繼續(xù)找更大的塊 一一1024個頁框的塊。
    • 如果這樣的塊存在,內(nèi)核把1024個頁框塊的256個頁框用作請求,然后從剩余的768個頁框中拿512個插入到512個頁框的鏈表中
    • 再把最后的256個插人到256個頁框的鏈表中。
  4. 如果1024個頁框的鏈表還是空的,算法就放棄并發(fā)出錯信號

以上過程的逆過程就是頁框塊的釋放過程,也是該算法名字的由來。內(nèi)核試圖把大小為b的一對空閑伙伴塊合并為一個大小為2b的單獨塊。滿足以下條件的兩個塊稱為伙伴:

  1. 兩個塊具有相同的大小,記作 b。
  2. 它們的物理地址是連續(xù)的。
  3. 第一塊的第一個頁框的物理地址是2 x b x \(2^{12}\)的倍數(shù)。

注意:該算法是迭代的,如果它成功合并所釋放的塊,它會試圖合并2b的塊,以再次試圖形成更大的塊。然而伙伴系統(tǒng)的實現(xiàn)并沒有這么簡單。

避免碎片

伙伴系統(tǒng)作為內(nèi)存管理系統(tǒng),也難以逃脫一個經(jīng)典的難題,物理內(nèi)存的碎片問題。尤其是在系統(tǒng)長期運行后,其內(nèi)存可能會變成如下的樣子:

深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

為了解決這個問題,Linux提供了兩種避免碎片的方式:

  1. 可移動頁
  2. 虛擬可移動內(nèi)存區(qū)

可移動頁

物理內(nèi)存被零散的占據(jù),無法尋找到一塊連續(xù)的大塊內(nèi)存。內(nèi)核2.6.24版本,防止碎片的方法最終加入內(nèi)核。內(nèi)核采用的方法是反碎片,即試圖從最初開始盡可能防止碎片。因為許多物理內(nèi)存頁不能移動到任意位置,因此無法整理碎片。

可以看到,內(nèi)核中內(nèi)存碎片難以處理的主要原因是許多頁無法移動到任意位置,那么如果我們將其單獨管理,在分配大塊內(nèi)存時,嘗試從可以任意移動的內(nèi)存區(qū)域內(nèi)分配,是不是更好呢?

為了達成這一點,Linux首先要了解哪些頁是可移動的,因此,操作系統(tǒng)將內(nèi)核已分配的頁劃分為如下3種類型:

類別名稱 描述
不可移動頁 在內(nèi)存中有固定位置,不能移動到其他地方。核心內(nèi)核分配的大多數(shù)內(nèi)存屬于該類別
可回收頁 不能直接移動,但可以刪除,其內(nèi)容可以從某些源重新生成
可移動頁 可以隨意移動。屬于用戶空間應(yīng)用程序的頁屬于該類別。它們是通過頁表映射的。如果它們復(fù)制到新位置,頁表項可以相應(yīng)地更新,應(yīng)用程序不會注意到任何事

內(nèi)核中定義了一系列宏來表示不同的遷移類型:

#define MIGRATE_UNMOVABLE 0 // 不可移動頁
#define MIGRATE_RECLAIMABLE 1 // 可回收頁
#define MIGRATE_MOVABLE 2 // 可移動頁
#define MIGRATE_RESERVE 3
#define MIGRATE_ISOLATE 4 /* 不能從這里分配 */
#define MIGRATE_TYPES 5

對于其他兩種類型(了解就好):

  • MIGRATE_RESERVE:如果向具有特定可移動性的列表請求分配內(nèi)存失敗,這種緊急情況下可從MIGRATE_RESERVE分配內(nèi)存
  • MIGRATE_ISOLATE:是一個特殊的虛擬區(qū)域,用于跨越NUMA結(jié)點移動物理內(nèi)存頁。在大型系統(tǒng)上,它有益于將物理內(nèi)存頁移動到接近于使用該頁最頻繁的CPU。

伙伴系統(tǒng)實現(xiàn)頁的可移動性特性,依賴于數(shù)據(jù)結(jié)構(gòu)free_area,其代碼如下:

struct free_area {
    struct list_head free_list[MIGRATE_TYPES];
    unsigned long nr_free;
};
屬性名 描述
free_list 每種遷移類型對應(yīng)一個空閑頁鏈表
nr_free 所有列表上空閑頁的數(shù)目

zone.free_area一樣,free_area.free_list也是一個鏈表,但這個鏈表終于直接連接struct page了。因此,我們的內(nèi)存管理結(jié)構(gòu)圖就變成了如下的樣子:

深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

與NUMA內(nèi)存域無法滿足分配請求時會有一個備用列表一樣,當一個遷移類型列表無法滿足分配請求時,同樣也會有一個備用列表,不過這個列表不用代碼生成,而是寫死的:

/*
* 該數(shù)組描述了指定遷移類型的空閑列表耗盡時,其他空閑列表在備用列表中的次序。
*/
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 },/* 從來不用 */
};

該數(shù)據(jù)結(jié)構(gòu)大體上是自明的:在內(nèi)核想要分配不可移動頁時,如果對應(yīng)鏈表為空,則后退到可回收頁鏈表,接下來到可移動頁鏈表,最后到緊急分配鏈表。

在各個遷移鏈表之間,當前的頁面分配狀態(tài)可以從/proc/pagetypeinfo獲得:

深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

虛擬可移動內(nèi)存域

可移動頁給與內(nèi)存分配一種層級分配的能力(按照備用列表順序分配)。但是可能會導(dǎo)致不可移動頁侵入可移動頁區(qū)域。

內(nèi)核在2.6.23版本將虛擬可移動內(nèi)存域(ZONE_MOVABLE)這一功能加入內(nèi)核。其基本思想為:可用的物理內(nèi)存劃分為兩個內(nèi)存域,一個用于可移動分配,一個用于不可移動分配。這會自動防止不可移動頁向可移動內(nèi)存域引入碎片。

取決于體系結(jié)構(gòu)和內(nèi)核配置,ZONE_MOVABLE內(nèi)存域可能位于高端或普通內(nèi)存域:

enum zone_type {
...
    ZONE_NORMAL
#ifdef CONFIG_HIGHMEM
    ZONE_HIGHMEM,
#endif
    ZONE_MOVABLE,
    MAX_NR_ZONES
};

與系統(tǒng)中所有其他的內(nèi)存域相反,ZONE_MOVABLE并不關(guān)聯(lián)到任何硬件上有意義的內(nèi)存范圍。實際上,該內(nèi)存域中的內(nèi)存取自高端內(nèi)存域或普通內(nèi)存域,因此我們在下文中稱ZONE_MOVABLE是一個虛擬內(nèi)存域

那么用于可移動分配和不可移動分配的內(nèi)存域大小如何分配呢?系統(tǒng)提供了兩個參數(shù)用來分配這兩個區(qū)域的大?。?/p>

  • kernelcore參數(shù)用來指定用于不可移動分配的內(nèi)存數(shù)量,即用于既不能回收也不能遷移的內(nèi)存數(shù)量。剩余的內(nèi)存用于可移動分配。
  • 還可以使用參數(shù)movablecore控制用于可移動內(nèi)存分配的內(nèi)存數(shù)量

輔助函數(shù)find_zone_movable_pfns_for_nodes用于計算進入ZONE_MOVABLE的內(nèi)存數(shù)量。如果kernelcore和movablecore參數(shù)都沒有指定,find_zone_movable_pfns_for_nodes會使ZONE_MOVABLE保持為空,該機制處于無效狀態(tài)。

但是ZONE_MOVABLE內(nèi)存域的內(nèi)存會按照如下情況分配:

  • 用于不可移動分配的內(nèi)存會平均地分布到所有內(nèi)存結(jié)點上。
  • 只使用來自最高內(nèi)存域的內(nèi)存。在內(nèi)存較多的32位系統(tǒng)上,這通常會是ZONE_HIGHMEM,但是對于64位系統(tǒng),將使用ZONE_NORMAL或ZONE_DMA32。

為ZONE_MOVABLE內(nèi)存域分配內(nèi)存后,會保存在如下位置:

  • 用于為虛擬內(nèi)存域ZONE_MOVABLE提取內(nèi)存頁的物理內(nèi)存域,保存在全局變量movable_zone中;
  • 對每個結(jié)點來說,zone_movable_pfn[node_id]表示ZONE_MOVABLE在movable_zone內(nèi)存域中所取得內(nèi)存的起始地址。

伙伴系統(tǒng)頁面分配與回收

就伙伴系統(tǒng)的接口而言,NUMA或UMA體系結(jié)構(gòu)是沒有差別的,二者的調(diào)用語法都是相同的。所有函數(shù)的一個共同點是:只能分配2的整數(shù)冪個頁。本節(jié)我們會按照如下順序介紹伙伴系統(tǒng)頁面的分配與回收:

  1. 介紹伙伴系統(tǒng)API接口
  2. 介紹API的核心邏輯

我們會按照分配頁面回收頁面兩節(jié)分別介紹。

分配頁面

分配頁面API

分配頁面的API包含如下4個:

API 描述
alloc_pages(mask, order) 分配\(2^{order}\)頁并返回一個struct page的實例,表示分配的內(nèi)存塊的起始頁
alloc_page(mask) alloc_pages(mask,0)的改寫,只分配1頁內(nèi)存
get_zeroed_page(mask) 分配一頁并返回一個page實例,頁對應(yīng)的內(nèi)存填充0
__get_free_pages(mask, order) 分配頁面,但返回分配內(nèi)存塊的虛擬地址
get_dma_pages(gfp_mask, order) 用來獲得適用于DMA的頁

在空閑內(nèi)存無法滿足請求以至于分配失敗的情況下,所有上述函數(shù)都返回空指針(alloc_pages和alloc_page)或者0(get_zeroed_page、__get_free_pages和__get_free_page)。

可以看到,每個分配頁面的接口都包含一個mask參數(shù),該參數(shù)是內(nèi)存修飾符,用來控制內(nèi)存分配的邏輯,例如內(nèi)存在哪個內(nèi)存區(qū)分配等,為了控制這一點,內(nèi)核提供了如下宏:

/* GFP_ZONEMASK中的內(nèi)存域修飾符(參見linux/mmzone.h,低3位) */
#define __GFP_DMA ((__force gfp_t)0x01u)
#define __GFP_HIGHMEM ((__force gfp_t)0x02u)
#define __GFP_DMA32 ((__force gfp_t)0x04u)
...
#define __GFP_MOVABLE ((__force gfp_t)0x100000u) /* 頁是可移動的 */

注意:設(shè)置__GFP_MOVABLE不會影響內(nèi)核的決策,除非它與__GFP_HIGHMEM同時指定。在這種情況下,會使用特殊的虛擬內(nèi)存域ZONE_MOVABLE滿足內(nèi)存分配請求。

這里給出其他一些掩碼的含義(需要用時現(xiàn)查):

深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

實際上,上面所有用于分配頁面的API,最終都是通過alloc_pages_node方法進行內(nèi)存分配的,其調(diào)用關(guān)系如下:

深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

后面我們將主要討論alloc_pages_node方法的具體邏輯。

alloc_pages_node:分配頁面的具體邏輯

static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask,
unsigned int order)
{
    if (unlikely(order >= MAX_ORDER))
        return NULL;
    /* 未知結(jié)點即當前結(jié)點 */
    if(nid< 0)
        nid = numa_node_id();
    return __alloc_pages(gfp_mask, order,NODE_DATA(nid)->node_zonelists + gfp_zone(gfp_mask));
}

alloc_pages_node方法很簡單,進行了一些簡單的檢查,并將頁面的分配邏輯交由__alloc_pages方法處理。這里我們又見到了老朋友zonelist,如果不熟悉請參見該鏈接。gfp_zone方法,負責根據(jù)gfp_mask選擇分配內(nèi)存的內(nèi)存域,因此可以通過指針運算,選擇合適的zonelist(內(nèi)存區(qū)選擇備用列表)。

分配頁面需要大量的檢查以及選擇合適的內(nèi)存域進行分配,在完成這些工作之后,就可以進行真正的分配物理內(nèi)存。__alloc_pages方法就是按照這個邏輯編寫的。

__alloc_pages會根據(jù)現(xiàn)實情況調(diào)用get_page_from_freelist方法選擇合適的內(nèi)存域,進行內(nèi)存分配,然而內(nèi)存域是否有空閑空間,也有一定的條件,這個條件由zone_watermark_ok函數(shù)判斷。這里的判斷條件主要和zone的幾個watermark有關(guān),即pages_min、pages_low、pages_high,這三個參數(shù)的具體含義可以參考第二章的講解

內(nèi)核提供了如下幾個宏,用于控制到達各個水印指定的臨界狀態(tài)時的行為:

#define ALLOC_NO_WATERMARKS 0x01 /* 完全不檢查水印 */
#define ALLOC_WMARK_MIN 0x02 /* 使用pages_min水印 */
#define ALLOC_WMARK_LOW 0x04 /* 使用pages_low水印 */
#define ALLOC_WMARK_HIGH 0x08 /* 使用pages_high水印 */
#define ALLOC_HARDER 0x10 /* 試圖更努力地分配,即放寬限制 */
#define ALLOC_HIGH 0x20 /* 設(shè)置了__GFP_HIGH */
#define ALLOC_CPUSET 0x40 /* 檢查內(nèi)存結(jié)點是否對應(yīng)著指定的CPU集合 */

前幾個標志表示在判斷頁是否可分配時,需要考慮哪些水印。

  • 默認情況下(即沒有因其他因素帶來的壓力而需要更多的內(nèi)存),只有內(nèi)存域包含頁的數(shù)目至少為zone->pages_high時,才能分配頁。這對應(yīng)于ALLOC_WMARK_HIGH標志。
  • 如果要使用較低(zone->pages_low)或最低(zone->pages_min)設(shè)置,則必須相應(yīng)地設(shè)置ALLOC_WMARK_MIN或ALLOC_WMARK_LOW
  • ALLOC_HARDER通知伙伴系統(tǒng)在急需內(nèi)存時放寬分配規(guī)則
  • 在分配高端內(nèi)存域的內(nèi)存時,ALLOC_HIGH進一步放寬限制
  • ALLOC_CPUSET告知內(nèi)核,內(nèi)存只能從當前進程允許運行的CPU相關(guān)聯(lián)的內(nèi)存結(jié)點分配,當然該選項只對NUMA系統(tǒng)有意義

zone_watermark_ok方法,使用了ALLOC_HIGHALLOC_HARDER標志,其代碼如下:

int zone_watermark_ok(struct zone *z, int order, unsigned long mark,
int classzone_idx, int alloc_flags)
{
    /* free_pages可能變?yōu)樨撝?,沒有關(guān)系 */
    long min = mark;
    long free_pages = zone_page_state(z, NR_FREE_PAGES) -(1 << order) + 1;
    int o;
    if (alloc_flags & ALLOC_HIGH)
        min -= min / 2;
    if (alloc_flags & ALLOC_HARDER)
        min -= min / 4;
    if (free_pages <= min + z->lowmem_reserve[classzone_idx])
        return 0;
    for(o= 0;o <order;o++){
        /* 在下一階,當前階的頁是不可用的 */
        free_pages -= z->free_area[o].nr_free << o;
        /* 所需高階空閑頁的數(shù)目相對較少 */
        min >>= 1;
        if (free_pages <= min)
          return 0;
    }
    return 1;
}

注意,zone_watermark_ok方法中的mark參數(shù)就是zone中的水印,根據(jù)設(shè)置的ALLOC_WMARK_*標志的不同,mark選擇對應(yīng)的pages_*水印,zone_page_state方法用于訪問內(nèi)存域中的統(tǒng)計量,由于提供了標志NR_FREE_PAGES,這里獲取的是內(nèi)存域中空閑頁的數(shù)目。

可以看到當flag設(shè)置了ALLOC_HIGH和ALLOC_HARDER后,min的閾值變小了,這也就是所謂的放寬了限制。當前內(nèi)存域需要滿足如下兩個條件才能進行內(nèi)存分配:

  1. min+lowmem_reserve中指定的緊急分配值 < 內(nèi)存域中的空閑頁數(shù)目
  2. 對于指定order前的每一個分配階,都要高于當前階的min值(每升高一階,所需空閑頁的最小值折半)

了解了內(nèi)存域的可用性條件后,我們將討論,哪個方法負責從備用列表中選擇合適的內(nèi)存域。該方法為get_page_from_freelist,如果查找到對應(yīng)的內(nèi)存域,將發(fā)起實際的分配操作。

static struct page *
get_page_from_freelist(gfp_t gfp_mask, unsigned int order,
struct zonelist *zonelist, int alloc_flags)
{
    struct zone **z;
    struct page *page = NULL;
    int classzone_idx = zone_idx(zonelist->zones[0]);
    struct zone *zone;
    ...
    /*
    * 掃描zonelist,尋找具有足夠空閑空間的內(nèi)存域。
    * 請參閱kernel/cpuset.c中cpuset_zone_allowed()的注釋。
    */
    z = zonelist->zones;
    do {
        ...
        zone = *z;
        //cpuset_zone_allowed_softwall是另一個輔助函數(shù),用于檢查給定內(nèi)存域是否屬于該進程允許運行的CPU
        if ((alloc_flags & ALLOC_CPUSET) &&!cpuset_zone_allowed_softwall(zone, gfp_mask))
            continue;
        if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {
            unsigned long mark;
            if (alloc_flags & ALLOC_WMARK_MIN)
                mark = zone->pages_min;
            else if (alloc_flags & ALLOC_WMARK_LOW)
                mark = zone->pages_low;
            else
                mark = zone->pages_high;
            if (!zone_watermark_ok(zone, order, mark,classzone_idx, alloc_flags))
              continue;
        }
        page = buffered_rmqueue(*z, order, gfp_mask);
        if (page) {
            zone_statistics(zonelist, *z);
            break;
        }
    } while (*(++z) != NULL);
    return page;
}

可以看到do..while循環(huán)遍歷了整個備用列表,通過zone_watermark_ok方法查找第一個可用的內(nèi)存域,查找到后進行內(nèi)存分配(buffered_rmqueue方法負責處理分配邏輯)。

__alloc_pages通過調(diào)用get_page_from_freelist方法進行實際的分配,但是,分配內(nèi)存的時機是一個很復(fù)雜的問題,在現(xiàn)實生活中,內(nèi)存并不總是充足的,為了充分解決這些情況,__alloc_pages方法考慮了諸多情況:

  1. 內(nèi)存充足時,調(diào)用get_page_from_freelist方法直接分配:

    struct page * fastcall
    __alloc_pages(gfp_t gfp_mask, unsigned int order,
    struct zonelist *zonelist)
    {
        const gfp_t wait = gfp_mask & __GFP_WAIT;
        struct zone **z;
        struct page *page;
        struct reclaim_state reclaim_state;
        struct task_struct *p = current;
        int do_retry;
        int alloc_flags;
        int did_some_progress;
        might_sleep_if(wait);
    restart:
        z = zonelist->zones; /* 適合于gfp_mask的內(nèi)存域列表 */
        if (unlikely(*z == NULL)) {
            /*
            *如果在沒有內(nèi)存的結(jié)點上使用GFP_THISNODE,導(dǎo)致zonelist為空,就會發(fā)生這種情況
            */
            return NULL;
        }
        page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, order,zonelist, ALLOC_WMARK_LOW|ALLOC_CPUSET);
        if (page)
            goto got_pg;
    ...
    

    可以看到,第一次嘗試分配內(nèi)存時,系統(tǒng)對分配的要求會比較嚴格:

    1. gft_mask設(shè)置了__GFP_HARDWALL:它限制只在分配到當前進程的各個CPU所關(guān)聯(lián)的結(jié)點分配內(nèi)存。
    2. flag設(shè)置了ALLOC_WMARK_LOW和ALLOC_CPUSET(這兩個含義代碼注釋里有,這里就不解釋了)
  2. 首次分配失敗后,內(nèi)核會喚醒負責換出頁的kswapd守護進程,寫回或換出很少使用的頁。在交換守護進程喚醒后,再次嘗試get_page_from_freelist:

    ...
        for (z = zonelist->zones; *z; z++)
            wakeup_kswapd(*z, order);
        alloc_flags = ALLOC_WMARK_MIN;
        if ((unlikely(rt_task(p)) && !in_interrupt()) || !wait)
            alloc_flags |= ALLOC_HARDER;
        if (gfp_mask & __GFP_HIGH)
            alloc_flags |= ALLOC_HIGH;
        if (wait)
            alloc_flags |= ALLOC_CPUSET;
        page = get_page_from_freelist(gfp_mask, order, zonelist, alloc_flags);
        if (page)
            goto got_pg;
    ...
    }
    

    此處的策略不僅換出了非常用頁,而且放寬了水印的判斷條件:

    1. alloc_flags成為了ALLOC_WMARK_MIN
    2. 對實時進程和指定了__GFP_WAIT標志因而不能睡眠的調(diào)用,會設(shè)置ALLOC_HARDER。
  3. 如果設(shè)置了PF_MEMALLOC或進程設(shè)置了TIF_MEMDIE標志(在這兩種情況下,內(nèi)核不能處于中斷上下文中),內(nèi)核會忽略所有水印,調(diào)用get_page_from_freelist方法:

    rebalance:
        if (((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE)))&& !in_interrupt()) {
            if (!(gfp_mask & __GFP_NOMEMALLOC)) {
    nofail_alloc:
              /* 再一次遍歷zonelist,忽略水印 */
              page = get_page_from_freelist(gfp_mask, order,zonelist, ALLOC_NO_WATERMARKS);
              if (page)
                  goto got_pg;
              if (gfp_mask & __GFP_NOFAIL) {
                  congestion_wait(WRITE, HZ/50);
                  goto nofail_alloc;
              }
           }
           goto nopage;
      }
    ...
    

    通常只有在分配器自身需要更多內(nèi)存時,才會設(shè)置PF_MEMALLOC,而只有在線程剛好被OOM killer機制選中時,才會設(shè)置TIF_MEMDIE

    這里的兩個goto語句負責處理此種情況下,內(nèi)存分配失敗的情況:

    1. 設(shè)置了__GFP_NOMEMALLOC。該標志禁止使用緊急分配鏈表(如果忽略水印,這可能是最佳途徑),因此無法在禁用水印的情況下調(diào)用get_page_from_freelist。跳轉(zhuǎn)到nopage處,通過內(nèi)核消息將失敗報告給用戶,并將NULL指針返回調(diào)用者
    2. 在忽略水印的情況下,get_page_from_freelist仍然失敗了,這種情況下會放棄搜索,報告錯誤消息。如果設(shè)置了__GFP_NOFAIL,內(nèi)核會進入無限循環(huán)(跳轉(zhuǎn)到第4行的標號nofail_alloc),重復(fù)本段內(nèi)容。
  4. 如果上述3種情況都沒有成功分配內(nèi)存,內(nèi)核會進行一些耗時的操作。。前提是分配掩碼中設(shè)置了__GFP_WAIT標志,因為隨后的操作可能使進程睡眠(為了使得kswapd取得一些進展)。

        if (!wait)
            goto nopage;
        cond_schedule();
    ...
    

    如果wait標志沒有被設(shè)置,這里會放棄分配。如果設(shè)置了,內(nèi)核通過cond_resched?供了重調(diào)度的時機。這防止了花費過多時間搜索內(nèi)存,以致于使其他進程處于饑餓狀態(tài)。

    分頁機制提供了一個目前尚未使用的選項,將很少使用的頁換出到塊介質(zhì),以便在物理內(nèi)存中產(chǎn)生更多空間。但該選項非常耗時,還可能導(dǎo)致進程睡眠狀態(tài)。try_to_free_pages是相應(yīng)的輔助函數(shù),用于查找當前不急需的頁,以便換出。

        /* 我們現(xiàn)在進入同步回收狀態(tài) */
        p->flags |= PF_MEMALLOC;
    ...
        did_some_progress = try_to_free_pages(zonelist->zones, order, gfp_mask);
    ...
        p->flags &= ~PF_MEMALLOC;
        cond_resched();
    ...
    

    該調(diào)用被設(shè)置/清除PF_MEMALLOC標志的代碼間隔起來。try_to_free_pages自身可能也需要分配新的內(nèi)存。由于為獲得新內(nèi)存還需要額外分配一點內(nèi)存(相當矛盾的情形),該進程當然應(yīng)該在內(nèi)存管理方面享有最高優(yōu)先級,上述標志的設(shè)置即達到了這一目的。try_to_free_pages會返回增加的空閑頁數(shù)目。

    接下來,如果try_to_free_pages釋放了一些頁,那么內(nèi)核再次調(diào)用get_page_from_freelist嘗試分配內(nèi)存:

        if (likely(did_some_progress)) {
            page = get_page_from_freelist(gfp_mask, order,zonelist, alloc_flags);
        if (page)
            goto got_pg;
        } else if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {
    ...
    

    如果內(nèi)核可能執(zhí)行影響VFS層的調(diào)用而又沒有設(shè)置GFP_NORETRY,那么調(diào)用OOM killer:

    /* OOM killer無助于高階分配,因此失敗 */
        if (order > PAGE_ALLOC_COSTLY_ORDER) {
            clear_zonelist_oom(zonelist);
            goto nopage;
        }
        out_of_memory(zonelist, gfp_mask, order);
        goto restart;
    }
    

    out_of_memory函數(shù)函數(shù)選擇一個內(nèi)核認為犯有分配過多內(nèi)存“罪行”的進程,并殺死該進程。這有很大幾率騰出較多的空閑頁,然后跳轉(zhuǎn)到標號restart,重試分配內(nèi)存的操作。但殺死一個進程未必立即出現(xiàn)多于\(2^{PAGE_COSTLY_ORDER}\)頁的連續(xù)內(nèi)存區(qū)(其中PAGE_COSTLY_ORDER_PAGES通常設(shè)置為3),因此如果當前要分配如此大的內(nèi)存區(qū),那么內(nèi)核會饒恕所選擇的進程,不執(zhí)行殺死進程的任務(wù),而是承認失敗并跳轉(zhuǎn)到nopage。

    如果設(shè)置了__GFP_NORETRY,或內(nèi)核不允許使用可能影響VFS層的操作,會判斷所需分配的長度,作出不同的決定:

    ...
        do_retry = 0;
        if (!(gfp_mask & __GFP_NORETRY)) {
            if ((order <= PAGE_ALLOC_COSTLY_ORDER) ||(gfp_mask & __GFP_REPEAT))
                do_retry = 1;
            if (gfp_mask & __GFP_NOFAIL)
                do_retry = 1;
        }
        if (do_retry) {
             congestion_wait(WRITE, HZ/50);
             goto rebalance;
        }
        nopage:
        if (!(gfp_mask & __GFP_NOWARN) && printk_ratelimit()) {
            printk(KERN_WARNING "%s: page allocation failure."" order:%d, mode:0x%x\n"p->comm, order, gfp_mask);
            dump_stack();
            show_mem();
        }
    got_pg:
        return page;
    }
    
    • 如果分配長度小于\(2^{PAGE_ALLOC_COSTLY_ORDER}\)=8頁,或設(shè)置了__GFP_REPEAT標志,則內(nèi)核進入無限循環(huán)。在這兩種情況下,是不能設(shè)置GFP_NORETRY的。因為如果調(diào)用者不打算重試,那么進入無限循環(huán)重試并沒有意義。內(nèi)核會跳轉(zhuǎn)回rebalance標號,即 的入口,并一直等待,直至找到適當大小的內(nèi)存塊——根據(jù)所要分配的內(nèi)存大小,內(nèi)核可以假定該無限循環(huán)不會持續(xù)太長時間。內(nèi)核在跳轉(zhuǎn)之前會調(diào)用congestion_wait,等待塊設(shè)備層隊列釋放,這樣內(nèi)核就有機會換出頁。
    • 在所要求的分配階大于3但設(shè)置了__GFP_NOFAIL標志的情況下,內(nèi)核也會進入上述無限循環(huán),因為該標志無論如何都不允許失敗。
    • 如果情況不是這樣,內(nèi)核只能放棄,并向用戶返回NULL指針,并輸出一條內(nèi)存請求無法滿足的警告消息。

總結(jié)

本節(jié)主要總結(jié)了伙伴系統(tǒng)中__alloc_pages方法的主要流程,由于后續(xù)內(nèi)容過多,這里會分為多個小結(jié)總結(jié)。文章來源地址http://www.zghlxwxcb.cn/news/detail-692665.html

到了這里,關(guān)于深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 深入理解Linux虛擬內(nèi)存管理

    深入理解Linux虛擬內(nèi)存管理

    Linux 內(nèi)核設(shè)計與實現(xiàn) 深入理解 Linux 內(nèi)核 Linux 設(shè)備驅(qū)動程序 Linux設(shè)備驅(qū)動開發(fā)詳解 深入理解Linux虛擬內(nèi)存管理(一) 深入理解Linux虛擬內(nèi)存管理(二) 深入理解Linux虛擬內(nèi)存管理(三) 深入理解Linux虛擬內(nèi)存管理(四) 深入理解Linux虛擬內(nèi)存管理(五) 深入理解Linux虛擬內(nèi)存

    2024年02月06日
    瀏覽(18)
  • 深入理解Linux虛擬內(nèi)存管理(六)

    深入理解Linux虛擬內(nèi)存管理(六)

    Linux 內(nèi)核設(shè)計與實現(xiàn) 深入理解 Linux 內(nèi)核 Linux 設(shè)備驅(qū)動程序 Linux設(shè)備驅(qū)動開發(fā)詳解 深入理解Linux虛擬內(nèi)存管理(一) 深入理解Linux虛擬內(nèi)存管理(二) 深入理解Linux虛擬內(nèi)存管理(三) 深入理解Linux虛擬內(nèi)存管理(四) 深入理解Linux虛擬內(nèi)存管理(五) 深入理解Linux虛擬內(nèi)存

    2024年02月08日
    瀏覽(23)
  • Linux內(nèi)核源碼分析 (B.4) 深度剖析 Linux 伙伴系統(tǒng)的設(shè)計與實現(xiàn)

    Linux內(nèi)核源碼分析 (B.4) 深度剖析 Linux 伙伴系統(tǒng)的設(shè)計與實現(xiàn)

    Linux內(nèi)核源碼分析 (B.4) 深度剖析 Linux 伙伴系統(tǒng)的設(shè)計與實現(xiàn) 在上篇文章 《深入理解 Linux 物理內(nèi)存分配全鏈路實現(xiàn)》 中,筆者為大家詳細介紹了 Linux 內(nèi)存分配在內(nèi)核中的整個鏈路實現(xiàn): image.png 但是當內(nèi)核執(zhí)行到 get_page_from_freelist 函數(shù),準備進入伙伴系統(tǒng)執(zhí)行具體內(nèi)存分配

    2024年02月07日
    瀏覽(25)
  • Linux源碼解讀系列是一套深入剖析Linux內(nèi)核源碼的教程,旨在幫助讀者理解Linux操作系統(tǒng)的底層原理和工作機制

    Linux源碼解讀系列是一套深入剖析Linux內(nèi)核源碼的教程,旨在幫助讀者理解Linux操作系統(tǒng)的底層原理和工作機制

    Linux源碼解讀系列是一套深入剖析Linux內(nèi)核源碼的教程,旨在幫助讀者理解Linux操作系統(tǒng)的底層原理和工作機制。該系列教程從Linux內(nèi)核的各個模塊入手,逐一分析其源碼實現(xiàn),并結(jié)合實際應(yīng)用場景進行講解。通過學習本系列,讀者可以深入了解Linux操作系統(tǒng)的底層機制,掌握

    2024年01月21日
    瀏覽(26)
  • Linux 內(nèi)核深入理解 - 緒論

    目錄 多用戶系統(tǒng) 進程 內(nèi)核體系架構(gòu) 文件系統(tǒng)概述 Base 硬鏈接和軟鏈接 Unix文件類型 文件描述符與索引節(jié)點 文件操作的系統(tǒng)調(diào)用 Unix內(nèi)核簡述 進程的實現(xiàn) 可重入內(nèi)核 進程地址空間 同步和臨界區(qū) 信號與進程之間的通信 進程管理 內(nèi)存管理 虛擬內(nèi)存 隨機訪問存儲器的使用 內(nèi)

    2024年04月28日
    瀏覽(20)
  • 深入理解 Linux 內(nèi)核

    深入理解 Linux 內(nèi)核

    Linux 內(nèi)核設(shè)計與實現(xiàn) 深入理解 Linux 內(nèi)核 深入理解 Linux 內(nèi)核(二) Linux 設(shè)備驅(qū)動程序 Linux設(shè)備驅(qū)動開發(fā)詳解 ??本文主要用來摘錄《深入理解 Linux 內(nèi)核》一書中學習知識點,本書基于 Linux 2.6.11 版本,源代碼摘錄基于 Linux 2.6.34 ,兩者之間可能有些出入。 ??可參考 ? 1、

    2023年04月27日
    瀏覽(28)
  • 深入理解Linux 內(nèi)核追蹤機制

    深入理解Linux 內(nèi)核追蹤機制

    Linux 存在眾多 tracing tools,比如 ftrace、perf,他們可用于內(nèi)核的調(diào)試、提高內(nèi)核的可觀測性。眾多的工具也意味著繁雜的概念,諸如 tracepoint、trace events、kprobe、eBPF 等,甚至讓人搞不清楚他們到底是干什么的。本文嘗試理清這些概念。 ? Probe Handler 如果我們想要追蹤內(nèi)核的一

    2024年02月15日
    瀏覽(29)
  • Linux Centos系統(tǒng) 磁盤分區(qū)和文件系統(tǒng)管理 (深入理解)

    Linux Centos系統(tǒng) 磁盤分區(qū)和文件系統(tǒng)管理 (深入理解)

    作者主頁: 點擊! Linux專欄:點擊! 磁盤 在Linux系統(tǒng)中,磁盤是一種用于存儲數(shù)據(jù)的物理設(shè)備,可以是傳統(tǒng)的硬盤驅(qū)動器(HDD)或固態(tài)硬盤(SSD)。Linux將磁盤設(shè)備視為塊設(shè)備,它們通常以文件形式表示在 /dev 目錄下。 文件系統(tǒng) 在計算機系統(tǒng)中, 文件系統(tǒng) 定義了如何存儲

    2024年03月15日
    瀏覽(22)
  • 深入理解Linux權(quán)限管理:保護系統(tǒng)安全的重要措施

    Linux操作系統(tǒng)以其穩(wěn)定性、可靠性和靈活性而受到廣泛使用。其中一個關(guān)鍵特性是其強大的權(quán)限管理系統(tǒng),它可以保護系統(tǒng)資源和用戶數(shù)據(jù)的安全性。本文將深入探討Linux權(quán)限管理的概念、原則和實踐,幫助您理解如何正確配置和管理權(quán)限,以確保系統(tǒng)的安全性和完整性。 第

    2024年02月11日
    瀏覽(14)
  • 【深入理解Linux內(nèi)核鎖】三、原子操作

    系列文章 : 我的圈子:高級工程師聚集地 【深入理解Linux鎖機制】一、內(nèi)核鎖的由來 【深入理解Linux鎖機制】二、中斷屏蔽 【深入理解Linux鎖機制】三、原子操作 【深入理解Linux鎖機制】四、自旋鎖 【深入理解Linux鎖機制】五、衍生自旋鎖 【深入理解Linux鎖機制】六、信號

    2024年02月12日
    瀏覽(28)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包