NUMA -1- (ARM64 초기화)

<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

 

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 값을 지정한다.

 

참고

 

 

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다