NUMA -3- (Memory policy)

<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)
      • 상대 노드 지정
  • get_mempolicy()에서 사용되는 플래그
    • MPOL_F_NODE(1)
      • 노드 매스크 대신 next IL 노드 반환
    • MPOL_F_ADDR(2)
      • 주소로 vma 검색
    • MPOL_F_MEMS_ALLOWED(4)
      • return allowed memories
  • 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

 

다음은 두 개의 노드에 각각 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()
      • 노드 비트맵의 처음에 위치한 노드를 알아온다.

 

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;
};

 

참고

 

디버그 메모리 -4- (Idle Page 추적)

<kernel v5.0>

디버그 메모리 -4- (Idle Page 추적)

/sys/kernel/mm/page_idle/bitmap을 조회하여 idle 페이지를 추적하기 위해 사용한다. 비트맵에서 각 비트는 페이지를 의미하고 8바이트 단위로 정렬하여 사용된다. 1로 설정된 비트가 idle 페이지이다. 이 기능은 유저 페이지에 한하여 사용될 수 있다.

  • bitmap 파일을 사용하여 특정 페이지를 추적하려면 해당 페이지에 해당하는 bit를 1로 설정하고, 해당 페이지에 접근한 후 다시 bitmap을 보면 0으로 변경됨을 확인할 수 있다.

 

사용 조건

  • CONFIG_IDLE_PAGE_TRACKING 커널 옵션

 

 

need_page_idle()

mm/page_idle.c

#ifndef CONFIG_64BIT
static bool need_page_idle(void)
{
        return true;
}
#endif

 

page_idle_init()

mm/page_idle.c

static int __init page_idle_init(void)
{
        int err;

        err = sysfs_create_group(mm_kobj, &page_idle_attr_group);
        if (err) {
                pr_err("page_idle: register sysfs failed\n");
                return err;
        }
        return 0;
}
subsys_initcall(page_idle_init);

 

참고

 

디버그 메모리 -3- (Page Owner 추적)

<kernel v5.0>

디버그 메모리 -3- (Page Owner 추적)

사용 조건

  • CONFIG_PAGE_EXTENSION  커널 옵션
  • CONFIG_PAGE_OWNER 커널 옵션
  • “page_owner=on” 커널 파라미터

 

  • “/sys/kernel/debug/page_owner” 파일을 출력하여 모든 페이지들의 onwer 추적을 수행할 수 있다.
  • 커널 v3.19-rc1에서 추가되었다.

 

page_ext_init_flatmem-2

early_page_owner_param()

mm/page_owner.c

static int early_page_owner_param(char *buf)
{
        if (!buf)
                return -EINVAL;

        if (strcmp(buf, "on") == 0)
                page_owner_disabled = false;

        return 0;
}               
early_param("page_owner", early_page_owner_param);

“page_owner=on” early 커널 파라메터를 사용하는 경우 페이지 owner 추적 기능을 사용한다.

 

need_page_owner()

mm/page_owner.c

static bool need_page_owner(void)
{
        if (page_owner_disabled)
                return false;

        return true;
}

page owner 추적 기능이 필요한지 여부를 반환한다.

 

mm/page_owner.c

static bool page_owner_disabled = true;

 

init_page_owner()

mm/page_owner.c

static void init_page_owner(void)
{
        if (page_owner_disabled)
                return;

        page_owner_inited = true;
        init_early_allocated_pages();
}

page owner 추적 기능이 사용되는 경우 할당 페이지를 초기화한다.

 

mm/page_owner.c

bool page_owner_inited __read_mostly;

 

init_early_allocated_pages()

mm/page_owner.c

static void init_early_allocated_pages(void)
{
        pg_data_t *pgdat;

        drain_all_pages(NULL);
        for_each_online_pgdat(pgdat)
                init_zones_in_node(pgdat);
}

Per-CPU Page Frame Cache를 버디 할당자로 다시 돌려보내고 노드안에 있는 zone을 초기화한다.

 

init_zones_in_node()

mm/page_owner.c

static void init_zones_in_node(pg_data_t *pgdat)
{
        struct zone *zone;
        struct zone *node_zones = pgdat->node_zones;
        unsigned long flags;
        
        for (zone = node_zones; zone - node_zones < MAX_NR_ZONES; ++zone) {
                if (!populated_zone(zone))
                        continue;

                spin_lock_irqsave(&zone->lock, flags);
                init_pages_in_zone(pgdat, zone);
                spin_unlock_irqrestore(&zone->lock, flags);
        }       
}

해당 노드에 대해 populate zone에 속한 페이지를 초기화한다.

 

init_pages_in_zone()

mm/page_owner.c

static void init_pages_in_zone(pg_data_t *pgdat, struct zone *zone)
{
        struct page *page;
        struct page_ext *page_ext;
        unsigned long pfn = zone->zone_start_pfn, block_end_pfn;
        unsigned long end_pfn = pfn + zone->spanned_pages;
        unsigned long count = 0;

        /* Scan block by block. First and last block may be incomplete */
        pfn = zone->zone_start_pfn;

        /*
         * Walk the zone in pageblock_nr_pages steps. If a page block spans
         * a zone boundary, it will be double counted between zones. This does
         * not matter as the mixed block count will still be correct
         */
        for (; pfn < end_pfn; ) {
                if (!pfn_valid(pfn)) {
                        pfn = ALIGN(pfn + 1, MAX_ORDER_NR_PAGES);
                        continue;
                }

                block_end_pfn = ALIGN(pfn + 1, pageblock_nr_pages);
                block_end_pfn = min(block_end_pfn, end_pfn);

                page = pfn_to_page(pfn);

                for (; pfn < block_end_pfn; pfn++) {
                        if (!pfn_valid_within(pfn))
                                continue;

                        page = pfn_to_page(pfn);

                        /*
                         * We are safe to check buddy flag and order, because
                         * this is init stage and only single thread runs.
                         */
                        if (PageBuddy(page)) {
                                pfn += (1UL << page_order(page)) - 1;
                                continue;
                        }

                        if (PageReserved(page))
                                continue;

                        page_ext = lookup_page_ext(page);

                        /* Maybe overraping zone */
                        if (test_bit(PAGE_EXT_OWNER, &page_ext->flags))
                                continue;

                        /* Found early allocated page */
                        set_page_owner(page, 0, 0);
                        count++;
                }
        }

        pr_info("Node %d, zone %8s: page owner found early allocated %lu pages\n",
                pgdat->node_id, zone->name, count);
}

zone에 구성된 페이지 중 onwer 설정이 필요한 경우 page_ext[]에서 해당 페이지를 찾아 owner 비트를 설정한다.

 

set_page_owner()

include/linux/page_owner.h

static inline void set_page_owner(struct page *page,
                        unsigned int order, gfp_t gfp_mask)
{
        if (likely(!page_owner_inited))
                return; 

        __set_page_owner(page, order, gfp_mask);
}

page_ext[]의 해당 페이지에 owner 비트를 설정한다.

 

__set_page_owner()

mm/page_owner.c

void __set_page_owner(struct page *page, unsigned int order, gfp_t gfp_mask)
{
        struct page_ext *page_ext = lookup_page_ext(page);
        struct stack_trace trace = {
                .nr_entries = 0,
                .max_entries = ARRAY_SIZE(page_ext->trace_entries),
                .entries = &page_ext->trace_entries[0],
                .skip = 3,
        };

        save_stack_trace(&trace);

        page_ext->order = order;
        page_ext->gfp_mask = gfp_mask;
        page_ext->nr_entries = trace.nr_entries;

        __set_bit(PAGE_EXT_OWNER, &page_ext->flags);
}

page_ext에 owner를 설정한다.

 

reset_page_owner()

include/linux/page_owner.h

static inline void reset_page_owner(struct page *page, unsigned int order)
{
        if (likely(!page_owner_inited))
                return;

        __reset_page_owner(page, order);
}

2^order 만큼의 페이지들에 대항하는 page_ext의 owner 비트를 clear한다.

 

__reset_page_owner()

mm/page_owner.c

void __reset_page_owner(struct page *page, unsigned int order)
{
        int i;
        struct page_ext *page_ext;

        for (i = 0; i < (1 << order); i++) {
                page_ext = lookup_page_ext(page + i);
                __clear_bit(PAGE_EXT_OWNER, &page_ext->flags);
        }
}

2^order 만큼의 페이지들에 대항하는 page_ext의 owner 비트를 clear한다.

 


page_owner 분석을 위한 덤프

$ cat /sys/kernel/debug/page_owner
Page allocated via order 0, mask 0x6212ca(GFP_HIGHUSER_MOVABLE|__GFP_NOWARN|__GFP_NORETRY)
PFN 262144 type Movable Block 512 type Movable Flags 0xfffc00000020836(referenced|uptodate|lru|activ
e|arch_1|mappedtodisk)
 get_page_from_freelist+0x1050/0x1e68
 __alloc_pages_nodemask+0x220/0xffc
 alloc_pages_current+0xac/0xe8
 __page_cache_alloc+0x130/0x138
 __do_page_cache_readahead+0x118/0x2c4
 filemap_fault+0x3d4/0x9a0
 ext4_filemap_fault+0x44/0x60
 __do_fault+0x7c/0x33c
 __handle_mm_fault+0x112c/0x1cd0
 handle_mm_fault+0x1b0/0x27c
 do_page_fault+0x290/0x4d8
 do_translation_fault+0x88/0x8c
 do_mem_abort+0x58/0xe8
 el0_da+0x20/0x24

Page allocated via order 0, mask 0x6212ca(GFP_HIGHUSER_MOVABLE|__GFP_NOWARN|__GFP_NORETRY)
PFN 262148 type Movable Block 512 type Movable Flags 0xfffc00000020836(referenced|uptodate|lru|activ
e|arch_1|mappedtodisk)
 get_page_from_freelist+0x1050/0x1e68
 __alloc_pages_nodemask+0x220/0xffc
 alloc_pages_current+0xac/0xe8
 __page_cache_alloc+0x130/0x138
 __do_page_cache_readahead+0x118/0x2c4
 filemap_fault+0x3d4/0x9a0
 ext4_filemap_fault+0x44/0x60
 __do_fault+0x7c/0x33c
 __handle_mm_fault+0x112c/0x1cd0
 handle_mm_fault+0x1b0/0x27c
 do_page_fault+0x290/0x4d8
 do_translation_fault+0x88/0x8c
 do_mem_abort+0x58/0xe8
 el0_da+0x20/0x24

...(생략)...

 

참고

 

디버그 메모리 -2- (Page Poisoning)

<kernel v5.0>

디버그 메모리 -2- (Page Poisoning)

free 페이지에 poison 표식을 한 후 페이지 할당 시마다 poison 표식을 확인하여 다음과 같이 오염된 경우 이에 대한 메시지 출력과 메모리 덤프 및 스택 트레이싱을 출력한다. 출력 내용은 rate limit이 걸려있으므로 5초 간격 이내에 10회까지로 출력을 제한한다.

  • 비트 하나만 바뀐 경우
    • “pagealloc: single bit error”
  • 그 외
    • “pagealloc: memory corruption”

 

 

사용 조건

  • CONFIG_PAGE_POISONING 커널 옵션
  • CONFIG_PAGE_POISONING_NO_SANITY 커널 옵션을 사용하는 경우 poison 체크는 skip 한다.
    • CONFIG_HIBERNATION을 사용하면 항상 CONFIG_PAGE_POISONING_NO_SANITY 가 enable 된다. 따라서 poison check를 수행하려면 하이버네이션 기능을 꺼야한다.
  • “page_poison” 커널 파라미터

 

  • 이 기능은 커널 v4.12-rc1에서 page_ext 구조체를 사용하다 부트업에서 처리하는 것으로 바뀌었다.

 

Early 커널 파라메터로 설정

early_page_poison_param()

mm/page_poison.c

static int __init early_page_poison_param(char *buf)
{
        if (!buf)
                return -EINVAL;
        return strtobool(buf, &want_page_poisoning);
}
early_param("page_poison", early_page_poison_param);

 

mm/page_poison.c

static bool want_page_poisoning __read_mostly;

 

kernel_poison_pages()

이 함수가 호출되는 곳은 다음과 같다.

  • 페이지 할당 과정
    • post_alloc_hook() 함수에서 kernel_poison_pages(page, 1 << order, 1);와 같이 호출된다.
  • 페이지 회수 과정
    • free_pages_prepare() 함수에서 kernel_poison_pages(page, 1 << order, 0);와 같이 호출된다.

mm/page_poison.c

void kernel_poison_pages(struct page *page, int numpages, int enable)
{
        if (!page_poisoning_enabled())
                return;

        if (enable)
                unpoison_pages(page, numpages);
        else
                poison_pages(page, numpages);
}

@page 부터 @numpages 수 만큼의 페이지 프레임을 대상으로 @enable 여부에 따라 poison 표식을 하거나 제거한다. 제거할 때에는 poison 체크를 수행하여 해당 페이지의 침범 등의 이상 여부를 알아낸다.

  • 코드 라인 3~4에서 “page_poison” 커널 파라미터가 지정되지 않은 경우 함수를 빠져나간다.
  • 코드 라인 6~7에서 페이지 할당 과정에서 @enable이 1로 요청되는데, 이 때 요청한 페이지 프레임 내용을 모두 poison 값으로 채운다.
  • 코드 라인 8~9에서 페이지 회수 과정에서 @enable이 0으로 요청되는데, 이 때 요청한 페이지 프레임 내용을 조사하여 poison 값이 아닌 경우 해당 메모리 덤프 및 stack 트레이싱 출력을 수행한다.

 


메모리 할당 시 unpoison 과정에서 poison 표식 훼손 체크

unpoison_pages()

mm/page_poison.c

static void unpoison_pages(struct page *page, int n)
{
        int i;

        for (i = 0; i < n; i++)
                unpoison_page(page + i);
}

@page 페이지 프레임부터 @n 개 페이지 프레임 내용을 조사하여 poison 표식이 훼손된 경우 해당 메모리 덤프 및 stack 트레이싱 출력을 수행한다.

 

unpoison_page()

mm/page_poison.c

static void unpoison_page(struct page *page)
{
        void *addr;

        addr = kmap_atomic(page);
        /*
         * Page poisoning when enabled poisons each and every page
         * that is freed to buddy. Thus no extra check is done to
         * see if a page was posioned.
         */
        check_poison_mem(addr, PAGE_SIZE);
        kunmap_atomic(addr);
}

@page 페이지 프레임 내용을 조사하여 poison 표식이 훼손된 경우 해당 메모리 덤프 및 stack 트레이싱 출력을 수행한다.

 

check_poison_mem()

mm/page_poison.c

static void check_poison_mem(unsigned char *mem, size_t bytes)
{
        static DEFINE_RATELIMIT_STATE(ratelimit, 5 * HZ, 10);
        unsigned char *start;
        unsigned char *end;

        if (IS_ENABLED(CONFIG_PAGE_POISONING_NO_SANITY))
                return;

        start = memchr_inv(mem, PAGE_POISON, bytes);
        if (!start)
                return;

        for (end = mem + bytes - 1; end > start; end--) {
                if (*end != PAGE_POISON)
                        break;
        }

        if (!__ratelimit(&ratelimit))
                return;
        else if (start == end && single_bit_flip(*start, PAGE_POISON))
                pr_err("pagealloc: single bit error\n");
        else
                pr_err("pagealloc: memory corruption\n");

        print_hex_dump(KERN_ERR, "", DUMP_PREFIX_ADDRESS, 16, 1, start,
                        end - start + 1, 1);
        dump_stack();
}

@mem 주소부터 @bytes 만큼 메모리 내용을 조사하여 poison 표식이 훼손된 경우 해당 메모리 덤프 및 stack 트레이싱 출력을 수행한다.

  • 력 형태는 두 가지로 출력한다.
    • 1 비트만 훼손된 경우 “pagealloc: single bit error” 메시지 출력
    • 그 외의 경우 “pagealloc: memory corruption” 메시지 출력

 

single_bit_flip()

mm/page_poison.c

static bool single_bit_flip(unsigned char a, unsigned char b)
{
        unsigned char error = a ^ b;

        return error && !(error & (error - 1));
}

주어진 @a 와 @b 바이트에서 1개 비트만 다른 경우 1을 반환한다.

 


메모리 회수 시 poison  표식

poison_pages()

mm/page_poison.c

static void poison_pages(struct page *page, int n)
{
        int i;

        for (i = 0; i < n; i++)
                poison_page(page + i);
}

@page에 해당하는 페이지 프레임부터 n개 페이지 프레임의 내용을 PAGE_POISON(0xaa) 값으로 표식한다.

 

poison_page()

mm/page_poison.c

static void poison_page(struct page *page)
{
        void *addr = kmap_atomic(page);

        memset(addr, PAGE_POISON, PAGE_SIZE);
        kunmap_atomic(addr);
}

@page에 해당하는 한 페이지 프레임의 내용을 PAGE_POISON(0xaa) 값으로 표식한다.

  • CONFIG_PAGE_POISONING_ZERO 커널 옵션을 사용하는 경우 PAGE_POISON 값으로 0x00을 사용할 수도 있다.

 

참고

디버그 메모리 -1- (Page Alloc)

<kernel v5.0>

디버그 메모리 -1- (Page Alloc)

minorder 미만의 페이지들 할당 시마다 별도의 가드 페이지를 준비하고, 이 페이지에 접근(R/W)하는 경우 예외를 발생시키기 위해 사용된다. 버디 시스템에서 minorder 미만의 페이지들에 대해 이에 대응하는 버디 페이지들을 별도의 가드 페이지들로 활용하기 위해  guard 플래그를 가진채로 free_list에서 제외된다. 이 guard 페이지들은 유저 가상 주소에 매핑하지 않으므로 접근할 수 없다.

 

사용 조건

  • CONFIG_PAGE_EXTENSION  & CONFIG_DEBUG_PAGEALLOC 커널 옵션
    • 커널 v5.3-rc1 부터는 CONFIG_PAGE_EXTENSION 없이도 동작한다.
  • CONFIG_HIBERNATION과 같이 사용할 수 없다.
  • “debug_pagealloc=on” 커널 파라미터
  • “debug_guardpage_minorder=<minorder>” 커널 파라미터
    • minorder는 디폴트 값으로 가드 페이지가 동작하지 않는 0 이고, 최대 MAX_ORDER의 절반까지 지정될 수 있다.
    • 이 값 이상의 페이지들은 가드 페이지들을 생성하지 않는다.
    • 이 값이 크면 클 수록 메모리 낭비가 커진다.

 

 

다음 그림은 가드 페이지를 사용하지 않는 보통 상태의 버디 시스템에서 페이지 할당을 보여준다.

 

다음 그림은 가드 페이지를 사용할 때 할당되는 페이지와 인접한 추가 가드 페이지들을 보여준다.

 

need_debug_guardpage()

mm/page_alloc.c

static bool need_debug_guardpage(void)
{
        /* If we don't use debug_pagealloc, we don't need guard page */
        if (!debug_pagealloc_enabled())
                return false; 

        if (!debug_guardpage_minorder())
                return false;
        return true;
}

가드 페이지 디버깅이 필요한지 여부를 반환한다.

  • 디버그용 guard 페이지를 사용하지 않는 경우에도 false를 반환한다.

 

debug_pagealloc_enabled()

include/linux/mm.h

static inline bool debug_pagealloc_enabled(void) 
{       
        return _debug_pagealloc_enabled;
}

페이지 할당 디버깅 기능 사용 여부를 반환한다.

 

debug_guardpage_minorder()

include/linux/mm.h

static inline unsigned int debug_guardpage_minorder(void)
{
        return _debug_guardpage_minorder;
}

디버그용 guard 페이지로 설정된 최소 order 값을 반환한다.

  • “debug_guardpage_minorder=” 커널 파라메터 값은 1~MAX_ORDER의 절반까지 가능하다.
    • MAX_ORDER가 11인 경우 1~5까지 설정 가능하다.

 

init_debug_guardpage()

mm/page_alloc.c

static void init_debug_guardpage(void)
{                           
        if (!debug_pagealloc_enabled())
                return;

        _debug_guardpage_enabled = true; 
}

가드 페이지 디버깅 기능을 enable 한다.

 

page_is_guard()

include/linux/mm.h

#ifdef CONFIG_DEBUG_PAGEALLOC
static inline bool page_is_guard(struct page *page)
{       
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return false;

        page_ext = lookup_page_ext(page); 
        return test_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);
}
#endif

페이지에 대항하는 page_ext의 guard 플래그 비트가 설정되었는지 여부를 반환한다.

 

clear_page_guard()

mm/page_alloc.c

static inline void clear_page_guard(struct zone *zone, struct page *page,
                                unsigned int order, int migratetype)
{
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return;

        page_ext = lookup_page_ext(page);
        __clear_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);

        set_page_private(page, 0);
        if (!is_migrate_isolate(migratetype))
                __mod_zone_freepage_state(zone, (1 << order), migratetype);
}

페이지에 대항하는 page_ext의 guard 플래그 비트를 clear 하고 _private에 0을 대입한다. zone에 대한 free page stat도 2^order 만큼 증가시킨다.

 


버디 페이지 expand 시 Guard 페이지로 사용

expand()

mm/page_alloc.c

/*
 * The order of subdivision here is critical for the IO subsystem.
 * Please do not alter this order without good reasons and regression
 * testing. Specifically, as large blocks of memory are subdivided,
 * the order in which smaller blocks are delivered depends on the order
 * they're subdivided in this function. This is the primary factor
 * influencing the order in which pages are delivered to the IO
 * subsystem according to empirical testing, and this is also justified
 * by considering the behavior of a buddy system containing a single
 * large block of memory acted on by a series of small allocations.
 * This behavior is a critical factor in sglist merging's success.
 *
 * -- nyc
 */
static inline void expand(struct zone *zone, struct page *page,
        int low, int high, struct free_area *area,
        int migratetype)
{
        unsigned long size = 1 << high;

        while (high > low) {
                area--;
                high--;
                size >>= 1;
                VM_BUG_ON_PAGE(bad_range(zone, &page[size]), &page[size]);

                /*
                 * Mark as guard pages (or page), that will allow to
                 * merge back to allocator when buddy will be freed.
                 * Corresponding page table entries will not be touched,
                 * pages will stay not present in virtual address space
                 */
                if (set_page_guard(zone, &page[size], high, migratetype))
                        continue;

                list_add(&page[size].lru, &area->free_list[migratetype]);
                area->nr_free++;
                set_page_order(&page[size], high);
        }
}

버디 시스템에서 요청한 order 페이지가 부족한 경우 더 큰 order 페이지를 확장하여 사용할 수 있는데 이 때 호출되어 동작하는 expand() 함수이다. 가드 페이지와 관련된 코드만을 잠시 살펴본다.

  • 코드 라인 10에서 size에는 할당할 페이지의 다음 페이지인 버디 페이지를 가드 페이지로 사용하기 위해 얻어온다.
  • 코드 라인 19~20에서 guard 페이지를 사용하는 경우 guard 페이지 플래그를 설정한다. 그 후 가드 페이지는 free_list에 추가하지 않기 위해 skip 한다.

 

page_is_guard()

mm/page_alloc.c

static inline bool page_is_guard(struct page *page)
{
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return false;

        page_ext = lookup_page_ext(page);
        if (unlikely(!page_ext))
                return false;

        return test_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);
}

@page가 가드 페이지인지 여부를 알아온다.

 

가드 페이지 지정

set_page_guard()

mm/page_alloc.c

static inline bool set_page_guard(struct zone *zone, struct page *page,
                                unsigned int order, int migratetype)
{
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return false;

        if (order >= debug_guardpage_minorder())
                return false;

        page_ext = lookup_page_ext(page);
        if (unlikely(!page_ext))
                return false;

        __set_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);

        INIT_LIST_HEAD(&page->lru);
        set_page_private(page, order);
        /* Guard pages are not available for any usage */
        __mod_zone_freepage_state(zone, -(1 << order), migratetype);

        return true;
}

@page를 guard 페이지로 지정한다. 정상적으로 설정한 경우 true를 반환한다.

  • guard 페이지는 private 멤버에 order 값이 지정되고, 확장 페이지에 guard 플래그가 설정된다.

 

가드 페이지 지정 해제

clear_page_guard()

mm/page_alloc.c

static inline void clear_page_guard(struct zone *zone, struct page *page,
                                unsigned int order, int migratetype)
{
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return;

        page_ext = lookup_page_ext(page);
        if (unlikely(!page_ext))
                return;

        __clear_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);

        set_page_private(page, 0);
        if (!is_migrate_isolate(migratetype))
                __mod_zone_freepage_state(zone, (1 << order), migratetype);
}

@page가 guard 페이지인 경우 취소한다.

  • private 멤버에 0 값이 지정되고, 확장 페이지에 guard 플래그를 클리어한다.

 

Early 커널 파라메터

early_debug_pagealloc()

mm/page_alloc.c

static int __init early_debug_pagealloc(char *buf)
{
        if (!buf)
                return -EINVAL;

        if (strcmp(buf, "on") == 0)
                _debug_pagealloc_enabled = true;
        
        return 0;
}       
early_param("debug_pagealloc", early_debug_pagealloc);

“debug_pagealloc=on” early 커널 파라메터를 사용하는 경우 페이지 할당 디버깅 기능을 사용한다.

 

커널 Tools

tools/vm에서 make를 사용하여 빌드하면 3개의 유틸리티가 생성된다.

  • page-types
    • 참조하는 proc 인터페이스들
      • /proc/<pid>/pagemap
      • /proc/<pid>maps
      • /proc/self/pagemap
      • /proc/kpageflags
      • /proc/kpagecount
      • /proc/kpagecgroup
      • /sys/kernel/mm/page_idle/bitmap
  • page_owner_sort
  • slabinfo

 

page-types

이 유틸리티를 통해 페이지 타입 유형들을 알아볼 수 있다.

  • 옵션을 사용하여 특정 가상 주소, 태스크, 파일에 관여된 페이지 타입들을 관찰할 수 있다.
$ tools/vm/page-types --help
page-types [options]
            -r|--raw                   Raw mode, for kernel developers
            -d|--describe flags        Describe flags
            -a|--addr    addr-spec     Walk a range of pages
            -b|--bits    bits-spec     Walk pages with specified bits
            -c|--cgroup  path|@inode   Walk pages within memory cgroup
            -p|--pid     pid           Walk process address space
            -f|--file    filename      Walk file address space
            -i|--mark-idle             Mark pages idle
            -l|--list                  Show page details in ranges
            -L|--list-each             Show page details one by one
            -C|--list-cgroup           Show cgroup inode for pages
            -M|--list-mapcnt           Show page map count
            -N|--no-summary            Don't show summary info
            -X|--hwpoison              hwpoison pages
            -x|--unpoison              unpoison pages
            -F|--kpageflags filename   kpageflags file to parse
            -h|--help                  Show this usage message
flags:
            0x10                       bitfield format, e.g.
            anon                       bit-name, e.g.
            0x10,anon                  comma-separated list, e.g.
addr-spec:
            N                          one page at offset N (unit: pages)
            N+M                        pages range from N to N+M-1
            N,M                        pages range from N to M-1
            N,                         pages range from N to end
            ,M                         pages range from 0 to M-1
bits-spec:
            bit1,bit2                  (flags & (bit1|bit2)) != 0
            bit1,bit2=bit1             (flags & (bit1|bit2)) == bit1
            bit1,~bit2                 (flags & (bit1|bit2)) == bit1
            =bit1,bit2                 flags == (bit1|bit2)
bit-names:
          locked              error         referenced           uptodate
           dirty                lru             active               slab
       writeback            reclaim              buddy               mmap
       anonymous          swapcache         swapbacked      compound_head
   compound_tail               huge        unevictable           hwpoison
          nopage                ksm                thp            balloon
       zero_page          idle_page            pgtable           reserved(r)
         mlocked(r)    mappedtodisk(r)         private(r)       private_2(r)
   owner_private(r)            arch(r)        uncached(r)       softdirty(r)
       readahead(o)       slob_free(o)     slub_frozen(o)      slub_debug(o)
            file(o)            swap(o)  mmap_exclusive(o)
                                   (r) raw mode bits  (o) overloaded bits

 

$ tools/vm/page-types
             flags      page-count       MB  symbolic-flags                     long-symbolic-flags
0x0000000000000000           92360      360  ___________________________________________
0x0000000000100000             512        2  ____________________n______________________        nopage
0x0000000001000000               1        0  ________________________z__________________        zero_page
0x0000000000000014              10        0  __R_D______________________________________        referenced,dirty
0x0000000000000020              18        0  _____l_____________________________________        lru
0x0000000000000024              16        0  __R__l_____________________________________        referenced,lru
0x0000000000000028          102116      398  ___U_l_____________________________________        uptodate,lru
0x0000000000004028            1536        6  ___U_l________b____________________________        uptodate,lru,swapbacked
0x000000000000002c           90364      352  __RU_l_____________________________________        referenced,uptodate,lru
0x0000000000004030           11454       44  ____Dl________b____________________________        dirty,lru,swapbacked
0x000000000000403c           15846       61  __RUDl________b____________________________        referenced,uptodate,dirty,lru,swapbacked
0x0000000000000040               1        0  ______A____________________________________        active
0x0000000000000060            7293       28  _____lA____________________________________        lru,active
0x0000000000000064           19994       78  __R__lA____________________________________        referenced,lru,active
0x0000000000000068          340912     1331  ___U_lA____________________________________        uptodate,lru,active
0x000000000000006c          233190      910  __RU_lA____________________________________        referenced,uptodate,lru,active
0x0000000000000074               4        0  __R_DlA____________________________________        referenced,dirty,lru,active
0x0000000000004078           16146       63  ___UDlA_______b____________________________        uptodate,dirty,lru,active,swapbacked
0x0000000000000078               1        0  ___UDlA____________________________________        uptodate,dirty,lru,active
0x000000000000407c              91        0  __RUDlA_______b____________________________        referenced,uptodate,dirty,lru,active,swapbacked
0x000000000000007c               1        0  __RUDlA____________________________________        referenced,uptodate,dirty,lru,active
0x0000000000000080           37452      146  _______S___________________________________        slab
0x0000000000000228            2202        8  ___U_l___I_________________________________        uptodate,lru,reclaim
0x0000000000000268              26        0  ___U_lA__I_________________________________        uptodate,lru,active,reclaim
0x0000000000000400             965        3  __________B________________________________        buddy
0x0000000000000800               1        0  ___________M_______________________________        mmap
0x0000000000000804               1        0  __R________M_______________________________        referenced,mmap
0x0000000000000828             232        0  ___U_l_____M_______________________________        uptodate,lru,mmap
0x0000000000004828               8        0  ___U_l_____M__b____________________________        uptodate,lru,mmap,swapbacked
0x0000000000004838            4600       17  ___UDl_____M__b____________________________        uptodate,dirty,lru,mmap,swapbacked
0x000000000000483c               2        0  __RUDl_____M__b____________________________        referenced,uptodate,dirty,lru,mmap,swapbacked
0x0000000000000868             173        0  ___U_lA____M_______________________________        uptodate,lru,active,mmap
0x000000000000086c            4925       19  __RU_lA____M_______________________________        referenced,uptodate,lru,active,mmap
0x0000000000004878               1        0  ___UDlA____M__b____________________________        uptodate,dirty,lru,active,mmap,swapbacked
0x0000000000005048               3        0  ___U__A_____a_b____________________________        uptodate,active,anonymous,swapbacked
0x0000000000005848               2        0  ___U__A____Ma_b____________________________        uptodate,active,mmap,anonymous,swapbacked
0x0000000000005868           33295      130  ___U_lA____Ma_b____________________________        uptodate,lru,active,mmap,anonymous,swapbacked
0x000000000000586c              54        0  __RU_lA____Ma_b____________________________        referenced,uptodate,lru,active,mmap,anonymous,swapbacked
             total         1015808     3968

 

특정 유저 태스크(–pid 옵션)가 사용한 페이지 타입들을 보여준다.

$ tools/vm/page-types --pid 959
             flags      page-count       MB  symbolic-flags                     long-symbolic-flags
0x0000000000000800               1        0  ___________M_______________________________        mmap
0x0000000000000804               1        0  __R________M_______________________________        referenced,mmap
0x000000000000086c            1271        4  __RU_lA____M_______________________________        referenced,uptodate,lru,active,mmap
0x0000000000005868             311        1  ___U_lA____Ma_b____________________________        uptodate,lru,active,mmap,anonymous,swapbacked
0x000000000000586c               1        0  __RU_lA____Ma_b____________________________        referenced,uptodate,lru,active,mmap,anonymous,swapbacked
             total            1585        6

 

특정 파일(–file 옵션)에 사용한 페이지 타입들을 보여준다.

$ tools/vm/page-types --file abc.txt
             flags      page-count       MB  symbolic-flags                     long-symbolic-flags
0x0000000000000068               1        0  ___U_lA____________________________________        uptodate,lru,active
             total               1        0

 

참고