Ryotta's Linux 7.0 MM

메모리 관리 서브시스템 완전 분석

메모리 정책 (Memory Policy / mempolicy)

Linux 7.0 NUMA 메모리 할당 정책 서브시스템 분석

목차

1. 개요 (Overview)

2. 빠른 점검 명령

3. 핵심 자료구조

4. 핵심 함수

5. 호출 흐름

6. 조건별 비교

7. 관련 문서


개요 (Overview)

Linux 커널의 NUMA 메모리 정책(Memory Policy)은 사용자가 메모리 할당 시 어느 노드(node)에서 할당할지를 지정하는 메커니즘입니다. 7가지 정책 모드를 제공하며, VMA(Virtual Memory Area) 수준 또는 프로세스 수준에서 설정할 수 있습니다. VMA 정책이 프로세스 정책보다 우선하며, 인터럽트 컨텍스트에서는 정책이 무시되고 항상 로컬 CPU에서 할당합니다.

정책은 mbind() 시스템 콜로 VMA에 적용하거나, set_mempolicy() 시스템 콜로 프로세스 전체에 적용합니다. 정책은 /proc/pid/numa_mapsget_mempolicy()로 확인할 수 있으며, migrate_pages()로 기존 페이지를 다른 노드로 마이그레이션할 수도 있습니다. 최상위 zone만 정책이 적용되며, GFP_DMA 등 하위 zone 할당에는 정책이 적용되지 않습니다.

일상적인 비유로 보면 mempolicy는 큰 창고 여러 개 중 어느 창고에서 물건을 꺼낼지 정하는 배송 규칙입니다. MPOL_BIND는 지정 창고에서만 꺼내라는 강한 규칙이고, MPOL_PREFERRED는 특정 창고를 먼저 시도하되 부족하면 다른 창고도 허용하는 규칙입니다. MPOL_INTERLEAVEMPOL_WEIGHTED_INTERLEAVE는 여러 창고를 번갈아 사용해 병목을 줄이되, 후자는 창고별 처리 능력에 맞춰 더 빠른 창고에 더 많은 요청을 배분합니다.

소스 파일:

mm/mempolicy.c              ← 메인 구현 (~3945줄)
include/linux/mempolicy.h   ← 핵심 구조체 정의
include/linux/nodemask.h    ← 노드 마스크 연산
include/uapi/linux/mempolicy.h ← 사용자 API 정의
메모리 정책 구조도
mbind 호출 흐름

빠른 점검 명령

# 현재 프로세스의 NUMA 정책 확인
cat /proc/$$/numa_maps | head -20

# 시스템 전체 NUMA 정책 통계
numastat -p $$

# NUMA 노드 간 거리 확인
numactl --hardware

# 현재 정책으로 할당 테스트
numactl --interleave=all cat /proc/self/cmdline

# 메모리 정책별 NUMA 히트맵 (requires numad)
cat /sys/devices/system/node/node*/meminfo | grep "Node [0-9]"
numastat -m

# 특정 VMA의 정책 확인
cat /proc/$$/numa_maps | grep "policy="

# Weighted Interleave 가중치 확인 (커널 6.x+)
ls /sys/kernel/mm/mempolicy/weighted_interleave/
cat /sys/kernel/mm/mempolicy/weighted_interleave/node0

# 현재 numactl이 보는 프로세스 정책과 CPU/메모리 바인딩
numactl --show

# interleave 정책으로 짧은 실행 테스트
numactl --interleave=all cat /proc/self/numa_maps | head -5

# weighted interleave 노드별 가중치 일괄 확인
grep -H . /sys/kernel/mm/mempolicy/weighted_interleave/node* 2>/dev/null

핵심 자료구조

`struct mempolicy` — 메모리 정책의 핵심 구조체

소스: include/linux/mempolicy.h:47-59

struct mempolicy {
	atomic_t refcnt;
	unsigned short mode; 	/* 위 MPOL_* 참고 */
	unsigned short flags;	/* 위 set_mempolicy() MPOL_F_* 참고 */
	nodemask_t nodes;	/* interleave/bind/preferred 등 */
	int home_node;		/* MPOL_BIND와 MPOL_PREFERRED_MANY에 사용할 홈 노드 */

	union {
		nodemask_t cpuset_mems_allowed;	/* 이 노드들 기준 상대값 */
		nodemask_t user_nodemask;	/* 사용자가 전달한 nodemask */
	} w;
	struct rcu_head rcu;
};

필드 설명:

  • mode: 7가지 정책 모드 중 하나 (0=DEFAULT, 1=PREFERRED, 2=BIND, 3=INTERLEAVE, 4=LOCAL, 5=PREFERRED_MANY, 6=WEIGHTED_INTERLEAVE)
  • flags: MPOL_F_STATIC_NODES(노드 마스크 고정), MPOL_F_RELATIVE_NODES(상대적 매핑), MPOL_F_MOF(NUMA 배치 힌트), MPOL_F_MORON(NUMA 밸런싱 허용)
  • nodes: 정책이 적용될 노드들의 비트마스크
  • home_node: BIND/PREFERRED_MANY 정책에서 우선 할당할 홈 노드
  • `struct shared_policy` — 공유 메모리 영역 정책

    소스: include/linux/mempolicy.h:113-116

    struct shared_policy {
    	struct rb_root root;
    	rwlock_t lock;
    };

    공유 메모리(shmem/tmpfs)에서 사용됩니다. inode별로 RB-tree에 sp_node를 저장하며, pgoff 범위별로 다른 정책을 적용할 수 있습니다.

    `struct sp_node` — 공유 정책 노드

    소스: include/linux/mempolicy.h:117-121

    struct sp_node {
    	struct rb_node nd;
    	pgoff_t start, end;
    	struct mempolicy *policy;
    };

    `struct mempolicy_operations` — 정책별 연산 테이블

    소스: mm/mempolicy.c:360-363

    static const struct mempolicy_operations {
    	int (*create)(struct mempolicy *pol, const nodemask_t *nodes);
    	void (*rebind)(struct mempolicy *pol, const nodemask_t *nodes);
    } mpol_ops[MPOL_MAX];

    각 정책 모드별로 createrebind 연산을 등록합니다. cpuset 변경 시 rebind가 호출되어 정책의 노드 마스크를 재조정합니다.

    소스: mm/mempolicy.c:577-604

    static const struct mempolicy_operations mpol_ops[MPOL_MAX] = {
    	[MPOL_DEFAULT] = {
    		.rebind = mpol_rebind_default,
    	},
    	[MPOL_INTERLEAVE] = {
    		.create = mpol_new_nodemask,
    		.rebind = mpol_rebind_nodemask,
    	},
    	[MPOL_PREFERRED] = {
    		.create = mpol_new_preferred,
    		.rebind = mpol_rebind_preferred,
    	},
    	[MPOL_BIND] = {
    		.create = mpol_new_nodemask,
    		.rebind = mpol_rebind_nodemask,
    	},
    	[MPOL_LOCAL] = {
    		.rebind = mpol_rebind_default,
    	},
    	[MPOL_PREFERRED_MANY] = {
    		.create = mpol_new_nodemask,
    		.rebind = mpol_rebind_preferred,
    	},
    	[MPOL_WEIGHTED_INTERLEAVE] = {
    		.create = mpol_new_nodemask,
    		.rebind = mpol_rebind_nodemask,
    	},
    };

    `struct weighted_interleave_state` — 가중 interleave 상태

    소스: mm/mempolicy.c:153-161

    /*
     * NULL weighted_interleave_state는 .mode="auto"로 해석되고,
     * .iw_table은 길이가 nr_node_ids인 1 배열로 해석됩니다.
     */
    struct weighted_interleave_state {
    	bool mode_auto;
    	u8 iw_table[];
    };
    static struct weighted_interleave_state __rcu *wi_state;

    wi_state는 RCU로 교체되는 전역 가중치 테이블입니다. sysfs의 weighted_interleave/node* 값을 바꾸면 각 NUMA 노드의 interleave 비율이 달라지고, MPOL_WEIGHTED_INTERLEAVE 정책은 iw_table[]을 읽어 페이지 인덱스 또는 현재 task의 interleave 상태에 따라 다음 노드를 고릅니다.

    `struct queue_pages` — 페이지 수집 컨텍스트

    소스: mm/mempolicy.c:626-635

    struct queue_pages {
    	struct list_head *pagelist;
    	unsigned long flags;
    	nodemask_t *nmask;
    	unsigned long start;
    	unsigned long end;
    	struct vm_area_struct *first;
    	struct folio *large;		/* 마지막으로 만난 large folio 기록 */
    	long nr_failed;			/* 현재 격리할 수 없었던 folio 수 */
    };

    핵심 함수

    1. `mpol_new()` — 새 정책 객체 생성

    소스: mm/mempolicy.c:437-483

    /*
     * 이 함수는 새 정책을 만들고 몇 가지 검사와 단순 초기화를 수행합니다.
     * 노드 설정은 mpol_set_nodemask()를 호출해야 합니다.
     */
    static struct mempolicy *mpol_new(unsigned short mode, unsigned short flags,
    				  nodemask_t *nodes)
    {
    	struct mempolicy *policy;
    
    	if (mode == MPOL_DEFAULT) {
    		if (nodes && !nodes_empty(*nodes))
    			return ERR_PTR(-EINVAL);
    		return NULL;
    	}
    	VM_BUG_ON(!nodes);
    
    	/*
    	 * MPOL_PREFERRED는 nodemask가 비어 있는 경우(로컬 할당)
    	 * MPOL_F_STATIC_NODES 또는 MPOL_F_RELATIVE_NODES와 함께 사용할 수 없습니다.
    	 * 다른 모든 모드는 비어 있지 않은 유효한 nodemask 포인터가 필요합니다.
    	 */
    	if (mode == MPOL_PREFERRED) {
    		if (nodes_empty(*nodes)) {
    			if (((flags & MPOL_F_STATIC_NODES) ||
    			     (flags & MPOL_F_RELATIVE_NODES)))
    				return ERR_PTR(-EINVAL);
    
    			mode = MPOL_LOCAL;
    		}
    	} else if (mode == MPOL_LOCAL) {
    		if (!nodes_empty(*nodes) ||
    		    (flags & MPOL_F_STATIC_NODES) ||
    		    (flags & MPOL_F_RELATIVE_NODES))
    			return ERR_PTR(-EINVAL);
    	} else if (nodes_empty(*nodes))
    		return ERR_PTR(-EINVAL);
    
    	policy = kmem_cache_alloc(policy_cache, GFP_KERNEL);
    	if (!policy)
    		return ERR_PTR(-ENOMEM);
    	atomic_set(&policy->refcnt, 1);
    	policy->mode = mode;
    	policy->flags = flags;
    	policy->home_node = NUMA_NO_NODE;
    
    	return policy;
    }

    역할: 새로운 struct mempolicy를 할당하고 초기화합니다.

    분기 로직:

  • MPOL_DEFAULT + nodes 없음 → NULL 반환 (기본 정책)
  • MPOL_DEFAULT + nodes 있음 → -EINVAL
  • MPOL_PREFERRED + nodes 비어있음 → MPOL_LOCAL로 변환
  • MPOL_PREFERRED + static/relative flags + nodes 비어있음 → -EINVAL
  • MPOL_LOCAL + nodes 있음 → -EINVAL
  • 기타 모드 + nodes 비어있음 → -EINVAL
  • 정상 → kmem_cache_alloc(policy_cache)로 할당
  • 2. `do_set_mempolicy()` — 프로세스 정책 설정

    소스: mm/mempolicy.c:1066-1104

    /* 프로세스 메모리 정책 설정 */
    static long do_set_mempolicy(unsigned short mode, unsigned short flags,
    			     nodemask_t *nodes)
    {
    	struct mempolicy *new, *old;
    	NODEMASK_SCRATCH(scratch);
    	int ret;
    
    	if (!scratch)
    		return -ENOMEM;
    
    	new = mpol_new(mode, flags, nodes);
    	if (IS_ERR(new)) {
    		ret = PTR_ERR(new);
    		goto out;
    	}
    
    	task_lock(current);
    	ret = mpol_set_nodemask(new, nodes, scratch);
    	if (ret) {
    		task_unlock(current);
    		mpol_put(new);
    		goto out;
    	}
    
    	old = current->mempolicy;
    	current->mempolicy = new;
    	if (new && (new->mode == MPOL_INTERLEAVE ||
    		    new->mode == MPOL_WEIGHTED_INTERLEAVE)) {
    		current->il_prev = MAX_NUMNODES-1;
    		current->il_weight = 0;
    	}
    	task_unlock(current);
    	mpol_put(old);
    	ret = 0;
    out:
    	NODEMASK_SCRATCH_FREE(scratch);
    	return ret;
    }

    역할: set_mempolicy() 시스템 콜의 핵심 구현. 현재 프로세스의 메모리 정책을 변경합니다.

    흐름:

    1. mpol_new()로 새 정책 생성

    2. task_lock(current)으로 잠금

    3. mpol_set_nodemask()로 노드 마스크 설정

    4. current->mempolicy = new로 교체

    5. INTERLEAVE/WEIGHTED_INTERLEAVE이면 il_prev, il_weight 초기화

    3. `do_mbind()` — VMA에 정책 적용

    소스: mm/mempolicy.c:1486-1528

    static long do_mbind(unsigned long start, unsigned long len,
    		     unsigned short mode, unsigned short mode_flags,
    		     nodemask_t *nmask, unsigned long flags)
    {
    	struct mm_struct *mm = current->mm;
    	struct vm_area_struct *vma, *prev;
    	struct vma_iterator vmi;
    	struct migration_mpol mmpol;
    	struct mempolicy *new;
    	unsigned long end;
    	long err;
    	long nr_failed;
    	LIST_HEAD(pagelist);
    
    	if (flags & ~(unsigned long)MPOL_MF_VALID)
    		return -EINVAL;
    	if ((flags & MPOL_MF_MOVE_ALL) && !capable(CAP_SYS_NICE))
    		return -EPERM;
    
    	if (start & ~PAGE_MASK)
    		return -EINVAL;
    
    	if (mode == MPOL_DEFAULT)
    		flags &= ~MPOL_MF_STRICT;
    
    	len = PAGE_ALIGN(len);
    	end = start + len;
    
    	if (end < start)
    		return -EINVAL;
    	if (end == start)
    		return 0;
    
    	new = mpol_new(mode, mode_flags, nmask);
    	if (IS_ERR(new))
    		return PTR_ERR(new);
    
    	/*
    	 * 기본 정책을 사용하는 경우에는 불연속 주소 공간에 대한 작업도 허용됩니다.
    	 */
    	if (!new)
    		flags |= MPOL_MF_DISCONTIG_OK;

    소스: mm/mempolicy.c:1550-1564

    	nr_failed = queue_pages_range(mm, start, end, nmask,
    			flags | MPOL_MF_INVERT | MPOL_MF_WRLOCK, &pagelist);
    
    	if (nr_failed < 0) {
    		err = nr_failed;
    		nr_failed = 0;
    	} else {
    		vma_iter_init(&vmi, mm, start);
    		prev = vma_prev(&vmi);
    		for_each_vma_range(vmi, vma, end) {
    			err = mbind_range(&vmi, vma, &prev, start, end, new);
    			if (err)
    				break;
    		}
    	}

    소스: mm/mempolicy.c:1611-1628

    	mmap_write_unlock(mm);
    
    	if (!err && !list_empty(&pagelist)) {
    		nr_failed |= migrate_pages(&pagelist,
    				alloc_migration_target_by_mpol, NULL,
    				(unsigned long)&mmpol, MIGRATE_SYNC,
    				MR_MEMPOLICY_MBIND, NULL);
    	}
    
    	if (nr_failed && (flags & MPOL_MF_STRICT))
    		err = -EIO;
    	if (!list_empty(&pagelist))
    		putback_movable_pages(&pagelist);
    mpol_out:
    	mpol_put(new);
    	if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL))
    		lru_cache_enable();
    	return err;
    }

    역할: mbind() 시스템 콜의 핵심 구현. 특정 주소 범위에 메모리 정책을 적용하고, 필요시 페이지를 마이그레이션합니다.

    흐름:

    1. mpol_new()로 새 정책 생성

    2. mpol_set_nodemask()로 노드 마스크 설정

    3. queue_pages_range()로 기존 페이지 수집 (마이그레이션 대상)

    4. mbind_range()로 각 VMA에 정책 적용

    5. migrate_pages()로 페이지 마이그레이션 실행

    플래그 분기:

  • MPOL_MF_STRICT: 정책 위반 시 즉시 -EIO 반환
  • MPOL_MF_MOVE: 페이지 마이그레이션 허용
  • MPOL_MF_MOVE_ALL: 공유 페이지도 마이그레이션 허용 (CAP_SYS_NICE 필요)
  • 4. `policy_nodemask()` — 정책에 따른 노드 결정

    소스: mm/mempolicy.c:2265-2311

    /*
     * 페이지 할당 노드 필터링에 사용할 mempolicy nodemask와
     * preferred node id(또는 입력 node id)를 함께 반환합니다.
     */
    static nodemask_t *policy_nodemask(gfp_t gfp, struct mempolicy *pol,
    			   pgoff_t ilx, int *nid)
    {
    	nodemask_t *nodemask = NULL;
    
    	switch (pol->mode) {
    	case MPOL_PREFERRED:
    		/* 입력 node id 재정의 */
    		*nid = first_node(pol->nodes);
    		break;
    	case MPOL_PREFERRED_MANY:
    		nodemask = &pol->nodes;
    		if (pol->home_node != NUMA_NO_NODE)
    			*nid = pol->home_node;
    		break;
    	case MPOL_BIND:
    		/* nodemask로 제한합니다. 단, 낮은 zone에는 적용하지 않습니다. */
    		if (apply_policy_zone(pol, gfp_zone(gfp)) &&
    		    cpuset_nodemask_valid_mems_allowed(&pol->nodes))
    			nodemask = &pol->nodes;
    		if (pol->home_node != NUMA_NO_NODE)
    			*nid = pol->home_node;
    		/*
    		 * __GFP_THISNODE는 bind 정책과 함께 사용하면 안 됩니다.
    		 * 요청 노드에 머문다는 기대를 깨거나 정책을 깨뜨릴 수 있기 때문입니다.
    		 */
    		WARN_ON_ONCE(gfp & __GFP_THISNODE);
    		break;
    	case MPOL_INTERLEAVE:
    		/* 입력 node id 재정의 */
    		*nid = (ilx == NO_INTERLEAVE_INDEX) ?
    			interleave_nodes(pol) : interleave_nid(pol, ilx);
    		break;
    	case MPOL_WEIGHTED_INTERLEAVE:
    		*nid = (ilx == NO_INTERLEAVE_INDEX) ?
    			weighted_interleave_nodes(pol) :
    			weighted_interleave_nid(pol, ilx);
    		break;
    	}
    
    	return nodemask;
    }

    역할: 정책 모드에 따라 할당할 노드 ID와 노드 마스크를 결정합니다.

    분기 로직:

  • MPOL_PREFERRED: nid를 첫 번째 노드로 오버라이드
  • MPOL_PREFERRED_MANY: nodemask 반환, home_node가 있으면 nid 오버라이드
  • MPOL_BIND: zone 검사 후 nodemask 반환, home_node 오버라이드
  • MPOL_INTERLEAVE: interleave_nid() 또는 interleave_nodes() 호출
  • MPOL_WEIGHTED_INTERLEAVE: weighted_interleave_nid() 또는 weighted_interleave_nodes() 호출
  • 5. `get_vma_policy()` — 유효 정책 선택

    소스: mm/mempolicy.c:2018-2055

    struct mempolicy *__get_vma_policy(struct vm_area_struct *vma,
    				   unsigned long addr, pgoff_t *ilx)
    {
    	*ilx = 0;
    	return (vma->vm_ops && vma->vm_ops->get_policy) ?
    		vma->vm_ops->get_policy(vma, addr, ilx) : vma->vm_policy;
    }
    
    /*
     * get_vma_policy(@vma, @addr, @order, @ilx)
     * @vma: 정책을 찾을 가상 메모리 영역
     * @addr: 공유 정책 조회에 사용할 VMA 내부 주소
     * @order: 0 또는 interleave에 맞는 huge_page_order
     * @ilx: interleave index 출력. MPOL_INTERLEAVE 또는 MPOL_WEIGHTED_INTERLEAVE에서만 사용합니다.
     *
     * 지정 주소의 VMA에 대한 유효 정책을 반환합니다.
     * 필요하면 current->mempolicy 또는 시스템 기본 정책으로 내려갑니다.
     * MPOL_F_SHARED로 표시된 공유 정책은 다른 task의 해제로부터 보호하기 위해
     * get_policy() vm_op가 적절히 추가한 추가 참조 카운트가 필요합니다.
     * 공유 정책의 추가 참조는 호출자가 해제해야 합니다.
     */
    struct mempolicy *get_vma_policy(struct vm_area_struct *vma,
    				 unsigned long addr, int order, pgoff_t *ilx)
    {
    	struct mempolicy *pol;
    
    	pol = __get_vma_policy(vma, addr, ilx);
    	if (!pol)
    		pol = get_task_policy(current);
    	if (pol->mode == MPOL_INTERLEAVE ||
    	    pol->mode == MPOL_WEIGHTED_INTERLEAVE) {
    		*ilx += vma->vm_pgoff >> order;
    		*ilx += (addr - vma->vm_start) >> (PAGE_SHIFT + order);
    	}
    	return pol;
    }

    역할: shared memory의 vm_ops->get_policy()가 있으면 pgoff 범위별 shared policy를 먼저 사용하고, 없으면 vma->vm_policy를 사용합니다. 둘 다 없으면 get_task_policy(current)를 통해 task policy 또는 default_policy로 내려갑니다. interleave 계열 정책에서는 파일 오프셋과 VMA 내부 주소를 합쳐 ilx를 계산하므로 같은 VMA 안에서도 페이지 위치별 대상 노드가 달라질 수 있습니다.

    6. `mpol_misplaced()` — 페이지 위치 검증

    소스: mm/mempolicy.c:2984-3079

    int mpol_misplaced(struct folio *folio, struct vm_fault *vmf,
    		   unsigned long addr)
    {
    	struct mempolicy *pol;
    	pgoff_t ilx;
    	struct zoneref *z;
    	int curnid = folio_nid(folio);
    	struct vm_area_struct *vma = vmf->vma;
    	int thiscpu = raw_smp_processor_id();
    	int thisnid = numa_node_id();
    	int polnid = NUMA_NO_NODE;
    	int ret = NUMA_NO_NODE;
    
    	/*
    	 * preempt가 일어나지 않고 안정적인 smp processor id를 갖도록
    	 * ptl이 잡혀 있는지 확인합니다.
    	 */
    	lockdep_assert_held(vmf->ptl);
    	pol = get_vma_policy(vma, addr, folio_order(folio), &ilx);
    	if (!(pol->flags & MPOL_F_MOF))
    		goto out;
    
    	switch (pol->mode) {
    	case MPOL_INTERLEAVE:
    		polnid = interleave_nid(pol, ilx);
    		break;
    
    	case MPOL_WEIGHTED_INTERLEAVE:
    		polnid = weighted_interleave_nid(pol, ilx);
    		break;
    
    	case MPOL_PREFERRED:
    		if (node_isset(curnid, pol->nodes))
    			goto out;
    		polnid = first_node(pol->nodes);
    		break;
    
    	case MPOL_LOCAL:
    		polnid = numa_node_id();
    		break;
    
    	case MPOL_BIND:
    	case MPOL_PREFERRED_MANY:
    		/*
    		 * MPOL_PREFERRED_MANY는 정책 nodemask 밖에서도 페이지를 할당할 수 있지만,
    		 * 현재는 NUMA migration이 정책 nodemask 밖으로 이동하는 것을 허용하지 않습니다.
    		 * 느린 메모리로 demotion을 일으키고 싶다면 DRAM node 'x'에서 할당하기 전에
    		 * node 'x'를 제외한 MPOL_PREFERRED_MANY mask를 사용하게 됩니다.
    		 * 이런 경우 느린 메모리 node에서 node 'x'로 promote하면 안 됩니다.
    		 */
    		if (pol->flags & MPOL_F_MORON) {
    			/*
    			 * NUMA balancing을 통해 여러 노드 사이 placement를 최적화합니다.
    			 */
    			if (node_isset(thisnid, pol->nodes))
    				break;
    			goto out;
    		}
    
    		/*
    		 * 현재 페이지가 정책 nodemask 안에 있으면 그대로 사용하고,
    		 * 아니면 가능한 경우 가장 가까운 허용 노드를 선택합니다.
    		 * 허용 노드가 없으면 현재 상태를 유지합니다.
    		 */
    		if (node_isset(curnid, pol->nodes))
    			goto out;
    		z = first_zones_zonelist(
    				node_zonelist(thisnid, GFP_HIGHUSER),
    				gfp_zone(GFP_HIGHUSER),
    				&pol->nodes);
    		polnid = zonelist_node_idx(z);
    		break;
    
    	default:
    		BUG();
    	}
    
    	/* folio를 이 folio를 참조한 CPU의 노드 쪽으로 이동합니다. */
    	if (pol->flags & MPOL_F_MORON) {
    		polnid = thisnid;
    
    		if (!should_numa_migrate_memory(current, folio, curnid,
    						thiscpu))
    			goto out;
    	}
    
    	if (curnid != polnid)
    		ret = polnid;
    out:
    	mpol_cond_put(pol);
    
    	return ret;
    }

    역할: NUMA 밸런싱에서 현재 folio가 정책에 맞는 노드에 있는지 확인하고, 마이그레이션이 필요하면 대상 노드를 반환합니다.

    반환값:

  • NUMA_NO_NODE: 현재 노드가 정책에 맞음 (마이그레이션 불필요)
  • 기타: 마이그레이션 대상 노드 ID

  • 호출 흐름

    mbind() 시스템 콜 호출 흐름

    SYSCALL_DEFINE6(mbind)
      └─ kernel_mbind()
           ├─ sanitize_mpol_flags()     ← 플래그 검증
           ├─ get_nodes()               ← 사용자 nodemask 복사
           └─ do_mbind()
                ├─ mpol_new()           ← 새 정책 생성
                ├─ mpol_set_nodemask()  ← 노드 마스크 설정
                ├─ queue_pages_range()  ← 기존 페이지 수집
                │    └─ walk_page_range()
                │         ├─ queue_pages_test_walk()
                │         ├─ queue_folios_pte_range()
                │         │    └─ migrate_folio_add()
                │         └─ queue_folios_hugetlb()
                ├─ mbind_range()        ← VMA에 정책 적용
                │    ├─ vma_modify_policy()
                │    └─ vma_replace_policy()
                │         └─ mpol_dup()
                └─ migrate_pages()      ← 페이지 마이그레이션
                     └─ alloc_migration_target_by_mpol()
                          └─ policy_nodemask()

    할당 시 정책 적용 흐름

    alloc_pages() / vma_alloc_folio()
      └─ alloc_pages_mpol()
           ├─ policy_nodemask()         ← 정책에 따른 노드 결정
           │    ├─ MPOL_PREFERRED: nid 오버라이드
           │    ├─ MPOL_INTERLEAVE: interleave_nid()
           │    ├─ MPOL_WEIGHTED_INTERLEAVE: weighted_interleave_nid()
           │    └─ MPOL_BIND: nodemask 반환
            └─ __alloc_frozen_pages_noprof()  ← Buddy Allocator 호출

    Weighted Interleave 노드 선택 흐름

    MPOL_WEIGHTED_INTERLEAVE
      ├─ alloc_pages()처럼 interleave index가 없으면
      │    └─ weighted_interleave_nodes()
      │         ├─ current->il_prev로 다음 후보 노드 선택
      │         ├─ get_il_weight(node)로 노드 가중치 로드
      │         └─ current->il_weight가 0이 될 때까지 같은 노드 유지
      └─ VMA/page-cache처럼 interleave index가 있으면
           └─ weighted_interleave_nid(pol, ilx)
                ├─ 정책 nodemask를 안정적으로 복사
                ├─ wi_state->iw_table[] 또는 기본값 1 사용
                ├─ 전체 weight 합산
                └─ ilx % weight_total 위치에 해당하는 노드 선택

    소스: mm/mempolicy.c:2203-2240

    static unsigned int weighted_interleave_nid(struct mempolicy *pol, pgoff_t ilx)
    {
    	struct weighted_interleave_state *state;
    	nodemask_t nodemask;
    	unsigned int target, nr_nodes;
    	u8 *table = NULL;
    	unsigned int weight_total = 0;
    	u8 weight;
    	int nid = 0;
    
    	nr_nodes = read_once_policy_nodemask(pol, &nodemask);
    	if (!nr_nodes)
    		return numa_node_id();
    
    	rcu_read_lock();
    
    	state = rcu_dereference(wi_state);
    	/* 초기화되지 않은 wi_state는 모든 weight가 1이라고 가정합니다. */
    	if (state)
    		table = state->iw_table;
    
    	/* 전체 weight 계산 */
    	for_each_node_mask(nid, nodemask)
    		weight_total += table ? table[nid] : 1;
    
    	/* 전체 weight 기준으로 node offset 계산 */
    	target = ilx % weight_total;
    	nid = first_node(nodemask);
    	while (target) {
    		/* 시스템 기본값 사용 감지 */
    		weight = table ? table[nid] : 1;
    		if (target < weight)
    			break;
    		target -= weight;
    		nid = next_node_in(nid, nodemask);
    	}
    	rcu_read_unlock();
    	return nid;
    }

    정책 조회 흐름

    get_mempolicy()
      └─ kernel_get_mempolicy()
           └─ do_get_mempolicy()
                ├─ MPOL_F_ADDR: vma_lookup() → __get_vma_policy()
                ├─ MPOL_F_NODE + MPOL_F_ADDR: lookup_node()
                └─ MPOL_F_NODE: interleave_nodes() / weighted_interleave_nodes()

    조건별 비교

    정책 모드 비교

    정책 모드동작fallback대표 사용 사례
    `MPOL_DEFAULT`로컬 노드 우선, 프로세스 정책 적용로컬 할당일반 애플리케이션
    `MPOL_LOCAL`항상 로컬 노드에서 할당없음 (로컬만)지연 시간 민감한 앱
    `MPOL_PREFERRED`지정 노드 우선 시도실패 시 로컬 fallback특정 노드 성능 최적화
    `MPOL_PREFERRED_MANY`지정 노드 그룹 우선 시도실패 시 전체 시스템DRAM 티어 우선 할당
    `MPOL_BIND`지정 노드에서만 할당없음 (할당 실패 가능)데이터 주권 (data sovereignty)
    `MPOL_INTERLEAVE`지정 노드들에 순환 할당없음메모리 대역폭 분산
    `MPOL_WEIGHTED_INTERLEAVE`가중치 기반 순환 할당없음비대칭 NUMA (CXL 등)

    정책 적용 위치 우선순위

    우선순위정책 위치선택 함수대표 대상비고
    1`vma->vm_ops->get_policy()``__get_vma_policy()`shmem/tmpfs shared policypgoff 범위별 `sp_node` 정책을 반환할 수 있음
    2`vma->vm_policy``__get_vma_policy()``mbind()`로 지정한 VMA주소 범위별 정책이 task 정책보다 우선
    3`current->mempolicy``get_task_policy()``set_mempolicy()`로 지정한 프로세스VMA 정책이 없을 때 사용
    4`default_policy``get_task_policy()`시스템 기본값task 정책이 NULL이면 사용

    Interleave 방식 비교

    방식노드 선택 기준상태 저장 위치장점주의점
    `MPOL_INTERLEAVE``ilx % nodes_weight(mask)` 또는 `current->il_prev`task의 `il_prev`모든 노드에 균등 분산노드별 대역폭 차이를 반영하지 않음
    `MPOL_WEIGHTED_INTERLEAVE``ilx % weight_total` 또는 `current->il_weight`task의 `il_prev/il_weight`, 전역 `wi_state->iw_table[]`CXL/DRAM/HBM처럼 비대칭 노드에 비율 배분 가능sysfs 가중치가 0 또는 비현실적이면 의도와 다른 편중 가능
    `MPOL_PREFERRED_MANY`nodemask 안의 선호 노드 집합`pol->nodes`, `home_node`선호 노드가 부족해도 fallback 가능`BIND`처럼 강제 격리는 아님

    정책 생성(create) 연산 비교

    정책 모드create 연산nodemask 필요비고
    DEFAULT없음불필요기본 정책은 NULL
    LOCAL없음불필요nodes 비어있어야 함
    PREFERRED`mpol_new_preferred`1개 노드nodes에서 first_node 추출
    PREFERRED_MANY`mpol_new_nodemask`1개 이상여러 노드 허용
    BIND`mpol_new_nodemask`1개 이상nodes 비어있으면 에러
    INTERLEAVE`mpol_new_nodemask`1개 이상nodes 비어있으면 에러
    WEIGHTED_INTERLEAVE`mpol_new_nodemask`1개 이상가중치는 별도 관리

    cpuset rebind 동작 비교

    정책 모드rebind 동작STATIC_NODESRELATIVE_NODES
    DEFAULT`mpol_rebind_default` (no-op)--
    LOCAL`mpol_rebind_default` (no-op)--
    PREFERRED`mpol_rebind_preferred` (cpuset만 업데이트)--
    PREFERRED_MANY`mpol_rebind_preferred` (cpuset만 업데이트)--
    BIND`mpol_rebind_nodemask` (노드 재매핑)AND 연산상대 매핑
    INTERLEAVE`mpol_rebind_nodemask` (노드 재매핑)AND 연산상대 매핑
    WEIGHTED_INTERLEAVE`mpol_rebind_nodemask` (노드 재매핑)AND 연산상대 매핑

    mbind() 플래그별 동작 비교

    플래그동작권한 필요비고
    0 (기본)정책 변경 + 페이지 수집 안함없음정책만 변경
    `MPOL_MF_STRICT`정책 위반 페이지 발견 시 `-EIO`없음검증 모드
    `MPOL_MF_MOVE`마이그레이션 가능한 페이지 수집없음일반 마이그레이션
    `MPOL_MF_MOVE_ALL`공유 페이지도 마이그레이션`CAP_SYS_NICE`강제 마이그레이션
    `MPOL_MF_DISCONTIG_OK`불연속 VMA 허용없음기본 정책일 때

    NUMA 밸런싱 통합

    Linux의 NUMA 밸런싱(numa_balancing)은 mempolicy.c와 밀접하게 연관됩니다:

    1. PROT_NONE 마킹: change_prot_numa()가 VMA의 PTE를 PROT_NONE으로 설정하여 NUMA 힌트 폴트를 유발합니다.

    2. 폴트 처리: NUMA 힌트 폴트 발생 시 do_numa_page()가 호출되고, mpol_misplaced()로 현재 페이지가 정책에 맞는지 확인합니다.

    3. 페이지 이동: MPOL_F_MORON 플래그가 설정된 BIND/PREFERRED_MANY 정책에서는 should_numa_migrate_memory()를 호출하여 현재 CPU가 실행 중인 노드로 페이지를 이동합니다.

    4. Tiering 지원: MPOL_F_MOF 플래그는 메모리 티어링(CXL, HBM 등)에서 NUMA 배치 힌트를 활성화합니다.


    초기화

    numa_policy_init() 함수는 부팅 시 호출됩니다:

    소스: mm/mempolicy.c:3335-3386

    void __init numa_policy_init(void)
    {
    	nodemask_t interleave_nodes;
    	unsigned long largest = 0;
    	int nid, prefer = 0;
    
    	policy_cache = kmem_cache_create("numa_policy",
    					 sizeof(struct mempolicy),
    					 0, SLAB_PANIC, NULL);
    
    	sn_cache = kmem_cache_create("shared_policy_node",
    				     sizeof(struct sp_node),
    				     0, SLAB_PANIC, NULL);
    
    	for_each_node(nid) {
    		preferred_node_policy[nid] = (struct mempolicy) {
    			.refcnt = ATOMIC_INIT(1),
    			.mode = MPOL_PREFERRED,
    			.flags = MPOL_F_MOF | MPOL_F_MORON,
    			.nodes = nodemask_of_node(nid),
    		};
    	}
    
    	/*
    	 * 시스템 초기화에 interleave 정책을 설정합니다. Interleave는 충분한 크기의
    	 * 노드(기본값 16MB 이상)에만 활성화하며, 모두 더 작으면 가장 큰 노드로 내려갑니다.
    	 */
    	nodes_clear(interleave_nodes);
    	for_each_node_state(nid, N_MEMORY) {
    		unsigned long total_pages = node_present_pages(nid);
    
    		/* 가장 큰 노드 보존 */
    		if (largest < total_pages) {
    			largest = total_pages;
    			prefer = nid;
    		}
    
    		/* 이 노드를 interleave할지 결정 */
    		if ((total_pages << PAGE_SHIFT) >= (16 << 20))
    			node_set(nid, interleave_nodes);
    	}
    
    	/* 모두 너무 작으면 가장 큰 노드 사용 */
    	if (unlikely(nodes_empty(interleave_nodes)))
    		node_set(prefer, interleave_nodes);
    
    	if (do_set_mempolicy(MPOL_INTERLEAVE, 0, &interleave_nodes))
    		pr_err("%s: interleaving failed\n", __func__);
    
    	check_numabalancing_enable();
    }

    1. policy_cache slab 캐시 생성 (struct mempolicy 할당용)

    2. sn_cache slab 캐시 생성 (struct sp_node 할당용)

    3. preferred_node_policy[] 배열 초기화 (노드별 MPOL_PREFERRED)

    4. 시스템 초기 정책을 MPOL_INTERLEAVE로 설정 (16MB 이상 노드 대상)

    5. NUMA 밸런싱 자동 활성화 검사


    관련 문서

  • 00-메모리 관리 개요
  • 13-NUMA
  • 20-Migration
  • 14-Memory Cgroup
  • 01-Buddy Allocator