<kernel v5.0>
NUMA -3- (Memory policy)
메모리 정책
NUMA 메모리 정책은 다음과 같은 종류가 있다.
- MPOL_DEFAULT
- 오직 메모리 정책 API 내부에서만 사용되는 모드이다. NULL로 리턴되어 내부에서 fallback되어 시스템 디폴트 메모리 정책을 사용하도록 한다.
- MPOL_PREFERRED
- 선호하는 노드 하나를 지정하여 할당한다. 메모리 부족 시 다른 노드에서 할당할 수 있다.
- 단 MPOL_F_LOCAL 플래그와 함께 사용되는 경우에는 preferred 기능이 무시되고, 로컬 노드 할당을 우선하게 한다.
- MPOL_BIND
- 지정된 bind 노드들에서만 메모리를 할당한다. 메모리 부족 시 다른 노드에서 할당할 수 없다.
- MPOL_INTERLEAVE
- 지정된 인터리브 노드들에서 순환(Round Robin)하며 할당하게 한다. 메모리 부족 시 다른 노드에서 할당할 수 있다.
- MPOL_LOCAL
- 모드를 MPOL_PREFERRED로 변경하고, MPOL_F_LOCAL 플래그와 함께 사용되어 로컬 노드를 우선하게 한다.
메모리 정책 플래그
다음과 같은 각종 메모리 정책 관련 플래그들을 사용한다.
- set_mempolicy()에서 사용되는 플래그
- MPOL_F_STATIC_NODES(0x8000)
- static 노드 지정
- MPOL_F_RELATIVE_NODES(0x4000)
- 상대 노드 지정
- MPOL_F_STATIC_NODES(0x8000)
- get_mempolicy()에서 사용되는 플래그
- MPOL_F_NODE(1)
- 노드 매스크 대신 next IL 노드 반환
- MPOL_F_ADDR(2)
- 주소로 vma 검색
- MPOL_F_MEMS_ALLOWED(4)
- return allowed memories
- MPOL_F_NODE(1)
- mbind()에서 사용되는 플래그
- MPOL_MF_STRICT(1)
- MPOL_MF_MOVE(2)
- MPOL_MF_MOVE_ALL(4)
- MPOL_MF_LAZY(8)
- MPOL_MF_INTERNAL(16)
- 모드와 함께 사용되는 내부 플래그
- MPOL_F_SHARED(1)
- 공유 정책
- MPOL_F_LOCAL(2)
- preferred 로컬 노드 할당
- MPOL_F_MOF(8)
- 폴트 시 마이그레이션
- MPOL_F_MORON(16)
- Migrate On protnone Reference On Node
- MPOL_F_SHARED(1)
다음은 두 개의 노드에 각각 20개의 cpu core가 사용되는 모습을 보여준다.
$ numactl --hardware available: 2 nodes (0-1) node 0 cpus: 0 1 2 3 4 5 6 7 8 9 20 21 22 23 24 25 26 27 28 29 node 0 size: 32654 MB node 0 free: 18259 MB node 1 cpus: 10 11 12 13 14 15 16 17 18 19 30 31 32 33 34 35 36 37 38 39 node 1 size: 32768 MB node 1 free: 15491 MB node distances: node 0 1 0: 10 21 1: 21 10
다음은 누마 노드 정책이 default를 사용하는 모습을 보여준다.
$ numactl --show policy: default preferred node: current physcpubind: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 cpubind: 0 1 nodebind: 0 1 membind: 0 1
NUMA 메모리 정책(Policy)
default_policy 전역 객체
mm/mempolicy.c
/* * run-time system-wide default policy => local allocation */ static struct mempolicy default_policy = { .refcnt = ATOMIC_INIT(1), /* never free it */ .mode = MPOL_PREFERRED, .flags = MPOL_F_LOCAL, };
메모리 정책을 로컬 노드 우선으로 지정한다.
태스크에 대한 메모리 정책 알아오기
get_task_policy()
mm/mempolicy.c
struct mempolicy *get_task_policy(struct task_struct *p) { struct mempolicy *pol = p->mempolicy; int node; if (pol) return pol; node = numa_node_id(); if (node != NUMA_NO_NODE) { pol = &preferred_node_policy[node]; /* preferred_node_policy is not initialised early in boot */ if (pol->mode) return pol; } return &default_policy; }
현재 태스크의 메모리 정책을 아래의 케이스에 따라 반환한다.
- 태스크에 지정된 정책이 있으면 -> 1) 태스크에 지정된 메모리 정책
- 지정한 노드가 있으면 -> 2) 지정한 노드 우선 메모리 정책 사용
- 지정한 노드 없이 요청 시 -> 3) 시스템 디폴트 메모리 정책(로컬 노드 우선)
policy_node()
mm/mempolicy.c
/* Return the node id preferred by the given mempolicy, or the given id */ static int policy_node(gfp_t gfp, struct mempolicy *policy, int nd) { if (policy->mode == MPOL_PREFERRED && !(policy->flags & MPOL_F_LOCAL)) nd = policy->v.preferred_node; else { /* * __GFP_THISNODE shouldn't even be used with the bind policy * because we might easily break the expectation to stay on the * requested node and not break the policy. */ WARN_ON_ONCE(policy->mode == MPOL_BIND && (gfp & __GFP_THISNODE)); } return nd; }
메모리 정책이 preferred 모드이고 preferred 노드가 지정된 경우 해당 노드 번호를 반환한다. 그렇지 않은 경우 입력 인자 @nd를 반환한다.
- preferred라도 로컬 노드를 사용하도록 요청한 경우에는 그냥 @nd를 반환한다.
policy_nodemask()
mm/mempolicy.c
/* * Return a nodemask representing a mempolicy for filtering nodes for * page allocation */
static nodemask_t *policy_nodemask(gfp_t gfp, struct mempolicy *policy) { /* Lower zones don't get a nodemask applied for MPOL_BIND */ if (unlikely(policy->mode == MPOL_BIND) && apply_policy_zone(policy, gfp_zone(gfp)) && cpuset_nodemask_valid_mems_allowed(&policy->v.nodes)) return &policy->v.nodes; return NULL; }
인터리브 메모리 정책
interleave_nodes()
mm/mempolicy.c
/* Do dynamic interleaving for a process */ static unsigned interleave_nodes(struct mempolicy *policy) { unsigned nid, next; struct task_struct *me = current; nid = me->il_next; next = next_node(nid, policy->v.nodes); if (next >= MAX_NUMNODES) next = first_node(policy->v.nodes); if (next < MAX_NUMNODES) me->il_next = next; return nid; }
인터리브 노드들을 대상으로 순회하며 노드 번호를 반환한다.
- currnet->il_next에 기억된 노드를 반환한다. 그 동안 다음에 배정할 노드를 current->il_next에 기억해놓는다.
- il_next에는 메모리 정책이 MPOL_INTERLEAVE로 설정된 경우 사용할 노드를 interleave(round robin) 방식에 의해 배정한다.
- next_node()
- 노드 비트맵에 대하여 지정된 노드의 다음노드를 알아온다. 못 찾은 경우 MAX_NUMNODES를 반환한다.
- first_node()
- 노드 비트맵의 처음에 위치한 노드를 알아온다.
- next_node()
alloc_page_interleave()
mm/mempolicy.c
/* Allocate a page in interleaved policy. Own path because it needs to do special accounting. */
static struct page *alloc_page_interleave(gfp_t gfp, unsigned order, unsigned nid) { struct page *page; page = __alloc_pages(gfp, order, nid); /* skip NUMA_INTERLEAVE_HIT counter update if numa stats is disabled */ if (!static_branch_likely(&vm_numa_stat_key)) return page; if (page && page_to_nid(page) == nid) { preempt_disable(); __inc_numa_state(page_zone(page), NUMA_INTERLEAVE_HIT); preempt_enable(); } return page; }
interleave 메모리 정책으로 2^order 페이지만큼 연속된 물리 메모리를 할당 받는다.
- 코드 라인 6에서 요청한 gfp 플래그, 노드에서 2^order 페이지만큼 연속된 물리 메모리를 할당해온다.
- gfp 플래그에 따라 전체 노드 zonelist인 node_zonelist[0]을 가져오거나 지정된 노드 zone만을 담은 node_zonelist[1]을 가져온다.
- 코드 라인 8~9에서 NUMA 통계를 사용하지 않는 경우 skip 한다.
- 코드 라인 10~14에서 NUMA 통계를 갱신한다.
- 요청한 노드에서 페이지를 할당 받은 경우에 한해 해당 존의 NUMA_INTERLEAVE_HIT stat을 증가시킨다.
주요 구조체
mempolicy 구조체
include/linux/mempolicy.h
/* * Describe a memory policy. * * A mempolicy can be either associated with a process or with a VMA. * For VMA related allocations the VMA policy is preferred, otherwise * the process policy is used. Interrupts ignore the memory policy * of the current process. * * Locking policy for interlave: * In process context there is no locking because only the process accesses * its own state. All vma manipulation is somewhat protected by a down_read on * mmap_sem. * * Freeing policy: * Mempolicy objects are reference counted. A mempolicy will be freed when * mpol_put() decrements the reference count to zero. * * Duplicating policy objects: * mpol_dup() allocates a new mempolicy and copies the specified mempolicy * to the new storage. The reference count of the new object is initialized * to 1, representing the caller of mpol_dup(). */
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; };
참고
- NUMA with Linux | Lunatine’s Box
- Local and Remote Memory: Memory in a Linux/NUMA System | Christoph Lameter – pdf 다운로드
- NUMA Best Practices for Dell PowerEdge 12th Generation Servers | Dell – pdf 다운로드
- What is Linux Memory Policy? | kernel.org
- NUMA API for Linux | LWN.net
- numa – overview of Non-Uniform Memory Architecture | man7.org
- NUMA (Non-Uniform Memory Access): An Overview | queue.acm.org
- Red Hat Enterprise Linux Non-Uniform Memory Access support for HP ProLiant servers | HP – pdf 다운로드