啓動期間的內存管理之build_zonelists初始化備用內存域列表zonelists--Linux內存管理(十三)

1. 今日內容(第二階段(二)–初始化備用內存域列表zonelists)

咱們以前講了在memblock完成以後, 內存初始化開始進入第二階段, 第二階段是一個漫長的過程, 它執行了一系列複雜的操做, 從體系結構相關信息的初始化慢慢向上層展開, 其主要執行了以下操做node

特定於體系結構的設置linux

在完成了基礎的內存結點和內存域的初始化工做之後, 咱們必須克服一些硬件的特殊設置算法

  • 在初始化內存的結點和內存區域以前, 內核先經過pagging_init初始化了內核的分頁機制, 這樣咱們的虛擬運行空間就初步創建, 並能夠完成物理地址到虛擬地址空間的映射工做.

在arm64架構下, 內核在start_kernel()->setup_arch()中經過arm64_memblock_init( )完成了memblock的初始化以後, 接着經過setup_arch()->paging_init()開始初始化分頁機制c#

paging_init負責創建只能用於內核的頁表, 用戶空間是沒法訪問的. 這對管理普通應用程序和內核訪問內存的方式,有深遠的影響數組

  • 在分頁機制完成後, 內核經過setup_arch()->bootmem_init開始進行內存基本數據結構(內存結點pg_data_t, 內存域zone和頁幀)的初始化工做, 就是在這個函數中, 內核開始從體系結構相關的部分逐漸展開到體系結構無關的部分, 在zone_sizes_init->free_area_init_node中開始, 內核開始進行內存基本數據結構的初始化, 也再也不依賴於特定體系結構無關的層次
bootmem_init
始化內存數據結構包括內存節點, 內存域和頁幀page
|
|---->arm64_numa_init();
|     支持numa架構
|
|---->zone_sizes_init(min, max);
    來初始化節點和管理區的一些數據項
    |
    |---->free_area_init_node
    |   初始化內存節點
    |
        |---->free_area_init_core
            |   初始化zone
            |
            |---->memmap_init
            |   初始化page頁面
|
|---->memblock_dump_all();
|   初始化完成, 顯示memblock的保留的全部內存信息

至此,bootmem_init已經完成了節點和管理區的關鍵數據已完成初始化, 內核在後面爲內存管理作得一個準備工做就是將全部節點的管理區都鏈入到zonelist中,便於後面內存分配工做的進行.緩存

內核在start_kernel()–>build_all_zonelist()中完成zonelist的初始化數據結構

2 後備內存域列表zonelists

內核setup_arch的最後經過bootmem_init中完成了內存數據結構的初始化(包括內存結點pg_data_t, 內存管理域zone和頁面信息page), 數據結構已經基本準備好了, 在後面爲內存管理作得一個準備工做就是將全部節點的管理區都鏈入到zonelist中, 便於後面內存分配工做的進行.架構

2.1 回到start_kernel函數(已經完成的工做)

asmlinkage __visible void __init start_kernel(void)
{

    setup_arch(&command_line);


    build_all_zonelists(NULL, NULL);
    page_alloc_init();


    /*
     * These use large bootmem allocations and must precede
     * mem_init();
     * kmem_cache_init();
     */
    mm_init();

    kmem_cache_init_late();

    kmemleak_init();
    setup_per_cpu_pageset();

    rest_init();
}

下面內核開始經過start_kernel()->build_all_zonelists來設計內存的組織形式app

2.2 後備內存域列表zonelist

內存節點pg_data_t中將內存節點中的內存區域zone按照某種組織層次存儲在一個zonelist中, 即pglist_data->node_zonelists成員信息less

//  http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=4.7#L626
typedef struct pglist_data
{
    struct zone node_zones[MAX_NR_ZONES];
    struct zonelist node_zonelists[MAX_ZONELISTS];
}

內核定義了內存的一個層次結構關係, 首先試圖分配廉價的內存,若是失敗,則根據訪問速度和容量,逐漸嘗試分配更昂貴的內存.

高端內存最廉價, 由於內核沒有任何部分依賴於從該內存域分配的內存, 若是高端內存用盡, 對內核沒有反作用, 因此優先分配高端內存

普通內存域的狀況有所不一樣, 許多內核數據結構必須保存在該內存域, 而不能放置到高端內存域, 所以若是普通內存域用盡, 那麼內核會面臨內存緊張的狀況

DMA內存域最昂貴,由於它用於外設和系統之間的數據傳輸。

舉例來說,若是內核指定想要分配高端內存域。它首先在當前結點的高端內存域尋找適當的空閒內存段,若是失敗,則查看該結點的普通內存域,若是還失敗,則試圖在該結點的DMA內存域分配。若是在3個本地內存域都沒法找到空閒內存,則查看其餘結點。這種狀況下,備選結點應該儘量靠近主結點,以最小化訪問非本地內存引發的性能損失。

2.3 build_all_zonelists初始化zonelists

內核在start_kernel中經過build_all_zonelists完成了內存結點及其管理內存域的初始化工做, 調用以下

build_all_zonelists(NULL, NULL);

build_all_zonelists創建內存管理結點及其內存域的組織形式, 將描述內存的數據結構(結點, 管理域, 頁幀)經過必定的算法組織在一塊兒, 方便之後內存管理工做的進行. 該函數定義在mm/page_alloc.c?v4.7, line 5029

2.4 build_all_zonelists函數

/*
 * Called with zonelists_mutex held always
 * unless system_state == SYSTEM_BOOTING.
 *
 * __ref due to (1) call of __meminit annotated setup_zone_pageset
 * [we're only called with non-NULL zone through __meminit paths] and
 * (2) call of __init annotated helper build_all_zonelists_init
 * [protected by SYSTEM_BOOTING].
 */
void __ref build_all_zonelists(pg_data_t *pgdat, struct zone *zone)
{
    /*  設置zonelist中節點和內存域的組織形式
     *  current_zonelist_order變量標識了當前系統的內存組織形式
     *  zonelist_order_name以字符串存儲了系統中內存組織形式的名稱  */
    set_zonelist_order();

    if (system_state == SYSTEM_BOOTING) {
        build_all_zonelists_init();
    } else {
#ifdef CONFIG_MEMORY_HOTPLUG
        if (zone)
            setup_zone_pageset(zone);
#endif
        /* we have to stop all cpus to guarantee there is no user
           of zonelist */
        stop_machine(__build_all_zonelists, pgdat, NULL);
        /* cpuset refresh routine should be here */
    }
    vm_total_pages = nr_free_pagecache_pages();
    /*
     * Disable grouping by mobility if the number of pages in the
     * system is too low to allow the mechanism to work. It would be
     * more accurate, but expensive to check per-zone. This check is
     * made on memory-hotadd so a system can start with mobility
     * disabled and enable it later
     */
    if (vm_total_pages < (pageblock_nr_pages * MIGRATE_TYPES))
        page_group_by_mobility_disabled = 1;
    else
        page_group_by_mobility_disabled = 0;

    pr_info("Built %i zonelists in %s order, mobility grouping %s.  Total pages: %ld\n",
        nr_online_nodes,
        zonelist_order_name[current_zonelist_order],
        page_group_by_mobility_disabled ? "off" : "on",
        vm_total_pages);
#ifdef CONFIG_NUMA
    pr_info("Policy zone: %s\n", zone_names[policy_zone]);
#endif
}

3 設置結點初始化順序

在build_all_zonelists開始, 首先內核經過set_zonelist_order函數設置了zonelist_order,以下所示, 參見mm/page_alloc.c?v=4.7, line 5031

void __ref build_all_zonelists(pg_data_t *pgdat, struct zone *zone)
{
    set_zonelist_order();
    /* .......  */
}

3.1 zonelist

前面咱們講解內存管理域時候講解到, 系統中的全部管理域都存儲在一個多維的數組zone_table. 內核在初始化內存管理區時, 必需要創建管理區表zone_table. 參見mm/page_alloc.c?v=2.4.37, line 38

/*
 *
 * The zone_table array is used to look up the address of the
 * struct zone corresponding to a given zone number (ZONE_DMA,
 * ZONE_NORMAL, or ZONE_HIGHMEM).
 */
zone_t *zone_table[MAX_NR_ZONES*MAX_NR_NODES];
EXPORT_SYMBOL(zone_table);
  • MAX_NR_NODES爲系統中內存結點的數目
  • MAX_NR_ZONES爲系統中單個內存結點所擁有的最大內存區域數目

3.2 內存域初始化順序zonelist_order

NUMA系統中存在多個節點, 每一個節點對應一個struct pglist_data結構, 每一個結點中能夠包含多個zone, 如: ZONE_DMA, ZONE_NORMAL, 這樣就產生幾種排列順序, 以2個節點2個zone爲例(zone從高到低排列, ZONE_DMA0表示節點0的ZONE_DMA,其它相似).

  • Legacy方式, 每一個節點只排列本身的zone;

  • Node方式, 按節點順序依次排列,先排列本地節點的全部zone,再排列其它節點的全部zone。

  • Zone方式, 按zone類型從高到低依次排列各節點的同相類型zone

可經過啓動參數」numa_zonelist_order」來配置zonelist order,內核定義了3種配置, 這些順序定義在mm/page_alloc.c?v=4.7, line 4551

// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4551
/*
 *  zonelist_order:
 *  0 = automatic detection of better ordering.
 *  1 = order by ([node] distance, -zonetype)
 *  2 = order by (-zonetype, [node] distance)
 *
 *  If not NUMA, ZONELIST_ORDER_ZONE and ZONELIST_ORDER_NODE will create
 *  the same zonelist. So only NUMA can configure this param.
 */
#define ZONELIST_ORDER_DEFAULT  0 /* 智能選擇Node或Zone方式 */

#define ZONELIST_ORDER_NODE     1 /* 對應Node方式 */

#define ZONELIST_ORDER_ZONE     2 /* 對應Zone方式 */

注意

在非NUMA系統中(好比UMA), 因爲只有一個內存結點, 所以ZONELIST_ORDER_ZONE和ZONELIST_ORDER_NODE選項會配置相同的內存域排列方式, 所以, 只有NUMA能夠配置這幾個參數

全局的current_zonelist_order變量標識了系統中的當前使用的內存域排列方式, 默認配置爲ZONELIST_ORDER_DEFAULT, 參見mm/page_alloc.c?v=4.7, line 4564

//  http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4564
/* zonelist order in the kernel.
 * set_zonelist_order() will set this to NODE or ZONE.
 */
static int current_zonelist_order = ZONELIST_ORDER_DEFAULT;
static char zonelist_order_name[3][8] = {"Default", "Node", "Zone"};

而zonelist_order_name方式分別對應了Legacy方式, Node方式和Zone方式. 其zonelist_order_name[current_zonelist_order]就標識了當前系統中所使用的內存域排列方式的名稱」Default」, 「Node」, 「Zone」.

zonelist_order_name宏 排列方式 描述
ZONELIST_ORDER_DEFAULT Default 由系統智能選擇Node或Zone方式
ZONELIST_ORDER_NODE Node Node方式 按節點順序依次排列,先排列本地節點的全部zone,再排列其它節點的全部zone
ZONELIST_ORDER_ZONE Zone Zone方式 按zone類型從高到低依次排列各節點的同相類型zone

3.3 set_zonelist_order設置排列方式

內核就經過經過set_zonelist_order函數設置當前系統的內存域排列方式current_zonelist_order, 其定義依據系統的NUMA結構仍是UMA結構有很大的不一樣.

// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4571
#ifdef CONFIG_NUMA
/* The value user specified ....changed by config */
static int user_zonelist_order = ZONELIST_ORDER_DEFAULT;
/* string for sysctl */
#define NUMA_ZONELIST_ORDER_LEN 16
char numa_zonelist_order[16] = "default";


//  http://lxr.free-electrons.com/source/mm/page_alloc.c#L4571
static void set_zonelist_order(void)
{
    if (user_zonelist_order == ZONELIST_ORDER_DEFAULT)
        current_zonelist_order = default_zonelist_order();
    else
        current_zonelist_order = user_zonelist_order;
}


#else   /* CONFIG_NUMA */

//  http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4892
static void set_zonelist_order(void)
{
    current_zonelist_order = ZONELIST_ORDER_ZONE;
}

其設置的基本流程以下

  • 若是系統當前系統是非NUMA結構的, 則系統中只有一個結點, 配置ZONELIST_ORDER_NODE和ZONELIST_ORDER_ZONE結果相同. 那麼set_zonelist_order函數被定義爲直接配置當前系統的內存域排列方式current_zonelist_order爲ZONE方式(與NODE效果相同)
  • 若是系統是NUMA結構, 則設置爲系統指定的方式便可
  1. 當前的排列方式爲ZONELIST_ORDER_DEFAULT, 即系統默認方式, 則current_zonelist_order則由內核交給default_zonelist_order採用必定的算法選擇一個最優的分配策略, 目前的系統中若是是32位則配置爲ZONE方式, 而若是是64位系統則設置爲NODE方式
  2. 當前的排列方式不是默認方式, 則設置爲user_zonelist_order指定的內存域排列方式

3.4 default_zonelist_order函數選擇最優的配置

在UMA結構下, 內存域使用NODE和ZONE兩個排列方式會產生相同的效果, 所以系統不用特殊指定, 直接經過set_zonelist_order函數, 將當前系統的內存域排列方式current_zonelist_order配置爲爲ZONE方式(與NODE效果相同)便可

可是NUMA結構下, 默認狀況下(當配置了ZONELIST_ORDER_DEFAULT), 系統須要根據系統自身的環境信息選擇一個最優的配置(NODE或者ZONE方式), 這個工做就由default_zonelist_order函數了來完成. 其定義在mm/page_alloc.c?v=4.7, line 4789

#if defined(CONFIG_64BIT)
/*
 * Devices that require DMA32/DMA are relatively rare and do not justify a
 * penalty to every machine in case the specialised case applies. Default
 * to Node-ordering on 64-bit NUMA machines
 */
static int default_zonelist_order(void)
{
    return ZONELIST_ORDER_NODE;
}
#else
/*
 * On 32-bit, the Normal zone needs to be preserved for allocations accessible
 * by the kernel. If processes running on node 0 deplete the low memory zone
 * then reclaim will occur more frequency increasing stalls and potentially
 * be easier to OOM if a large percentage of the zone is under writeback or
 * dirty. The problem is significantly worse if CONFIG_HIGHPTE is not set.
 * Hence, default to zone ordering on 32-bit.
 */
static int default_zonelist_order(void)
{
    return ZONELIST_ORDER_ZONE;
}
#endif /* CONFIG_64BIT */

3.5 user_zonelist_order用戶指定排列方式

在NUMA結構下, 系統支持用戶指定內存域的排列方式, 用戶以字符串的形式操做numa_zonelist_order(default, node和zone), 最終被內核轉換爲user_zonelist_order, 這個變量被指定爲字符串numa_zonelist_order指定的排列方式, 他們定義在mm/page_alloc.c?v4.7, line 4573, 注意只有在NUMA結構中才須要這個配置信息.

#ifdef CONFIG_NUMA
/* The value user specified ....changed by config */
static int user_zonelist_order = ZONELIST_ORDER_DEFAULT;
/* string for sysctl */
#define NUMA_ZONELIST_ORDER_LEN 16
char numa_zonelist_order[16] = "default";

#else
/* ......*/
#endif

而接受和處理用戶配置的工做, 天然是交給咱們強大的proc文件系統來完成的, 能夠經過/proc/sys/vm/numa_zonelist_order動態改變zonelist order的分配方式。

內核經過setup_numa_zonelist_order讀取並處理用戶寫入的配置信息

  • 接收到用戶的信息後用__parse_numa_zonelist_order處理接收的參數
  • 若是前面用__parse_numa_zonelist_order處理的信息串成功, 則將對用的設置信息寫入到字符串numa_zonelist_order中

參見mm/page_alloc.c?v=4.7, line 4578

/*
 * interface for configure zonelist ordering.
 * command line option "numa_zonelist_order"
 *      = "[dD]efault   - default, automatic configuration.
 *      = "[nN]ode      - order by node locality, then by zone within node
 *      = "[zZ]one      - order by zone, then by locality within zone
 */

static int __parse_numa_zonelist_order(char *s)
{
    if (*s == 'd' || *s == 'D') {
        user_zonelist_order = ZONELIST_ORDER_DEFAULT;
    } else if (*s == 'n' || *s == 'N') {
        user_zonelist_order = ZONELIST_ORDER_NODE;
    } else if (*s == 'z' || *s == 'Z') {
        user_zonelist_order = ZONELIST_ORDER_ZONE;
    } else {
        pr_warn("Ignoring invalid numa_zonelist_order value:  %s\n", s);
        return -EINVAL;
    }
    return 0;
}

static __init int setup_numa_zonelist_order(char *s)
{
    int ret;

    if (!s)
        return 0;

    ret = __parse_numa_zonelist_order(s);
    if (ret == 0)
        strlcpy(numa_zonelist_order, s, NUMA_ZONELIST_ORDER_LEN);

    return ret;
}
early_param("numa_zonelist_order", setup_numa_zonelist_order);

4 build_all_zonelists_init完成內存域zonelists的初始化

build_all_zonelists函數在經過set_zonelist_order設置了zonelists中結點的組織順序後, 首先檢查了ssytem_state標識. 若是當前系統處於boot階段(SYSTEM_BOOTING), 就開始經過build_all_zonelists_init函數初始化zonelist

build_all_zonelists(pg_data_t *pgdat, struct zone *zone)
{
    /*  設置zonelist中節點和內存域的組織形式
     *  current_zonelist_order變量標識了當前系統的內存組織形式
     *  zonelist_order_name以字符串存儲了系統中內存組織形式的名稱  */
    set_zonelist_order();

    if (system_state == SYSTEM_BOOTING) {
        build_all_zonelists_init();

4.1 system_state系統狀態標識

其中system_state變量是一個系統全局定義的用來表示系統當前運行狀態的枚舉變量, 其定義在include/linux/kernel.h?v=4.7, line 487

/* Values used for system_state */
extern enum system_states
{
    SYSTEM_BOOTING,
    SYSTEM_RUNNING,
    SYSTEM_HALT,
    SYSTEM_POWER_OFF,
    SYSTEM_RESTART,
} system_state;
  • 若是系統system_state是SYSTEM_BOOTING, 則調用build_all_zonelists_init初始化全部的內存結點
  • 不然的話若是定義了冷熱頁CONFIG_MEMORY_HOTPLUG且參數zone(待初始化的內存管理域zone)不爲NULL, 則調用setup_zone_pageset設置冷熱頁
if (system_state == SYSTEM_BOOTING)
{
    build_all_zonelists_init();
}
else
{
#ifdef CONFIG_MEMORY_HOTPLUG
    if (zone)
        setup_zone_pageset(zone);
#endif

4.2 build_all_zonelists_init函數

build_all_zonelists函數在若是當前系統處於boot階段(system_state == SYSTEM_BOOTING), 就開始經過build_all_zonelists_init函數初始化zonelist

build_all_zonelists_init函數定義在mm/page_alloc.c?v=4.7, line 5013

static noinline void __init
build_all_zonelists_init(void)
{
    __build_all_zonelists(NULL);
    mminit_verify_zonelist();
    cpuset_init_current_mems_allowed();
}

build_all_zonelists_init將將全部工做都委託給__build_all_zonelists完成了zonelists的初始化工做, 後者又對系統中的各個NUMA結點分別調用build_zonelists.

函數__build_all_zonelists定義在mm/page_alloc.c?v=4.7, line 4959

/* return values int ....just for stop_machine() */
static int __build_all_zonelists(void *data)
{
    int nid;
    int cpu;
    pg_data_t *self = data;

    /*  ......  */

    for_each_online_node(nid) {
        pg_data_t *pgdat = NODE_DATA(nid);

        build_zonelists(pgdat);
    }
    /*  ......  */
}

for_each_online_node遍歷了系統中全部的活動結點.

因爲UMA系統只有一個結點,build_zonelists只調用了一次, 就對全部的內存建立了內存域列表.

NUMA系統調用該函數的次數等同於結點的數目. 每次調用對一個不一樣結點生成內存域數據

4.3 build_zonelists初始化每一個內存結點的zonelists

build_zonelists(pg_data_t *pgdat)完成了節點pgdat上zonelists的初始化工做, 它創建了備用層次結構zonelists. 因爲UMA和NUMA架構下結點的層次結構有很大的區別, 所以內核分別提供了兩套不一樣的接口.

以下所示

// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7
4571 #ifdef CONFIG_NUMA

4586 static int __parse_numa_zonelist_order(char *s)

4601 static __init int setup_numa_zonelist_order(char *s)

4619 int numa_zonelist_order_handler(struct ctl_table *table, int write,
4620                 void __user *buffer, size_t *length,

4678 static int find_next_best_node(int node, nodemask_t *used_node_mask)

4730 static void build_zonelists_in_node_order(pg_data_t *pgdat, int node)

4746 static void build_thisnode_zonelists(pg_data_t *pgdat)

4765 static void build_zonelists_in_zone_order(pg_data_t *pgdat, int nr_nodes)

4789 #if defined(CONFIG_64BIT)

4795 static int default_zonelist_order(void)
4799 #else
4808 static int default_zonelist_order(void)
4812 #endif /* CONFIG_64BIT */

4822 static void build_zonelists(pg_data_t *pgdat)

4872 #ifdef CONFIG_HAVE_MEMORYLESS_NODES
4879 int local_memory_node(int node)
4888 #endif

4890 #else   /* CONFIG_NUMA */

4897 static void build_zonelists(pg_data_t *pgdat)

4892 static void set_zonelist_order(void)

4931 #endif  /* CONFIG_NUMA */

header 1 | header 2

row 1 col 1 | row 1 col 2
row 2 col 1 | row 2 col 2

函數 NUMA UMA
build_zonelists build_zonelists -=> mm/page_alloc.c?v=4.7, line 4822 build_zonelists -=> mm/page_alloc.c?v=4.7, line 4897
build_zonelists_node -=> mm/page_alloc.c?v=4.7, line 4531

咱們以UMA結構下的build_zonelists爲例, 來說講內核是怎麼初始化備用內存域層次結構的, UMA結構下的build_zonelists函數定義在mm/page_alloc.c?v=4.7, line 4897, 以下所示

node_zonelists的數組元素經過指針操做尋址, 這在C語言中是徹底合法的慣例。實際工做則委託給build_zonelist_node。在調用時,它首先生成本地結點內分配內存時的備用次

內核在build_zonelists中按分配代價從昂貴到低廉的次序, 迭代告終點中全部的內存域. 而在build_zonelists_node中, 則按照分配代價從低廉到昂貴的次序, 迭代了分配代價不低於當前內存域的內存域.

首先咱們來看看build_zonelists_node函數, 該函數定義在mm/page_alloc.c?v=4.7, line 4531

/*
 * Builds allocation fallback zone lists.
 *
 * Add all populated zones of a node to the zonelist.
 */
static int build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist, int nr_zones)
{
    struct zone *zone;
    enum zone_type zone_type = MAX_NR_ZONES;

    do {
        zone_type--;
        zone = pgdat->node_zones + zone_type;
        if (populated_zone(zone)) {
            zoneref_set_zone(zone,
                &zonelist->_zonerefs[nr_zones++]);
            check_highest_zone(zone_type);
        }
    } while (zone_type);

    return nr_zones;
}

備用列表zonelists的各項是藉助於zone_type參數排序的, 該參數指定了最優先選擇哪一個內存域, 該參數的初始值是外層循環的控制變量i.

咱們知道其值多是ZONE_HIGHMEM、ZONE_NORMAL、ZONE_DMA或ZONE_DMA32之一.

nr_zones表示從備用列表中的哪一個位置開始填充新項. 因爲列表中尚沒有項, 所以調用者傳遞了0.

內核在build_zonelists中按分配代價從昂貴到低廉的次序, 迭代告終點中全部的內存域. 而在build_zonelists_node中, 則按照分配代價從低廉到昂貴的次序, 迭代了分配代價不低於當前內存域的內存域.

在build_zonelists_node的每一步中, 都對所選的內存域調用populated_zone, 確認zone->present_pages大於0, 即確認內存域中確實有頁存在. 假若如此, 則將指向zone實例的指針添加到zonelist->zones中的當前位置. 後備列表的當前位置保存在nr_zones.

在每一步結束時, 都將內存域類型zone_type減1.換句話說, 設置爲一個更昂貴的內存域類型. 例如, 若是開始的內存域是ZONE_HIGHMEM, 減1後下一個內存域類型是ZONE_NORMAL.

考慮一個系統, 有內存域ZONE_HIGHMEM、ZONE_NORMAL、ZONE_DMA。在第一次運行build_zonelists_node時, 實際上會執行下列賦值

zonelist->zones[0] = ZONE_HIGHMEM;
zonelist->zones[1] = ZONE_NORMAL;
zonelist->zones[2] = ZONE_DMA;

咱們以某個系統爲例, 圖中示範了一個備用列表在屢次循環中不斷填充的過程. 系統中共有四個結點

其中
A=(NUMA)結點0 0=DMA內存域
B=(NUMA)結點1 1=普通內存域
C=(NUMA)結點2 2=高端內存域
D=(NUMA)結點3

第一步以後, 列表中的分配目標是高端內存, 接下來是第二個結點的普通和DMA內存域.

內核接下來必須確立次序, 以便將系統中其餘結點的內存域按照次序加入到備用列表.

如今咱們回到build_zonelists函數, UMA架構下該函數定義在mm/page_alloc.c?v=4.7, line 4897, 以下所示

static void build_zonelists(pg_data_t *pgdat)
{
    int node, local_node;
    enum zone_type j;
    struct zonelist *zonelist;

    /*  ......  */

    for (node = local_node + 1; node < MAX_NUMNODES; node++) {
        if (!node_online(node))
            continue;
        j = build_zonelists_node(NODE_DATA(node), zonelist, j);
    }
    for (node = 0; node < local_node; node++) {
        if (!node_online(node))
            continue;
        j = build_zonelists_node(NODE_DATA(node), zonelist, j);
    }

    zonelist->_zonerefs[j].zone = NULL;
    zonelist->_zonerefs[j].zone_idx = 0;
}

第一個循環依次迭代大於當前結點編號的全部結點. 在咱們的例子中,有4個結點編號副本爲0、一、二、3,此時只剩下結點3。新的項經過build_zonelists_node被加到備用列表。此時j的做用就體現出來了。在本地結點的備用目標找到以後,該變量的值是3。該值用做新項的起始位置。若是結點3也由3個內存域組成,備用列表在第二個循環以後的狀況如圖3-9的第二步所示

第二個for循環接下來對全部編號小於當前結點的結點生成備用列表項。在咱們的例子中,這些結點的編號爲0和1。 若是這些結點也有3個內存域,則循環完畢以後備用列表的狀況以下圖下半部分所示

備用列表中項的數目通常沒法準確知道,由於系統中不一樣結點的內存域配置可能並不相同。所以 列表的最後一項賦值爲空指針,顯式標記列表結束。

對總數N個結點中的結點m來講,內核生成備用列表時,選擇備用結點的順序老是:m、m+一、 m+二、…、N一、0、一、…、m1。這確保了不過分使用任何結點。例如,對照狀況是:使用一個獨立 於m、不變的備用列表

4.4 setup_pageset初始化per_cpu緩存

前面講解內存管理域zone的時候, 提到了per-CPU緩存, 即冷熱頁. 在組織每一個節點的zonelist的過程當中, setup_pageset初始化了per-CPU緩存(冷熱頁面)

static void setup_pageset(struct per_cpu_pageset *p, unsigned long batch)
{
    pageset_init(p);
    pageset_set_batch(p, batch);
}

在此以前free_area_init_node初始化內存結點的時候, 內核就輸出了冷熱頁的一些信息, 該工做由zone_pcp_init完成, 該函數定義在mm/page_alloc.c?v=4.7, line 5029

static __meminit void zone_pcp_init(struct zone *zone)
{
    /*
     * per cpu subsystem is not up at this point. The following code
     * relies on the ability of the linker to provide the
     * offset of a (static) per cpu variable into the per cpu area.
     */
    zone->pageset = &boot_pageset;

    if (populated_zone(zone))
        printk(KERN_DEBUG "  %s zone: %lu pages, LIFO batch:%u\n",
            zone->name, zone->present_pages,
                     zone_batchsize(zone));
}

5 總結

5.1 start_kernel啓動流程

start_kernel()
    |---->page_address_init()
    |     考慮支持高端內存
    |     業務:初始化page_address_pool鏈表;
    |          將page_address_maps數組元素按索引降序插入
    |          page_address_pool鏈表; 
    |          初始化page_address_htable數組.
    | 
    |---->setup_arch(&command_line);
    |
    |---->setup_per_cpu_areas();
    |     爲per-CPU變量分配空間
    |
    |---->build_all_zonelist()
    |     爲系統中的zone創建後備zone的列表.
    |     全部zone的後備列表都在
    |     pglist_data->node_zonelists[0]中;
    |
    |     期間也對per-CPU變量boot_pageset作了初始化. 
    |
    |---->page_alloc_init()
         |---->hotcpu_notifier(page_alloc_cpu_notifier, 0);
         |     不考慮熱插拔CPU 
         |
    |---->pidhash_init()
    |     詳見下文.
    |     根據低端內存頁數和散列度,分配hash空間,並賦予pid_hash
    |
    |---->vfs_caches_init_early()
          |---->dcache_init_early()
          |     dentry_hashtable空間,d_hash_shift, h_hash_mask賦值;
          |     同pidhash_init();
          |     區別:
          |         散列度變化了(13 - PAGE_SHIFT);
          |         傳入alloc_large_system_hash的最後參數值爲0;
          |
          |---->inode_init_early()
          |     inode_hashtable空間,i_hash_shift, i_hash_mask賦值;
          |     同pidhash_init();
          |     區別:
          |         散列度變化了(14 - PAGE_SHIFT);
          |         傳入alloc_large_system_hash的最後參數值爲0;
          |

5.2 pidhash_init配置高端內存

void pidhash_init(void)
    |---->pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), 
    |         0, 18, HASH_EARLY|HASH_SMALL, &pidhash_shift, NULL, 4096);
    |     根據nr_kernel_pages(低端內存的頁數),分配哈希數組,以及各個哈希
    |     數組元素下的哈希鏈表的空間,原理以下:
    |     number = nr_kernel_pages; 
    |     number >= (18 - PAGE_SHIFT) 根據散列度得到數組元素個數
    |     number = roundup_pow_of_two(number);
    |     pidhash_shift = max{x | 2**x <= number}
    |     size = number * sizeof(*pid_hash);
    |     使用位圖分配器分配size空間,將返回值付給pid_hash;
    |
    |---->pidhash_size = 1 << pidhash_shift;
    |
    |---->for(i = 0; i < pidhash_size; i++)
    |         INIT_HLIST_HEAD(&pid_hash[i]);

5.3 build_all_zonelists初始化每一個內存節點的zonelists

void build_all_zonelists(void)
    |---->set_zonelist_order()
         |---->current_zonelist_order = ZONELIST_ORDER_ZONE;
    |
    |---->__build_all_zonelists(NULL);
    |    Memory不支持熱插拔, 爲每一個zone創建後備的zone,
    |    每一個zone及本身後備的zone,造成zonelist
        |
        |---->pg_data_t *pgdat = NULL;
        |     pgdat = &contig_page_data;(單node)
        |
        |---->build_zonelists(pgdat);
        |     爲每一個zone創建後備zone的列表
            |
            |---->struct zonelist *zonelist = NULL;
            |     enum zone_type j;
            |     zonelist = &pgdat->node_zonelists[0];
            |
            |---->j = build_zonelists_node(pddat, zonelist, 0, MAX_NR_ZONES - 1);
            |     爲pgdat->node_zones[0]創建後備的zone,node_zones[0]後備的zone
            |     存儲在node_zonelist[0]內,對於node_zone[0]的後備zone,其後備的zone
            |     鏈表以下(只考慮UMA體系,並且不考慮ZONE_DMA):
            |     node_zonelist[0]._zonerefs[0].zone = &node_zones[2];
            |     node_zonelist[0]._zonerefs[0].zone_idx = 2;
            |     node_zonelist[0]._zonerefs[1].zone = &node_zones[1];
            |     node_zonelist[0]._zonerefs[1].zone_idx = 1;
            |     node_zonelist[0]._zonerefs[2].zone = &node_zones[0];
            |     node_zonelist[0]._zonerefs[2].zone_idx = 0;
            |
            |     zonelist->_zonerefs[3].zone = NULL;
            |     zonelist->_zonerefs[3].zone_idx = 0;
        |
        |---->build_zonelist_cache(pgdat);
              |---->pdat->node_zonelists[0].zlcache_ptr = NULL;
              |     UMA體系結構
              |
        |---->for_each_possible_cpu(cpu)
        |     setup_pageset(&per_cpu(boot_pageset, cpu), 0);
              |詳見下文
    |---->vm_total_pages = nr_free_pagecache_pages();
    |    業務:得到全部zone中的present_pages總和.
    |
    |---->page_group_by_mobility_disabled = 0;
    |     對於代碼中的判斷條件通常不會成立,由於頁數會最夠多(內存較大)
相關文章
相關標籤/搜索