Linux-2.6.32 NUMA架構之內存和調度



Linux-2.6.32 NUMA架構之內存和調度

 

本文將以XLP832通過ICI互連形成的NUMA架構進行分析,主要包括內存管理和調度兩方面,參考內核版本2.6.32.9NUMA架構常見配置選項有:CONFIG_SMP, CONFIG_NUMA, CONFIG_NEED_MULTIPLE_NODES, CONFIG_NODES_SHIFT, CONFIG_SPARSEMEM, CONFIG_CGROUPS, CONFIG_CPUSETS, CONFIG_MIGRATION等。

本文試圖從原理上介紹,儘量避免涉及代碼的實現細節

 

1 NUMA架構簡介

NUMA(Non Uniform Memory Access)即非一致內存訪問架構,市面上主要有X86_64(JASPER)MIPS64(XLP)體系。

1.1 概念

NUMA具有多個節點(Node),每個節點可以擁有多個CPU(每個CPU可以具有多個核或線程),節點內使用共有的內存控制器,因此節點的所有內存對於本節點的所有CPU都是等同的,而對於其它節點中的所有CPU都是不同的。節點可分爲本地節點(Local Node)、鄰居節點(Neighbour Node)和遠端節點(Remote Node)三種類型。

       本地節點:對於某個節點中的所有CPU,此節點稱爲本地節點;

鄰居節點:與本地節點相鄰的節點稱爲鄰居節點;

遠端節點:非本地節點或鄰居節點的節點,稱爲遠端節點。

鄰居節點和遠端節點,稱作非本地節點(Off Node)

CPU訪問不同類型節點內存的速度是不相同的:本地節點>鄰居節點>遠端節點。訪問本地節點的速度最快,訪問遠端節點的速度最慢,即訪問速度與節點的距離有關,距離越遠訪問速度越慢,此距離稱作Node Distance

常用的NUMA系統中:硬件設計已保證系統中所有的Cache是一致的(Cache Coherent, ccNUMA);不同類型節點間的Cache同步時間不一樣,會導致資源競爭不公平,對於某些特殊的應用,可以考慮使用FIFO Spinlock保證公平性。

1.2 關鍵信息

1) 物理內存區域與Node號之間的映射關係;

2) Node之間的Node Distance

3) 邏輯CPU號與Node號之間的映射關係。

 

2 XLP832 NUMA初始化

首先需要完成1.2節中描述的3個關鍵信息的初始化。

2.1 CPUNode的關係

start_kernel()->setup_arch()->prom_init():

#ifdef CONFIG_NUMA

       build_node_cpu_map();

#endif

 

build_node_cpu_map()函數工作:

a) 確定CPUNode的相互關係,做法很簡單:

#define cpu_to_node(cpu)       (cpu >> 5)

 #define cpumask_of_node    (NODE_CPU_MASK(node)) /* node0:0~31; node1: 32~63 */

 

說明:XLP832每個節點有1個物理CPU,每個物理CPU8個核,每個核有4個超線

程,因此每個節點對應32個邏輯CPU,按節點依次展開。另外,實際物理存在的CPU

數目是通過DTB傳遞給內核的;numa_node_id()可以獲取當前CPU所處的Node號。

 

b) 設置每個物理存在的節點的在線狀態,具體是通過node_set_online()函數來設置全局變量

nodemask_t node_states[];

   這樣,類似於CPU號,Node號也就具有如下功能宏:

   for_each_node(node);

for_each_online_node(node);

   詳細可參考include/linux/nodemask.h

2.2 Node Distance確立

作用:建立buddy時用,可以依此來構建zonelist,以及zone relaim(zone_reclaim_mode)使

用,詳見後面的4.2.2節。

2.3 內存區域與Node的關係

start_kernel()->setup_arch()->arch_mem_init->bootmem_init()->nlm_numa_bootmem_init():

nlm_get_dram_mapping();

XLP832上電後的默認memory-mapped物理地址空間分佈:

其中PCIE配置空間映射地址範圍爲[0x1800_0000, 0x1BFF_FFFF],由寄存器ECFG_BASEECFG_LIMIT指定(注:但這2個寄存器本身是處於PCIE配置空間之中的)

 

PCIE配置空間:

PCIE配置空間與memory-mapped物理地址的映射方式:

XLP832實現了所有設備都位於虛擬總線0上,每個節點有8個設備,按節點依次排開。

DRAM映射寄存器組:

每個節點都獨立實現有幾組不同類型的DRAM(每組有8個相同類型的)寄存器可以配置DRAM空間映射到物理地址空間中的基址和大小,以及所屬的節點信息(這些寄存器的值事先會由bootloader設好);這組寄存器位於虛擬總線0的設備0/8/16/24(依次對應每個節點的第一個設備號)Function0(每個設備最多可定義8Function,每個Function有着獨立的PCIE 4KB的配置空間)PCIE配置空間中(這個配置空間實現的是DRAM/Bridge控制器)

 

本小節涉及到的3組不同類型的寄存器(注:按索引對應即DRAM_BAR,DRAM_LIMIT DRAM_NODE_TRANSLATION描述一個內存區域屬性)

 

第一組(DRAM空間映射物理空間基址)

DRAM_BAR0: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x54

DRAM_BAR1: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x55

DRAM_BAR2: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x56

DRAM_BAR3: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x57

DRAM_BAR4: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x58

DRAM_BAR5: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x59

DRAM_BAR6: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x5A

DRAM_BAR7: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x5B

 

第二組(DRAM空間映射物理空間長度)

DRAM_LIMIT0: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x5C

DRAM_LIMIT1: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x5D

DRAM_LIMIT2: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x5E

DRAM_LIMIT3: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x5F

DRAM_LIMIT4: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x60

DRAM_LIMIT5: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x61

DRAM_LIMIT6: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x62

DRAM_LIMIT7: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x63

 

第三組(節點相關)

DRAM_NODE_TRANSLATION0: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x64

DRAM_NODE_TRANSLATION1: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x65

DRAM_NODE_TRANSLATION2: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x66

DRAM_NODE_TRANSLATION3: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x67

DRAM_NODE_TRANSLATION4: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x68

DRAM_NODE_TRANSLATION5: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x69

DRAM_NODE_TRANSLATION6: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x6A

DRAM_NODE_TRANSLATION7: PCIe Bus 0, Device 0/8/16/24, Function 0, Register 0x6B

 

根據上述的PCIE配置空間memory-mapped映射方式便可直接獲取寄存器中的值,就可以建立各個節點中的所有內存區域(最多8個區域)信息。關於這些寄存器的使用可以參考“XLP® Processor Family Programming Reference Manual”的“Chapter 7 Memory and I/O Subsystem”。

 

3 Bootmem初始化

bootmem_init()->…->init_bootmem_node()->init_bootmem_core():

每個節點擁有各自的bootmem管理(code&data之前可以爲空閒頁面)。

 

 

4 Buddy初始化

初始化流程最後會設置全局struct node_active_region early_node_map[]用於初始化Buddy系統,for_each_online_node()遍歷所有在線節點調用free_area_init_node()初始化,主要初始化每個zone的大小和所涉及頁面的struct page結構(flags中初始化有所屬zonenode信息,由set_page_links()函數設置)等。

4.1 NUMA帶來的變化

1) pglist_data

typedef struct pglist_data {

       struct zone node_zones[MAX_NR_ZONES];

       struct zonelist node_zonelists[MAX_ZONELISTS];

       int nr_zones;

       struct bootmem_data *bdata;

       unsigned long node_start_pfn;

       unsigned long node_present_pages; /* total number of physical pages */

       unsigned long node_spanned_pages; /* total size of physical page

range, including holes */

       int node_id;

       wait_queue_head_t kswapd_wait;

       struct task_struct *kswapd;

       int kswapd_max_order;

} pg_data_t;

a)上節的bootmem結構的描述信息存放在NODE_DATA(node)-> bdata中;NODE_DATA(i)宏返回節點istruct pglist_data結構,需要在架構相關的mmzone.h中實現;

b) #define MAX_ZONELISTS 2,請參考後面的“zonelist初始化”。

2) zone

struct zone {

#ifdef CONFIG_NUMA

       int node;

       /*

        * zone reclaim becomes active if more unmapped pages exist.

        */

       unsigned long        min_unmapped_pages;

       unsigned long        min_slab_pages;

       struct per_cpu_pageset   *pageset[NR_CPUS];

#else

… …

};

 

a)最終調用kmalloc_node()pageset成員在每個CPU的對應的內存節點分配內存;

b)min_unmapped_pages 對應/proc/sys/vm/min_unmapped_ratio,默認值爲1

  min_slab_pages對應/proc/sys/vm/min_slab_ratio,默認值爲5

  作用:當剩餘可回收的非文件映射和SLAB頁面超過這2個值時,才激活當前zone回收;

c) 增加了zone對應的節點號。

4.2 zonelist初始化

本節講述zonelist的構建方式,實現位於start_kernel()->build_all_zonelists()中,zonelist的組織方式非常關鍵(這一點與以前的2.6.21內核版本不一樣,2.6.32組織得更清晰)

4.2.1 zonelist order

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

a) Legacy方式

       

       每個節點只排列自己的zone

        b)Node方式

按節點順序依次排列,先排列本地節點的所有zone,再排列其它節點的所有zone

 

c) Zone方式

zone類型從高到低依次排列各節點的同相類型zone

 

可通過啓動參數“numa_zonelist_order”來配置zonelist order,內核定義了3種配置:

#define ZONELIST_ORDER_DEFAULT  0 /* 智能選擇NodeZone方式 */

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

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

 

默認配置爲ZONELIST_ORDER_DEFAULT,由內核通過一個算法來判斷選擇NodeZone方式,算法思想:

a) alloc_pages()分配內存是按照ZONE從高到低的順序進行的,例如上節“Node方式”的圖示中,從ZONE_NORMAL0中分配內存時,ZONE_NORMAL0中無內存時將落入較低的ZONE_DMA0中分配,這樣當ZONE_DMA0比較小的時候,很容易將ZONE_DMA0中的內存耗光,這樣是很不理智的,因爲還有更好的分配方式即從ZONE_NORMAL1中分配;

b) 內核會檢測各ZONE的頁面數來選擇Zone組織方式,當ZONE_DMA很小時,選擇ZONELIST_ORDER_DEFAULT時,內核將傾向於選擇ZONELIST_ORDER_ZONE方式,否則選擇ZONELIST_ORDER_NODE方式。

 

另外,可以通過/proc/sys/vm/numa_zonelist_order動態改變zonelist order的分配方式。

4.2.2 Node Distance

上節中的例子是以2個節點爲例,如果有>2個節點存在,就需要考慮不同節點間的距離來安排節點,例如以4個節點2ZONE爲例,各節點的佈局(4XLP832物理CPU級聯)值如下:

上圖中,Node0Node2Node Distance25Node1Node3Node Distance25,其它的Node Distance15

4.2.2.1 優先進行Zone Reclaim

另外,當Node Distance超過20的時候,內核會在某個zone分配內存不足的時候,提前激活本zone的內存回收工作,由全局變量zone_reclaim_mode控制,build_zonelists()中:

/*

               * If another node is sufficiently far away then it is better

               * to reclaim pages in a zone before going off node.

               */

              if (distance > RECLAIM_DISTANCE)

                     zone_reclaim_mode = 1;

通過/proc/sys/vm/zone_reclaim_mode可以動態調整zone_reclaim_mode的值來控制回收模式,含義如下:

#define RECLAIM_OFF    0

#define RECLAIM_ZONE  (1<<0)     /* Run shrink_inactive_list on the zone */

#define RECLAIM_WRITE (1<<1)     /* Writeout pages during reclaim */

#define RECLAIM_SWAP  (1<<2)     /* Swap pages out during reclaim */

4.2.2.2 影響zonelist方式

採用Node方式組織的zonelist爲:

 即各節點按照與本節點的Node Distance距離大小來排序,以達到更優的內存分配。

4.2.3 zonelist[2]

配置NUMA後,每個節點將關聯2zonelist

1) zonelist[0]中存放以Node方式或Zone方式組織的zonelist,包括所有節點的zone

2) zonelist[1]中只存放本節點的zoneLegacy方式;

zonelist[1]用來實現僅從節點自身zone中的內存分配(參考__GFP_THISNODE標誌)

 

5 SLAB初始化

配置NUMA後對SLAB(本文不涉及SLOBSLUB)的初始化影響不大,只是在分配一些變量採用類似Buddy系統的per_cpu_pageset(單面頁緩存)CPU本地節點進行內存分配。

5.1 NUMA帶來的變化

struct kmem_cache {

struct array_cache *array[NR_CPUS];

… …

struct kmem_list3 *nodelists[MAX_NUMNODES];

};

 

struct kmem_list3 {      

… …

struct array_cache *shared;    /* shared per node */

struct array_cache **alien;    /* on other nodes */

… …

};

 

struct slab {

    … …

       unsigned short nodeid;

    … …

};

  

上面的4種類型的指針變量在SLAB初始化完畢後將改用kmalloc_node()分配的內存。具體實現請參考enable_cpucache(),此函數最終調用alloc_arraycache()alloc_kmemlist()來分配這些變量代表的空間。

       nodelists[MAX_NUMNODES]存放的是所有節點對應的相關數據,本文稱作SLAB節點。每個節點擁有各自的數據;

 

注:有些非NUMA系統比如非連續內存系統可能根據不同的內存區域定義多個節點(實際上Node Distance都是0即物理內存訪問速度相同),所以這些變量並沒有採用CONFIG_NUMA宏來控制,本文暫稱爲NUMA帶來的變化。

5.2 SLAB緩存

配置NUMA後,SLAB將有三種類型的緩存:本地緩存(當前CPU的緩存),共享緩存(節點內的緩存)和外部緩存(節點間的緩存)

SLAB系統分配對象時,先從本地緩存中查找,如果本地緩存爲空,則將共享緩存中的緩存搬運本地緩存中,重新從本地緩存中分配;如果共享緩存爲空,則從SLAB中進行分配;如果SLAB中已經無空閒對象,則分配新的SLAB後重新分配本地緩存。

SLAB系統釋放對象時,先不歸還給SLAB (簡化分配流程,也可充分利用CPU Cache),如果是同節點的SLAB對象先放入本地緩存中,如果本地緩存溢出(滿),則轉移一部分(batch爲單位)至共享緩存中;如果是跨節點釋放,則先放入外部緩存中,如果外部緩存溢出,則轉移一部分至共享緩存中,以供後續分配時使用;如果共享緩存溢出,則調用free_block()函數釋放溢出的緩存對象。

關於這三種類型緩存的大小以及參數設置,不在本文的討論範圍。

 

本地緩存

kmem_cache-> array[] 中緩存每個CPUSLAB cached objects

共享緩存

kmem_list3[]->shared(如果存在shared緩存)中緩存與當前CPU同節點的所有CPU (XLP832 NUMA系統中的Node0包含爲CPU0~CPU31) 本地緩存溢出的緩存,詳細實現請參考cache_flusharray();另外,大對象SLAB不存在共享緩存。

外部緩存

kmem_list3[]->alien中存放其它節點的SLAB cached objects,當在某個節點上分配的SLAB object在另外一個節點上被釋放的時候(slab->nodeidnuma_node_id()當前節點不相等時),將加入到對象所在節點的alien緩存中(如果不存在此alien緩存,此對象不會被緩存,而是直接釋放給此對象所屬SLAB),否則加入本地緩存或共享緩存(本地緩存溢出且存在shared緩存時);當alien緩存滿的時候,會調用cache_free_alien()搬遷至shared緩存中(如果不存在shared緩存,直接釋放給SLAB)

slab->nodeid記錄本SLAB內存塊(若干個頁面)所在的節點。

 

示例

例如2個節點,CPU0~31位於Node0CPU32~CPU63位於Node1

64(依次對應於CPU0~CPU63)本地緩存

kmem_cache->array[0~31]:Node0分配“array_cache結構+cached Objs指針”;

kmem_cache->array[32~63]:Node1分配“array_cache結構+cached Objs指針”;

 

2SLAB節點

kmem_cache->nodelists[0]:Node0分配“kmem_list3結構”;

kmem_cache->nodelists[1]:Node1分配“kmem_list3結構”;

 

SLAB節點0(CPU0~CPU31)共享緩存和外部緩存alien[1]

kmem_cache->nodelists[0]->shared:Node0分配“array_cache結構+cached Objs指針”;

kmem_cache->nodelists[0]->alien:Node0分配“節點數*sizeof(void*)”;

kmem_cache->nodelists[0]->alien[0]:置爲NULL

kmem_cache->nodelists[0]->alien[1]:Node0分配“array_cache結構+cached Objs指針”;

SLAB節點1(CPU32~CPU63)共享緩存和外部緩存alien[0]

kmem_cache->nodelists[1]->shared:Node1分配“array_cache結構+cached Objs指針”;

kmem_cache->nodelists[1]->alien:Node1分配“節點數*sizeof(void*)”;

kmem_cache->nodelists[1]->alien[0]:Node1分配“array_cache結構+cached Objs指針”;

kmem_cache->nodelists[1]->alien[1]:置爲NULL

 

另外,可以用內核啓動參數“use_alien_caches”來控制是否開啓alien緩存:默認值爲1,當系統中的節點數目爲1時,use_alien_caches初始化爲0use_alien_caches目的是用於某些多節點非連續內存(訪問速度相同)的非NUMA系統。

 

由上可見,隨着節點個數的增加,SLAB明顯會開銷越來越多的緩存,這也是SLUB涎生的一個重要原因。

5.3 __GFP_THISNODE

SLAB在某個節點創建新的SLAB時,都會置__GFP_THISNODE標記向Buddy系統提交頁面申請,Buddy系統中看到此標記,選用申請節點的Legacy zonelist[1],僅從申請節點的zone中分配內存,並且不會走內存不足流程,也不會重試或告警,這一點需要引起注意。

 

SLAB在申請頁面的時候會置GFP_THISNODE標記後調用cache_grow()來增長SLAB

GFP_THISNODE定義如下:

#ifdef CONFIG_NUMA

#define GFP_THISNODE     (__GFP_THISNODE | __GFP_NOWARN | __GFP_NORETRY)

 
 
6 調度初始化

配置NUMA後負載均衡會多一層NUMA調度域,根據需要在topology.h中定義,示例:

#define SD_NODE_INIT (struct sched_domain) {             \

       .parent                  = NULL,               \

       .child                    = NULL,               \

       .groups                  = NULL,               \

       .min_interval         = 8,               \

       .max_interval         = 32,                     \

       .busy_factor           = 32,                     \

       .imbalance_pct              = 125,                   \

       .cache_nice_tries    = 1,               \

       .flags                    = SD_LOAD_BALANCE |    \

                              SD_BALANCE_EXEC,    \

       .last_balance          = jiffies,         \

       .balance_interval    = 1,               \

       .nr_balance_failed  = 0,               \

}

   
    順便提一下,2.6.32對於實時任務不走負載均衡流程,採用了全局優先級調度的思想,保證實時任務的及時運行;這樣的做法同時也解決了低版本內核在處理同一個邏輯CPU上相同最高優先級實時任務的負載均衡的時延。
 
7 NUMA內存分配

Zonelist[2]組織方式在NUMA內存分配過程中起着至關重要的作用,它決定了整個頁面在不同節點間的申請順序和流程。

7.1顯式分配

       顯式分配即指定節點的分配函數,此類基礎分配函數主要有2個:Buddy系統的   alloc_pages_node()SLAB系統的kmem_cache_alloc_node(),其它的函數都可以從這2個派生出來。

例如,kmalloc_node()最終調用kmem_cache_alloc_node()進行分配。

7.1.1 Buddy顯式分配

alloc_pages_node(node, gfp_flags, order)分配流程:

1) 如果node小於0node取本地節點號(node = numa_node_id())

2) NODE_DATA(node)得到node對應的struct pglist_data結構,從而得到zonelist[2]

3) 如果gfp_flags含有__GFP_THISNODE標誌,僅在此節點分配內存,使用node

點的Legacy zonelist[1],否則使用其包含所有節點zonezonelist[0] (4.2.2.3)

4) 遍歷確定出來的zonelist結構中包含的每一個符合要求的zonegfp_flags指定了本

次分配中的最高的zone,如__GFP_HIGHMEM表示最高的zoneZONE_HIGH

5) 分配結束。

7.1.2 SLAB顯式分配

kmem_cache_alloc_node(cachep, gfp_flags, node)分配流程:

1) 如果node值爲-1node取本地節點號(node = numa_node_id())

2) 如果node < -1,則執行fall back行爲,此行爲與用戶策略有關,有點類似隱式分配:

a) 根據用戶策略(包括CPUSET和內存策略)依次選取節點,根據gfp_flags選取合適

zonelist進行分配;

b) 如果內存不足分配失敗,則跳過內存策略直接進行隱式Buddy頁面分配(仍受

CPUSET的限定,關於CPUSET和內存策略後面會介紹),最終構建成新的SLAB

並完成本次分配;轉5)

3) 如果node是正常節點號,則先在node節點上根據gfp_flags選取合適的zonelist

行分配;

4) 如果3)node節點內存不足分配失敗,轉2) a)執行fall back行爲。

5) 分配結束。

 

注:fall back行爲指的是某個節點上內存不足時會落到此節點的zonelist[0]中定義的其它節點zone分配。

7.1.3 設備驅動

配置CONFIG_NUMA後,設備會關聯一個NUMA節點信息,struct device結構中會多一個numa_node字段記錄本設備所在的節點,這個結構嵌套在各種類型的驅動中,如struct net_device結構。

struct device {

    … …

#ifdef CONFIG_NUMA

       int          numa_node;    /* NUMA node this device is close to */

#endif

    … …

}

 

__netdev_alloc_skb()的實現:

struct sk_buff *__netdev_alloc_skb(struct net_device *dev,

              unsigned int length, gfp_t gfp_mask)

{

       int node = dev->dev.parent ? dev_to_node(dev->dev.parent) : -1;

       struct sk_buff *skb;

 

       skb = __alloc_skb(length + NET_SKB_PAD, gfp_mask, 0, node);

       if (likely(skb)) {

              skb_reserve(skb, NET_SKB_PAD);

              skb->dev = dev;

       }

       return skb;

}

 

__alloc_skb()最終調用kmem_cache_alloc_node()kmalloc_node()在此node上分配內存。

7.2隱式分配和內存策略

隱式分配即不指定節點的分配函數,此類基礎分配函數主要有2個:Buddy系統的   alloc_pages()SLAB系統的kmem_cache_alloc(),其它的函數都可以從這2個派生出來。

    隱式分配涉及到NUMA內存策略(Memory Policy),內核定義了四種內存策略。

注:隱式分配還涉及到CPUSET,本文後面會介紹。

7.2.1 內存策略

內核mm/mempolicy.c中實現了NUMA內存的四種內存分配策略:MPOL_DEFAULT, MPOL_PREFERRED, MPOL_INTERLEAVEMPOL_BIND,內存策略會從父進程繼承。

 

MPOL_DEFAULT使用本地節點的zonelist

MPOL_PREFERRED使用指定節點的zonelist

MPOL_BIND 設置一個節點集合,只能從這個集合中節點的zone申請內存:

1)__GFP_THISNODE申請標記,使用本地節點的zonelist[0]

2)置有__GFP_THISNODE申請標記,如果本地節點:

a)在集合中,使用本地節點的zonelist[1]

b)不在集合中,使用集合中最小節點號的zonelist[1]              

MPOL_INTERLEAVE採用Round-Robin方式從設定的節點集合中選出某個

節點,使用此節點的zonelist

 

內核實現的內存策略,用struct mempolicy結構來描述:

struct mempolicy {

       atomic_t refcnt;

       unsigned short mode;    /* See MPOL_* above */

       unsigned short flags;      /* See set_mempolicy() MPOL_F_* above */

       union {

              short              preferred_node; /* preferred */

              nodemask_t    nodes;          /* interleave/bind */

              /* undefined for default */

       } v;

       union {

              nodemask_t cpuset_mems_allowed;      /* relative to these nodes */

              nodemask_t user_nodemask;  /* nodemask passed by user */

       } w;

};

    

成員mode表示使用四種分配策略中的哪一種,聯合體v根據不同的分配策略記錄相應的分配信息。

另外,MPOL_PREFERRED策略有一種特殊的模式,當其flags置上MPOL_F_LOCAL標誌後,將等同於MPOL_DEFAULT策略,內核默認使用此種策略,見全局變量default_policy

 

內存策略涉及的分配函數有2個:alloc_pages_current()alloc_page_vma(),可以分別爲不同任務以及任務的不同VMA設置內存策略。

7.2.2 Buddy隱式分配

以默認的NUMA內存策略爲例講解,alloc_pages(gfp_flags, order)分配流程:

1) 得到本地節點對應的struct pglist_data結構,從而得到zonelist[2]

2) 如果gfp_flags含有__GFP_THISNODE標誌,僅在此節點分配內存即使用本地節

點的Legacy zonelist[1],否則使用zonelist[0] (4.2.2.3)

3) 遍歷確定出來的zonelist結構中包含的每一個符合要求的zonegfp_flags指定了本

次分配中的最高的zone,如__GFP_HIGHMEM表示最高的zoneZONE_HIGH

4) 分配結束。

7.2.3 SLAB隱式分配

以默認的NUMA內存策略爲例講解,kmem_cache_alloc(cachep, gfp_flags)分配流程:

1) 調用____cache_alloc()函數在本地節點local_node分配,此函數無fall back行爲;

2) 如果1)中本地節點內存不足分配失敗,調用____cache_alloc_node(cachep, gfp_flags,

local_node)再次嘗試在本地節點分配,如果還失敗此函數會進行fall back行爲;

3) 分配結束。

7.3小結

上文提到的所有的內存分配函數都允許fall back行爲,但有2種情況例外:

1) __GFP_THISNODE分配標記限制了只能從某一個節點上分配內存;

2) MPOL_BIND策略,限制了只能從一個節點集合中的節點上分配內存;

   (gfp_zone(gfp_flags) < policy_zone的情況,MPOL_BIND不限制節點)

 

注:還有一種情況,CPUSET限制的內存策略,後面會介紹。

 

8 CPUSET

CPUSET基於CGROUP的框架構建的子系統,有如下特點:

1) 限定一組任務所允許使用的內存NodeCPU資源;

2) CPUSET在內核各子系統中添加的檢測代碼很少,對內核沒有性能影響;

3) CPUSET的限定優先級高於內存策略(針對於Node)和綁定(針對於CPU)

4) 沒有額外實現系統調用接口,只能通過/proc文件系統和用戶交互。

 

本節只講述CPUSET的使用方法和說明。

8.1創建CPUSET

因爲CPUSET只能使用/proc文件系統訪問,所以第一步就要先mount cpuset文件系統,配置CONFIG_CGROUPSCONFIG_CPUSETS/proc/filesystems中將有這個文件系統。

CPUSET是分層次的,可以在cpuset文件系統根目錄是最頂層的CPUSET,可以在其下創建CPUSET子項,創建方式很簡單即創建一個新的目錄。

 

mount命令:mount nodev –t cpuset /your_dirmount nodev –t cgroup –o cpuset /your_dir

 

Mount成功後,進入mount目錄,這個就是最頂層的CPUSET(top_cpuset),下面附一個演示例子:

8.2 CPUSET文件

    介紹幾個重要的CPUSET文件:

1) tasks,實際上是CGROUPS文件,爲此CPUSET包含的線程pid集合;

   echo 100 > tasks

 

2) cgroup.procsCGROUPS文件,爲此CPUSET包含的線程組tgid集合;

   echo 100 > cgroup.procs

 

3) cpusCPUSET文件,表示此CPUSET允許的CPU

  echo 0-8 > cpus

 

4) memsCPUSET文件,表示此CPUSET允許的內存節點;

  echo 0-1 > mems  (對應於struct task_struct中的mems_allowed字段)

 

5) sched_load_balance,爲CPUSET文件,設置cpus集合的CPU是否參與負載均衡;

  echo 0 > sched_load_balance (禁止負載均衡);默認值爲1表示開啓負載均衡;

 

6) sched_relax_domain_level,爲CPUSET文件,數值代表某個調度域級別,大於此級

別的調度域層次將禁用閒時均衡和喚醒均衡,而其餘級別的調度域都開啓;

也可以通過啓動參數“relax_domain_level”設置,其值含義:

-1 : 無效果,此爲默認值

   0 - 設置此值會禁用所有調度域的閒時均衡和喚醒均衡

   1 - 超線程域

   2 - 核域

   3 - 物理域

   4 - NUMA

   5 - ALLNODES模式的NUMA

 

7) mem_exclusivemem_hardwall,爲CPUSET文件,表示內存硬牆標記;默認爲0

表示軟牆;有關CPUSET的內存硬牆(HardWall)和內存軟牆(SoftWall),下文會介紹;

 

8) memory_spread_pagememory_spread_slab,爲CPUSET文件,設定CPUSET中的

任務PageCacheSLAB(創建時置有SLAB_MEM_SPREAD)Round-Robin方式使

用內存節點(類似於MPOL_INTERLEAVE);默認爲0,表示未開啓;struct task_struct

結構中增加成員cpuset_mem_spread_rotor記錄下次使用的節點號;

 

    9) memory_migrate,爲CPUSET文件,表明開啓此CPUSET的內存遷移,默認爲0

      當一個任務從一個CPUSET1(mems值爲0)遷移至另一個CPUSET2(mems值爲1)

時候,此任務在節點0上分配的頁面內容將遷移至節點1上分配新的頁面(將數據同

步到新頁面),這樣就避免了此任務的非本地節點的內存訪問。

上圖爲單Node8CPU的系統。

1) 頂層CPUSET包含了系統中的所有CPU以及Node,而且是隻讀的,不能更改;

2) 頂層CPUSET包含了系統中的所有任務,可以更改;

3) child爲新創建的子CPUSET,子CPUSET的資源不能超過父CPUSET的資源;

4) 新創建的CPUSETmemscpus都是空的,使用前必須先初始化;

5) 添加任務:設置taskscgroup.procs文件;

6) 刪除任務:將任務重新添加至其它CPUSET(如頂層)就可以從本CPUSET刪除任務。

8.3 利用CPUSET限定CPUNode

    設置步驟:

1) 在某個父CPUSET中創建子CPUSET

2) 在子CPUSET目錄下,輸入指定的Node號至mems文件;

3) 在子CPUSET目錄下,輸入指定的Node號至mems文件;

4) 在子CPUSET目錄下,設定任務至tasksgroup.procs文件;

5) 還可以設置memory_migrate1,激活內存頁面的遷移功能。

 

這樣限定後,此CPUSET中所有的任務都將使用限定的CPUNode,但畢竟系統中的任務並不能完全孤立,比如還是可能會全局共享Page Cache,動態庫等資源,因此內核在某些情況下還是可以允許打破這個限制,如果不允許內核打破這個限制,需要設定CPUSET的內存硬牆標誌即mem_exclusivemem_hardwall1即可;CPUSET默認是軟牆。

 

硬軟牆用於Buddy系統的頁面分配,優先級高於內存策略,請參考內核函數:

cpuset_zone_allowed_hardwall()cpuset_zone_allowed_softwall()

 

另外,當內核分不到內存將導致Oops的時候,CPUSET所有規則將被打破,畢竟一個系統的正常運行纔是最重要的:

1) __GFP_THISNODE標記分配內存的時候(通常是SLAB系統)

2) 中斷中分配內存的時候;

3) 任務置有TIF_MEMDIE標記即被內核OOM殺死的任務。

8.4 利用CPUSET動態改變調度域結構

利用sched_load_balance文件可以禁用掉某些CPU的負載均衡,同時重新構建調度域,此功能類似啓動參數“isolcpus”的功能。

 

8CPU的系統中,系統中存在一個物理域,現需要禁掉CPU4~CPU7的負載均衡,配置步驟爲:

1) mkdir child”在頂層CPUSET中創建子CPUSET,記爲child

2) echo 0-3 > child/cpus (新建CPUSETsched_load_balance默認是是打開的)

3) echo 0 > sched_load_balance”關閉頂層CPUSET的負載均衡。

 

操作過程見下圖:

由圖可見,CPU4~CPU7的調度域已經不存在了,具體效果是將CPU4~CPU7從負載均衡中隔離出來。

 

9 NUMA雜項

1) /sys/devices/system/node/中記錄有系統中的所有內存節點信息;

2)任務額外關聯一個/proc//numa_smaps文件信息;

3) tmpfs可以指定在某個Node上創建;

4) libnuma庫和其numactl小工具可以方便操作NUMA內存;

5) … …

發佈了17 篇原創文章 · 獲贊 25 · 訪問量 60萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章