<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
