Linux-2.6.32 NUMA架構之記憶體和排程
http://blog.chinaunix.net/uid-7295895-id-3076420.html
本文將以XLP832通過ICI互連形成的NUMA架構進行分析,主要包括記憶體管理和排程兩方面,參考核心版本2.6.32.9;NUMA架構常見配置選項有:CONFIG_SMP, CONFIG_NUMA, CONFIG_NEED_MULTIPLE_NODES, CONFIG_NODES_SHIFT, CONFIG_SPARSEMEM, CONFIG_CGROUPS, CONFIG_CPUSETS, CONFIG_MIGRATION等。
本文試圖從原理上介紹,儘量避免涉及程式碼的實現細節。
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號之間的對映關係。
首先需要完成1.2節中描述的3個關鍵資訊的初始化。
2.1 CPU和Node的關係
start_kernel()->setup_arch()->prom_init():
#ifdef CONFIG_NUMA
build_node_cpu_map();
#endif
build_node_cpu_map()函式工作:
a) 確定CPU與Node的相互關係,做法很簡單:
#define cpu_to_node(cpu) (cpu >> 5)
#define cpumask_of_node (NODE_CPU_MASK(node)) /* node0:0~31; node1: 32~63 */
說明:XLP832每個節點有1個物理CPU,每個物理CPU有8個核,每個核有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_BASE和ECFG_LIMIT指定(注:但這2個暫存器本身是處於PCIE配置空間之中的)。
PCIE配置空間:
PCIE配置空間與memory-mapped實體地址的對映方式:
XLP832實現了所有裝置都位於虛擬匯流排0上,每個節點有8個裝置,按節點依次排開。
DRAM對映暫存器組:
每個節點都獨立實現有幾組不同型別的DRAM(每組有8個相同型別的)暫存器可以配置DRAM空間對映到實體地址空間中的基址和大小,以及所屬的節點資訊(這些暫存器的值事先會由bootloader設好);這組暫存器位於虛擬匯流排0的裝置0/8/16/24(依次對應每個節點的第一個裝置號)的Function0(每個裝置最多可定義8個Function,每個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”。
bootmem_init()->…->init_bootmem_node()->init_bootmem_core():
每個節點擁有各自的bootmem管理(code&data之前可以為空閒頁面)。
初始化流程最後會設定全域性struct node_active_region early_node_map[]用於初始化Buddy系統,for_each_online_node()遍歷所有線上節點呼叫free_area_init_node()初始化,主要初始化每個zone的大小和所涉及頁面的struct page結構(flags中初始化有所屬zone和node資訊,由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)巨集返回節點i的struct 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個節點2個zone為例(zone從高到低排列, ZONE_DMA0表示節點0的ZONE_DMA,其它類似):
a) Legacy方式
每個節點只排列自己的zone;
b)Node方式
按節點順序依次排列,先排列本地節點的所有zone,再排列其它節點的所有zone。
c) Zone方式
按zone型別從高到低依次排列各節點的同相型別zone。
可通過啟動引數“numa_zonelist_order”來配置zonelist order,核心定義了3種配置:
#define ZONELIST_ORDER_DEFAULT 0 /* 智慧選擇Node或Zone方式 */
#define ZONELIST_ORDER_NODE 1 /* 對應Node方式 */
#define ZONELIST_ORDER_ZONE 2 /* 對應Zone方式 */
預設配置為ZONELIST_ORDER_DEFAULT,由核心通過一個演算法來判斷選擇Node或Zone方式,演算法思想:
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個節點2個ZONE為例,各節點的佈局(如4個XLP832物理CPU級聯)值如下:
上圖中,Node0和Node2的Node Distance為25,Node1和Node3的Node Distance為25,其它的Node Distance為15。
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後,每個節點將關聯2個zonelist:
1) zonelist[0]中存放以Node方式或Zone方式組織的zonelist,包括所有節點的zone;
2) zonelist[1]中只存放本節點的zone即Legacy方式;
zonelist[1]用來實現僅從節點自身zone中的記憶體分配(參考__GFP_THISNODE標誌)。
配置NUMA後對SLAB(本文不涉及SLOB或SLUB)的初始化影響不大,只是在分配一些變數採用類似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[] 中快取每個CPU的SLAB 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->nodeid與numa_node_id()當前節點不相等時),將加入到物件所在節點的alien快取中(如果不存在此alien快取,此物件不會被快取,而是直接釋放給此物件所屬SLAB),否則加入本地快取或共享快取(本地快取溢位且存在shared快取時);當alien快取滿的時候,會呼叫cache_free_alien()搬遷至shared快取中(如果不存在shared快取,直接釋放給SLAB);
slab->nodeid記錄本SLAB記憶體塊(若干個頁面)所在的節點。
示例
例如2個節點,CPU0~31位於Node0,CPU32~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指標”;
2個SLAB節點
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初始化為0;use_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) |
配置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, \ } |
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小於0,node取本地節點號(node = numa_node_id());
2) NODE_DATA(node)得到node對應的struct pglist_data結構,從而得到zonelist[2];
3) 如果gfp_flags含有__GFP_THISNODE標誌,僅在此節點分配記憶體,使用node節
點的Legacy zonelist[1],否則使用其包含所有節點zone的zonelist[0] (見4.2.2.3節);
4) 遍歷確定出來的zonelist結構中包含的每一個符合要求的zone,gfp_flags指定了本
次分配中的最高的zone,如__GFP_HIGHMEM表示最高的zone為ZONE_HIGH;
5) 分配結束。
7.1.2 SLAB顯式分配
kmem_cache_alloc_node(cachep, gfp_flags, node)分配流程:
1) 如果node值為-1,node取本地節點號(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_INTERLEAVE和MPOL_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結構中包含的每一個符合要求的zone,gfp_flags指定了本
次分配中的最高的zone,如__GFP_HIGHMEM表示最高的zone為ZONE_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限制的記憶體策略,後面會介紹。
CPUSET基於CGROUP的框架構建的子系統,有如下特點:
1) 限定一組任務所允許使用的記憶體Node和CPU資源;
2) CPUSET在核心各子系統中新增的檢測程式碼很少,對核心沒有效能影響;
3) CPUSET的限定優先順序高於記憶體策略(針對於Node)和繫結(針對於CPU);
4) 沒有額外實現系統呼叫介面,只能通過/proc檔案系統和使用者互動。
本節只講述CPUSET的使用方法和說明。
8.1建立CPUSET
因為CPUSET只能使用/proc檔案系統訪問,所以第一步就要先mount cpuset檔案系統,配置CONFIG_CGROUPS和CONFIG_CPUSETS後/proc/filesystems中將有這個檔案系統。
CPUSET是分層次的,可以在cpuset檔案系統根目錄是最頂層的CPUSET,可以在其下建立CPUSET子項,建立方式很簡單即建立一個新的目錄。
mount命令:mount nodev –t cpuset /your_dir或mount 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.procs是CGROUPS檔案,為此CPUSET包含的執行緒組tgid集合;
echo 100 > cgroup.procs
3) cpus是CPUSET檔案,表示此CPUSET允許的CPU;
echo 0-8 > cpus
4) mems是CPUSET檔案,表示此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_exclusive和mem_hardwall,為CPUSET檔案,表示記憶體硬牆標記;預設為0,
表示軟牆;有關CPUSET的記憶體硬牆(HardWall)和記憶體軟牆(SoftWall),下文會介紹;
8) memory_spread_page和memory_spread_slab,為CPUSET檔案,設定CPUSET中的
任務PageCache和SLAB(建立時置有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上分配新的頁面(將資料同
步到新頁面),這樣就避免了此任務的非本地節點的記憶體訪問。
上圖為單Node,8個CPU的系統。
1) 頂層CPUSET包含了系統中的所有CPU以及Node,而且是隻讀的,不能更改;
2) 頂層CPUSET包含了系統中的所有任務,可以更改;
3) child為新建立的子CPUSET,子CPUSET的資源不能超過父CPUSET的資源;
4) 新建立的CPUSET的mems和cpus都是空的,使用前必須先初始化;
5) 新增任務:設定tasks和cgroup.procs檔案;
6) 刪除任務:將任務重新新增至其它CPUSET(如頂層)就可以從本CPUSET刪除任務。
8.3 利用CPUSET限定CPU和Node
設定步驟:
1) 在某個父CPUSET中建立子CPUSET;
2) 在子CPUSET目錄下,輸入指定的Node號至mems檔案;
3) 在子CPUSET目錄下,輸入指定的Node號至mems檔案;
4) 在子CPUSET目錄下,設定任務至tasks或group.procs檔案;
5) 還可以設定memory_migrate為1,啟用記憶體頁面的遷移功能。
這樣限定後,此CPUSET中所有的任務都將使用限定的CPU和Node,但畢竟系統中的任務並不能完全孤立,比如還是可能會全域性共享Page Cache,動態庫等資源,因此核心在某些情況下還是可以允許打破這個限制,如果不允許核心打破這個限制,需要設定CPUSET的記憶體硬牆標誌即mem_exclusive或mem_hardwall置1即可;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”的功能。
8個CPU的系統中,系統中存在一個物理域,現需要禁掉CPU4~CPU7的負載均衡,配置步驟為:
1) “mkdir child”在頂層CPUSET中建立子CPUSET,記為child;
2) “echo 0-3 > child/cpus ”(新建CPUSET的sched_load_balance預設是是開啟的);
3) “echo 0 > sched_load_balance”關閉頂層CPUSET的負載均衡。
操作過程見下圖:
由圖可見,CPU4~CPU7的排程域已經不存在了,具體效果是將CPU4~CPU7從負載均衡中隔離出來。
1) /sys/devices/system/node/中記錄有系統中的所有記憶體節點資訊;
2)任務額外關聯一個/proc//numa_smaps檔案資訊;
3) tmpfs可以指定在某個Node上建立;
4) libnuma庫和其numactl小工具可以方便操作NUMA記憶體;
5) … …
2. ULK3
相關文章
- iOS 任務排程器:為 CPU 和記憶體減負iOS記憶體
- 教你如何解決DPDK記憶體大頁在NUMA架構重分配問題記憶體架構
- PostgreSQL-PG的體系架構之記憶體管理(三)SQL架構記憶體
- MySQL整體架構與記憶體結構MySql架構記憶體
- NUMA架構的個人理解架構
- MSSQL記憶體架構及管理SQL記憶體架構
- 嵌入式軟體開發之程式架構設計-任務排程架構
- NUMA 架構與 資料庫架構資料庫
- Oracle體系結構之-記憶體結構Oracle記憶體
- JavaScript之記憶體溢位和記憶體洩漏JavaScript記憶體溢位
- 分散式系統架構之構建你的任務排程中心分散式架構
- Oracle記憶體詳解之一 整體架構Oracle記憶體架構
- 【記憶體洩漏和記憶體溢位】JavaScript之深入淺出理解記憶體洩漏和記憶體溢位記憶體溢位JavaScript
- JVM記憶體結構、Java記憶體模型和Java物件模型JVM記憶體Java模型物件
- JVM之記憶體結構詳解JVM記憶體
- Oracle體系結構之記憶體結構(SGA、PGA)Oracle記憶體
- Linux 核心 101:NUMA架構Linux架構
- NUMA架構下oracle的oom異常架構OracleOOM
- 淺談JVM記憶體結構 和 Java記憶體模型 和 Java物件模型JVM記憶體Java模型物件
- 讀書筆記】《PostgreSQL指南-內幕探索》-2.程式和記憶體架構筆記SQL記憶體架構
- OCP課程54:管理II之管理記憶體記憶體
- Go記憶體架構,一個有趣的問題Go記憶體架構
- 系統架構設計之-任務排程系統的設計架構
- JVM讀書筆記之java記憶體結構JVM筆記Java記憶體
- Oracle體系結構:記憶體結構和程式結構(轉)Oracle記憶體
- NUMA架構介紹及優缺點分析架構
- 叢集排程框架的架構演進之路框架架構
- 記憶體結構記憶體
- 架構設計 | 快取管理模式,監控和記憶體回收策略架構快取模式記憶體
- 實體記憶體和虛擬記憶體記憶體
- 架構之:軟體架構漫談架構
- STM32記憶體結構介紹和FreeRTOS記憶體分配技巧記憶體
- Kubernetes全棧架構師(資源排程上)--學習筆記全棧架構筆記
- Kubernetes全棧架構師(資源排程下)--學習筆記全棧架構筆記
- 分析oc物件的記憶體結構及其建立過程物件記憶體
- 深入理解 JVM 之 JVM 記憶體結構JVM記憶體
- JVM系列之Java記憶體結構詳解JVMJava記憶體
- numa 架構下mysql可能遭遇的swap問題架構MySql