<kernel v5.10>
NUMA -1- (ARM64 초기화)
arm64_numa_init()
arch/arm64/mm/numa.c
/** * arm64_numa_init - Initialize NUMA * * Try each configured NUMA initialization method until one succeeds. The * last fallback is dummy single node config encomapssing whole memory. */
void __init arm64_numa_init(void) { if (!numa_off) { if (!acpi_disabled && !numa_init(arm64_acpi_numa_init)) return; if (acpi_disabled && !numa_init(of_numa_init)) return; } numa_init(dummy_numa_init); }
ARM64 시스템에서 ACPI 테이블 또는 디바이스 트리를 통해 NUMA 시스템 구성을 시도한다. 그러한 구성이 없으면 노드가 한 개인 dummy 누마 초기화를 수행한다.
numa_init()
arch/arm64/mm/numa.c
static int __init numa_init(int (*init_func)(void)) { int ret; nodes_clear(numa_nodes_parsed); nodes_clear(node_possible_map); nodes_clear(node_online_map); ret = numa_alloc_distance(); if (ret < 0) return ret; ret = init_func(); if (ret < 0) goto out_free_distance; if (nodes_empty(numa_nodes_parsed)) { pr_info("No NUMA configuration found\n"); ret = -EINVAL; goto out_free_distance; } ret = numa_register_nodes(); if (ret < 0) goto out_free_distance; setup_node_to_cpumask_map(); return 0; out_free_distance: numa_free_distance(); return ret; }
NUMA 시스템을 초기화한다.
- 코드 라인 5~7에서 NUMA 상태를 관리하는 비트맵들을 모두 초기화한다.
- 코드 라인 9~11에서 numa_disatance[] 배열을 필요한 만큼 할당하고 초기 값들을 지정한다.
- 코드 라인 13~15에서 인자로 받은 함수 @init_func을 실행시켜 설정한다.
- 코드 라인 17~21에서 노드 정보가 하나도 없는 경우 “No NUMA configuration found” 메시지를 출력하고 빠져나간다.
- 코드 라인 23~25에서 노드 데이터(struct pglist_data)를 할당하고 기본 노드 정보를 설정한다.
- 코드 라인 27에서 노드 -> cpu 맵을 설정한다.
아래와 같이 ACPI 또는 디바이스 트리를 통해 NUMA 설정을 찾을 수 없는 경우 “No NUMA configuration found” 메시지가 출력되는 것을 볼 수 있다. 그리고 fallback되어 dymmy NUMA 초기화 루틴을 통해 “Faking a node at …” 메시지가 출력되는 것도 볼 수 있다.
[ 0.000000] NUMA: No NUMA configuration found [ 0.000000] NUMA: Faking a node at [mem 0x0000000040000000-0x000000007fffffff] [ 0.000000] NUMA: NODE_DATA [mem 0x7ddf4840-0x7ddf5fff] [ 0.000000] Zone ranges: [ 0.000000] DMA32 [mem 0x0000000040000000-0x000000007fffffff] [ 0.000000] Normal empty [ 0.000000] Movable zone start for each node [ 0.000000] Early memory node ranges [ 0.000000] node 0: [mem 0x0000000040000000-0x000000007fffffff] [ 0.000000] Initmem setup node 0 [mem 0x0000000040000000-0x000000007fffffff]
numa_alloc_distance()
arch/arm64/mm/numa.c
static int __init numa_alloc_distance(void) { size_t size; u64 phys; int i, j; size = nr_node_ids * nr_node_ids * sizeof(numa_distance[0]); phys = memblock_find_in_range(0, PFN_PHYS(max_pfn), size, PAGE_SIZE); if (WARN_ON(!phys)) return -ENOMEM; memblock_reserve(phys, size); numa_distance = __va(phys); numa_distance_cnt = nr_node_ids; /* fill with the default distances */ for (i = 0; i < numa_distance_cnt; i++) for (j = 0; j < numa_distance_cnt; j++) numa_distance[i * numa_distance_cnt + j] = i == j ? LOCAL_DISTANCE : REMOTE_DISTANCE; pr_debug("Initialized distance table, cnt=%d\n", numa_distance_cnt); return 0; }
노드 수 * 노드 수만큼 numa_distance[] 배열을 만들어 할당한다.
- 코드 라인 7에서 할당할 사이즈로 노드 수 * 노드 수를 사용한다.
- 코드 라인 8~11에서 memblock의 빈 공간에서 size 만큼의 공간을 페이지 단위로 알아온다.
- 코드 라인 13에서 size 영역을 memblock에 reserve 한다.
- 코드 라인 15~16에서 할당한 물리 주소를 가상 주소로 변환하여 numa_distance에 대입하고, 노드 수를 지정한다.
- 코드 라인 19~22에서 numa_distance[]는 논리적으로 이중 배열을 표현하였다. 디폴트 numa_distance[] 값으로 같은 노드를 의미하는 경우에만 LOCAL_DISTANCE(10)를 지정하고 그 외의 경우는 REMOTE_DISTANCE(20) 값을 지정해둔다.
- 예) nr_node_ids=2인 경우 numa_distance[]를 알아본다.
- numa_distance[0] = 10
- numa_distance[1] = 20
- numa_distance[2] = 20
- numa_distance[3] = 10
- 예) nr_node_ids=2인 경우 numa_distance[]를 알아본다.
numa_register_nodes()
arch/arm64/mm/numa.c
static int __init numa_register_nodes(void) { int nid; struct memblock_region *mblk; /* Check that valid nid is set to memblks */ for_each_mem_region(mblk) { int mblk_nid = memblock_get_region_node(mblk); if (mblk_nid == NUMA_NO_NODE || mblk_nid >= MAX_NUMNODES) { pr_warn("Warning: invalid memblk node %d [mem %#010Lx-%#010Lx]\n", mblk_nid, mblk->base, mblk->base + mblk->size - 1); return -EINVAL; } } /* Finally register nodes. */ for_each_node_mask(nid, numa_nodes_parsed) { unsigned long start_pfn, end_pfn; get_pfn_range_for_nid(nid, &start_pfn, &end_pfn); setup_node_data(nid, start_pfn, end_pfn); node_set_online(nid); } /* Setup online nodes to actual nodes*/ node_possible_map = numa_nodes_parsed; return 0; }
노드 데이터(struct pglist_data)를 할당하고 기본 노드 정보를 설정한다.
- 코드 라인 7~16에서 memblock에 노드가 지정되지 않은 경우 경고 메시지를 출력하고 에러 -EINVAL 값을 반환한다.
- 코드 라인 19~25에서 노드를 순회하며 노드 데이터( struct pglist_data)를 할당하고 노드의 기본 정보를 설정하고, online 노드 상태로 변경한다.
- 코드 라인 28에서 online 노드들을 possible 맵에 지정한다.
setup_node_to_cpumask_map()
arm64/mm/numa.c
/* * Allocate node_to_cpumask_map based on number of available nodes * Requires node_possible_map to be valid. * * Note: cpumask_of_node() is not valid until after this is done. * (Use CONFIG_DEBUG_PER_CPU_MAPS to check this.) */
static void __init setup_node_to_cpumask_map(void) { int node; /* setup nr_node_ids if not done yet */ if (nr_node_ids == MAX_NUMNODES) setup_nr_node_ids(); /* allocate and clear the mapping */ for (node = 0; node < nr_node_ids; node++) { alloc_bootmem_cpumask_var(&node_to_cpumask_map[node]); cpumask_clear(node_to_cpumask_map[node]); } /* cpumask_of_node() will now work */ pr_debug("Node to cpumask map for %u nodes\n", nr_node_ids); }
노드 -> cpu 맵을 설정한다.
- 코드 라인 6~7에서 nr_node_ids가 아직 설정되지 않은 경우 possible 노드 수로 지정한다.
- 코드 라인 10~13에서 노드 수만큼 순회하며 node_to_cpumask_map[node] 비트맵을 할당하고 초기화한다.
dummy_numa_init()
arch/arm64/mm/numa.c
/** * dummy_numa_init - Fallback dummy NUMA init * * Used if there's no underlying NUMA architecture, NUMA initialization * fails, or NUMA is disabled on the command line. * * Must online at least one node (node 0) and add memory blocks that cover all * allowed memory. It is unlikely that this function fails. */
static int __init dummy_numa_init(void) { phys_addr_t start = memblock_start_of_DRAM(); phys_addr_t end = memblock_end_of_DRAM(); int ret; if (numa_off) pr_info("NUMA disabled\n"); /* Forced off on command line. */ pr_info("Faking a node at [mem %#018Lx-%#018Lx]\n", start, end - 1); ret = numa_add_memblk(0, start, end); if (ret) { pr_err("NUMA init failed\n"); return ret; } numa_off = true; return 0; }
NUMA 설정이 없는 경우 한 개의 노드로 구성된 dummy NUMA 구성으로 초기화한다.
- 코드 라인 7~8에서 “numa=off” 커널 파라미터가 지정된 경우 “NUMA disabled” 메시지를 출력한다.
- 코드 라인 9에서 “Faking a node at …” 메시지 정보를 통해 한 개의 메모리 정보를 출력한다.
- 코드 라인 11~15에서 memory memblock 모두에 0번 노드를 지정한다.
- 코드 라인 17에서 NUMA가 disable 되었음을 나타낸다.
numa_add_memblk()
arch/arm64/mm/numa.c
/** * numa_add_memblk() - Set node id to memblk * @nid: NUMA node ID of the new memblk * @start: Start address of the new memblk * @end: End address of the new memblk * * RETURNS: * 0 on success, -errno on failure. */
int __init numa_add_memblk(int nid, u64 start, u64 end) { int ret; ret = memblock_set_node(start, (end - start), &memblock.memory, nid); if (ret < 0) { pr_err("memblock [0x%llx - 0x%llx] failed to add on node %d\n", start, (end - 1), nid); return ret; } node_set(nid, numa_nodes_parsed); return ret; }
memblock 영역에 노드 id를 지정하고 numa_nodes_parsed 비트맵에 현재 노드를 설정한다.
“numa” Early 커널 파라미터
numa_parse_early_param()
arch/arm64/mm/numa.c
static __init int numa_parse_early_param(char *opt) { if (!opt) return -EINVAL; if (str_has_prefix(opt, "off")) numa_off = true; return 0; } early_param("numa", numa_parse_early_param);
“numa=off” 커널 파라미터가 지정된 경우 전역 변수 numa_off에 true를 대입한다.
디바이스 트리를 통한 NUMA 시스템 초기화
다음과 같이 4개의 NUMA 노드를 가진 시스템 구성을 참고한다.
- 각 노드마다 4개의 클러스터로 구성하여 총 16개의 클러스터를 사용한다.
- 각 클러스터마다 4개의 코어를 구성하여 총 64개의 코어를 사용한다.
- 메모리 노드의 누마 id는 UEFI 펌웨어가 인식하여 전달되므로 아래는 노드 0번 정보만 기록되어 있다.
#include "hip07.dtsi" / { model = "Hisilicon Hip07 D05 Development Board"; compatible = "hisilicon,hip07-d05"; /* the mem node will be updated by UEFI. */ memory@0 { device_type = "memory"; reg = <0x0 0x00000000 0x0 0x40000000>; numa-node-id = <0>; }; distance-map { compatible = "numa-distance-map-v1"; distance-matrix = <0 0 10>, <0 1 15>, <0 2 20>, <0 3 25>, <1 0 15>, <1 1 10>, <1 2 25>, <1 3 30>, <2 0 20>, <2 1 25>, <2 2 10>, <2 3 15>, <3 0 25>, <3 1 30>, <3 2 15>, <3 3 10>;
arch/arm64/boot/dts/hisilicon/hip07.dtsi
cpu-map { cluster0 { core0 { cpu = <&cpu0>; }; core1 { cpu = <&cpu1>; }; core2 { cpu = <&cpu2>; }; core3 { cpu = <&cpu3>; }; }; ... cluster15 { core0 { cpu = <&cpu60>; }; core1 { cpu = <&cpu61>; }; core2 { cpu = <&cpu62>; }; core3 { cpu = <&cpu63>; }; }; }; cpu0: cpu@10000 { device_type = "cpu"; compatible = "arm,cortex-a72", "arm,armv8"; reg = <0x10000>; enable-method = "psci"; next-level-cache = <&cluster0_l2>; numa-node-id = <0>; }; ... cpu63: cpu@70303 { device_type = "cpu"; compatible = "arm,cortex-a72", "arm,armv8"; reg = <0x70303>; enable-method = "psci"; next-level-cache = <&cluster15_l2>; numa-node-id = <3>; }; cluster0_l2: l2-cache0 { compatible = "cache"; }; ... cluster15_l2: l2-cache15 { compatible = "cache"; }; };
다음 그림은 NUMA 시스템의 distance를 보여준다.
of_numa_init()
drivers/of/of_numa.c
int __init of_numa_init(void) { int r; of_numa_parse_cpu_nodes(); r = of_numa_parse_memory_nodes(); if (r) return r; return of_numa_parse_distance_map(); }
디바이스 트리를 통해 cpu 노드와 memory 노드에서 누마 id를 읽어 설정하고, distance 맵을 파싱해온다.
of_numa_parse_cpu_nodes()
drivers/of/of_numa.c
/* * Even though we connect cpus to numa domains later in SMP * init, we need to know the node ids now for all cpus. */
static void __init of_numa_parse_cpu_nodes(void) { u32 nid; int r; struct device_node *np; for_each_of_cpu_node(np) { r = of_property_read_u32(np, "numa-node-id", &nid); if (r) continue; pr_debug("CPU on %u\n", nid); if (nid >= MAX_NUMNODES) pr_warn("Node id %u exceeds maximum value\n", nid); else node_set(nid, numa_nodes_parsed); } }
cpu 노드 수만큼 순회하며 노드를 파싱한다.
- “numa-node-id” 속성이 1 이상인 경우 numa_nodes_parsed 비트맵에 노드를 설정한다.
of_numa_parse_memory_nodes()
drivers/of/of_numa.c
static int __init of_numa_parse_memory_nodes(void) { struct device_node *np = NULL; struct resource rsrc; u32 nid; int i, r; for_each_node_by_type(np, "memory") { r = of_property_read_u32(np, "numa-node-id", &nid); if (r == -EINVAL) /* * property doesn't exist if -EINVAL, continue * looking for more memory nodes with * "numa-node-id" property */ continue; if (nid >= MAX_NUMNODES) { pr_warn("Node id %u exceeds maximum value\n", nid); r = -EINVAL; } for (i = 0; !r && !of_address_to_resource(np, i, &rsrc); i++) r = numa_add_memblk(nid, rsrc.start, rsrc.end + 1); if (!i || r) { of_node_put(np); pr_err("bad property in memory node\n"); return r ? : -EINVAL; } } return 0; }
memory 노드 수만큼 순회하며 노드를 파싱하여 memblock에 노드를 기록한다.
- “numa-node-id” 속성이 있는 메모리 노드인 경우 그 영역의 memblock에 노드 정보를 기록한다.
of_numa_parse_distance_map()
drivers/of/of_numa.c
static int __init of_numa_parse_distance_map(void) { int ret = 0; struct device_node *np; np = of_find_compatible_node(NULL, NULL, "numa-distance-map-v1"); if (np) ret = of_numa_parse_distance_map_v1(np); of_node_put(np); return ret; }
compatible = “numa-distance-map-v1” 속성 값을 가진 노드에서 distance 맵을 파싱하여 온다.
of_numa_parse_distance_map_v1()
drivers/of/of_numa.c
static int __init of_numa_parse_distance_map_v1(struct device_node *map) { const __be32 *matrix; int entry_count; int i; pr_info("parsing numa-distance-map-v1\n"); matrix = of_get_property(map, "distance-matrix", NULL); if (!matrix) { pr_err("No distance-matrix property in distance-map\n"); return -EINVAL; } entry_count = of_property_count_u32_elems(map, "distance-matrix"); if (entry_count <= 0) { pr_err("Invalid distance-matrix\n"); return -EINVAL; } for (i = 0; i + 2 < entry_count; i += 3) { u32 nodea, nodeb, distance; nodea = of_read_number(matrix, 1); matrix++; nodeb = of_read_number(matrix, 1); matrix++; distance = of_read_number(matrix, 1); matrix++; if ((nodea == nodeb && distance != LOCAL_DISTANCE) || (nodea != nodeb && distance <= LOCAL_DISTANCE)) { pr_err("Invalid distance[node%d -> node%d] = %d\n", nodea, nodeb, distance); return -EINVAL; } numa_set_distance(nodea, nodeb, distance); /* Set default distance of node B->A same as A->B */ if (nodeb > nodea) numa_set_distance(nodeb, nodea, distance); } return 0; }
distance 맵을 파싱하여 온다.
- “distance-matrix” 속성 값을 읽어 numa_distance[] 배열에 값을 지정한다.
numa_set_distance()
arch/arm64/mm/numa.c
/** * numa_set_distance() - Set inter node NUMA distance from node to node. * @from: the 'from' node to set distance * @to: the 'to' node to set distance * @distance: NUMA distance * * Set the distance from node @from to @to to @distance. * If distance table doesn't exist, a warning is printed. * * If @from or @to is higher than the highest known node or lower than zero * or @distance doesn't make sense, the call is ignored. * */
void __init numa_set_distance(int from, int to, int distance) { if (!numa_distance) { pr_warn_once("Warning: distance table not allocated yet\n"); return; } if (from >= numa_distance_cnt || to >= numa_distance_cnt || from < 0 || to < 0) { pr_warn_once("Warning: node ids are out of bound, from=%d to=%d distance=%d\n", from, to, distance); return; } if ((u8)distance != distance || (from == to && distance != LOCAL_DISTANCE)) { pr_warn_once("Warning: invalid distance parameter, from=%d to=%d distance=%d\n", from, to, distance); return; } numa_distance[from * numa_distance_cnt + to] = distance; }
numa distace를 설정한다.
- numa_distance[from * numa_distance_cnt + to] 배열에 distance 값을 지정한다.
참고
- NUMA -1- (ARM64 초기화) | 문c – 현재 글
- NUMA -2- (Fallback Node) | 문c
- NODE 비트맵 (API) | 문c
- NUMA binding description | kernel.org
- NUMA with Linux | Lunatine’s Box