<kernel v5.0>



  • mem_map은 모든 물리 메모리 페이지 프레임에 대한 정보를 담아둔 page 구조체 배열이다.
    • 처음 리눅스에서는 unsigned short 타입의 참조 카운터 배열로 시작하다가 점점 필요 멤버가 늘어나 오늘날의 page 구조체가 되었다.
  • NUMA 시스템과 UMA 시스템에서의 접근 방법이 다르고 메모리 모델별로도 접근 방법이 다르다.
  • mem_map의 초기화 함수 경로는 아키텍처 및 커널 설정마다 다르다.
    • arm with flatmem
    • arm with sparsemem
      • setup_arch() → paging_init() → bootmem_init()  → sparse_init()
    • arm64 with sparsemem
      • setup_arch() → bootmem_init() → sparse_init()



Flat Memory with mem_map

  • NEED_MULTIPLE_NODES 커널 옵션이 사용되지 않을 때 *mem_map 포인터 변수는 하나의 page[] 구조체 배열을 가리킨다.
  • FLAT_NODE_MEM_MAP 커널 옵션을 사용하는 경우 contig_page_data.node_mem_map를 통해서 page[] 구조체 배열을 가리킨다.



Discontiguous Memory with mem_map

  • node_data[].node_mem_map를 통해서 page[] 구조체 배열을 가리킨다.
  • x86_32에서는 섹션 to 노드 매핑을 하는 테이블을 별도로 구현하여 사용한다.
  • 실제 사용하는 메모리에 대해서만 관리하고 hole 영역에 대해서는 전혀 관리하지 않는다.



Sparse Memory with mem_map

  • 하나의 mem_section 구조체는 PAGES_PER_SECTION 갯 수 만큼의 page[] 배열을 가리키고 관리한다.
  • hole을 포함한 메모리 전체를 mem_section을 통해 관리한다.
  • hole 영역에 대해서도 mem_section 테이블에 엔트리가 존재하고 멤버 변수 node_mem_map 값을 null로 하는 것으로 미사용 섹션을 의미한다. 그 외 사용되는 메모리의 관리는 mem_section[]과 page[] 구조체를 통해 관리된다..
  • 섹션의 크기는 페이지 테이블에서 사용하는 섹션 크기가 아니라 Sparsemem에서 사용하는 크기로 수십MB ~ 수GB 크기로 online/offline(pluglable memory)을 관리하기 위한 최소 크기 단위이므로 혼동되지 않도록 한다.
  • 메모리 할당 관리를 위해 mem_section 구조체의 구현이 두 개로 나뉘어 있다.
      • mem_section[][] 이중 배열로 만들어 컴파일 타임에 각각의 page[] 배열을 가리키고 관리한다.
      • mem_section[][] 이중 배열은 hole을 포함한 모든 메모리를 관리하므로 hole의 size가 적절한 경우 별도의 dynamic 할당 없이 사용하므로 간편하나 hole size가 매우 큰 경우에는 mem_section 구조체 x hole 섹션 수 만큼의 메모리 낭비를 하므로 적합하지 않다.
      • *mem_section[] 포인터 배열만 컴파일 타임에 만들어지고 실제 mem_section[] 배열은 별도로 필요한 만큼 메모리를 할당 받아 page[] 배열을 가리키고 관리한다.
      • * mem_section[] 포인터 배열을 사용하여 hole size가 매우 큰 경우를 대비하여 메모리 낭비를 줄일 수 있도록 설계되었다.
      • page 구조체의 flags 멤버변수에 node 번호를 기록하지 않는 경우 node 번호를 별도로 섹션에 대해 1:1로 매핑하여 저장한다.
      • 32비트 시스템에서 비트 자리가 부족하여 노드 번호를 기록하지 못하는 경우 사용


다음 그림은 SPARSEMEM_EXTREME을 사용하지 않는 것으로 32bit arm – Realview-PBX 보드의 사용예이다.


다음 그림은 SPARSEMEM_EXTREME을 사용하는 것으로 arm64 아키텍처에서 4G DRAM을 사용한 예이다.


page 디스크립터


모든 물리 메모리 페이지마다 하나의 페이지 디스크립터가 할당된다. 모든 메모리 페이지마다 생성되므로 사이즈에 민감하다. 따라서 사이즈를 최대한 줄이기위해 페이지 디스크립터내에서 관리되는 멤버들을 유니온 타입으로 묶어 사용하도록 설계되었다.


다음은 32bit 시스템에서 동작하는 page 디스크립터를 보여준다. (디폴트 옵션을 사용 시 36바이트이다.)


다음은 64bit 시스템에서 동작하는 page 디스크립터를 보여준다. (디폴트 옵션을 사용 시 64바이트이다.)


struct page

include/linux/mm_types.h -1/3-

 * Each physical page in the system has a struct page associated with
 * it to keep track of whatever it is we are using the page for at the
 * moment. Note that we have no way to track which tasks are using
 * a page, though if it is a pagecache page, rmap structures can tell us
 * who is mapping it.
 * If you allocate the page using alloc_pages(), you can use some of the
 * space in struct page for your own purposes.  The five words in the main
 * union are available, except for bit 0 of the first word which must be
 * kept clear.  Many users use this word to store a pointer to an object
 * which is guaranteed to be aligned.  If you use the same storage as
 * page->mapping, you must restore it to NULL before freeing the page.
 * If your page will not be mapped to userspace, you can also use the four
 * bytes in the mapcount union, but you must call page_mapcount_reset()
 * before freeing it.
 * If you want to use the refcount field, it must be used in such a way
 * that other CPUs temporarily incrementing and then decrementing the
 * refcount does not cause problems.  On receiving the page from
 * alloc_pages(), the refcount will be positive.
 * If you allocate pages of order > 0, you can use some of the fields
 * in each subpage, but you may need to restore some of their values
 * afterwards.
 * SLUB uses cmpxchg_double() to atomically update its freelist and
 * counters.  That requires that freelist & counters be adjacent and
 * double-word aligned.  We align all struct pages to double-word
 * boundaries, and ensure that 'freelist' is aligned within the
 * struct.
struct page {
        unsigned long flags;            /* Atomic flags, some possibly
                                         * updated asynchronously */
         * Five words (20/40 bytes) are available in this union.
         * WARNING: bit 0 of the first word is used for PageTail(). That
         * means the other users of this union MUST NOT use the bit to
         * avoid collision and false-positive PageTail().
        union {
                struct {        /* Page cache and anonymous pages */
                         * @lru: Pageout list, eg. active_list protected by
                         * zone_lru_lock.  Sometimes used as a generic list
                         * by the page owner.
                        struct list_head lru;
                        /* See page-flags.h for PAGE_MAPPING_FLAGS */
                        struct address_space *mapping;
                        pgoff_t index;          /* Our offset within mapping. */
                         * @private: Mapping-private opaque data.
                         * Usually used for buffer_heads if PagePrivate.
                         * Used for swp_entry_t if PageSwapCache.
                         * Indicates order in the buddy system if PageBuddy.
                        unsigned long private;
                struct {        /* slab, slob and slub */
                        union {
                                struct list_head slab_list;     /* uses lru */
                                struct {        /* Partial pages */
                                        struct page *next;
#ifdef CONFIG_64BIT
                                        int pages;      /* Nr of pages left */
                                        int pobjects;   /* Approximate count */
                                        short int pages;
                                        short int pobjects;
                        struct kmem_cache *slab_cache; /* not slob */
                        /* Double-word boundary */
                        void *freelist;         /* first free object */
                        union {
                                void *s_mem;    /* slab: first object */
                                unsigned long counters;         /* SLUB */
                                struct {                        /* SLUB */
                                        unsigned inuse:16;
                                        unsigned objects:15;
                                        unsigned frozen:1;


First 워드
  • flags
    • 페이지 플래그


2nd ~ 6th 워드 -1/2-

페이지 캐시 또는 anonymous 페이지

  • _lru
    • LRU 리스트에 연결될 때 사용한다.
  • *mapping
    • 유저 매핑 관련 포인터가 담기며 하위 2비트는 이의 용도를 구분하는 플래그로 사용한다.
      • 페이지 캐시로 사용되는 경우 address_space 구조체를 가리킨다.
        • non-lru movable 페이지들은 address_space 구조체 포인터에 PAGE_MAPPING_MOVABLE 플래그를 추가하여 관리한다.
          • 예) zram, balloon 드라이버
      • 유저용 가상 메모리 페이지(anonymous page)인 경우 CONFIG_KSM 커널 옵션 사용 여부에 따라 달라진다.
        • KSM 커널 옵션 사용하지 않을 때에는 PAGE_MAPPING_ANON(1) 플래그만 추가되고 anon 매핑 영역인 anon_vma 구조체 포인터를 가리킨다.
        • KSM 커널 옵션을 사용하는 경우에는 PAGE_MAPPING_ANON(1) 및 PAGE_MAPPING_MOVABLE(2) 플래그를 추가하고, KSM용 private 구조체 포인터를 가리킨다.
  • index
    • 매핑 영역안의 offset 값이 담긴다.
  • private
    • 매핑에 사용하는 private 데이터가 담긴다.
      • Private 페이지에서 buffer_heads를 위해 사용된다.
      • 스웝 페이지 캐시의 swp_entry_t를 위해 사용된다.
      • 버디 페이지의 order가 담긴다.

슬랩(slab, slob, slub) 페이지

  • slab_list
    • LRU 리스트에 연결될 때 사용한다.
  • *next
    • partial 페이지를 관리한다.
  • pages
    • partial 페이지 수가 담긴다.
      • 자신을 포함하여 뒤(next)로 연결되어 있는 slub page들의 수가 담긴다.
  • pobjects
    • 대략적인 object 수
      • 정확하지는 않지만 대략적으로 내 slub page를 포함한 다음(next) slub page들의 총 free object 수가 담긴다.
      • 이 카운터는 종종 free 되는 object들로 인해 정확히 산출되지 않는다
  • *slab_cache
    • 연결된 슬랩 캐시를 가리킨다.
  • *freelist
    • free 오브젝트들이 대기하는 리스트이다.
  • *s_mem
    • slab:의 first object를 가리킨다.
  • counters
    • 아래 32바이트를 한꺼번에 access할 때 사용한다.
    • inuse:16
      • 사용 중인 object 수.
    • objects:15
      • 슬랩이 관리하는 object 수
    • frozen:1
      • per-cpu로 관리하고 있는 슬랩 페이지 여부를 가리킨다.


include/linux/mm_types.h -2/3-

                struct {        /* Tail pages of compound page */
                        unsigned long compound_head;    /* Bit zero is set */

                        /* First tail page only */
                        unsigned char compound_dtor;
                        unsigned char compound_order;
                        atomic_t compound_mapcount;
                struct {        /* Second tail page of compound page */
                        unsigned long _compound_pad_1;  /* compound_head */
                        unsigned long _compound_pad_2;
                        struct list_head deferred_list;
                struct {        /* Page table pages */
                        unsigned long _pt_pad_1;        /* compound_head */
                        pgtable_t pmd_huge_pte; /* protected by page->ptl */
                        unsigned long _pt_pad_2;        /* mapping */
                        union {
                                struct mm_struct *pt_mm; /* x86 pgds only */
                                atomic_t pt_frag_refcount; /* powerpc */
                        spinlock_t *ptl;
                        spinlock_t ptl;
                struct {        /* ZONE_DEVICE pages */
                        /** @pgmap: Points to the hosting device page map. */
                        struct dev_pagemap *pgmap;
                        unsigned long hmm_data;
                        unsigned long _zd_pad_1;        /* uses mapping */

                /** @rcu_head: You can use this to free a page by RCU. */
                struct rcu_head rcu_head;
2nd ~ 6th 워드 -2/2-

Compound  tail 페이지들

참고로 Compound 페이지의 헤드 페이지(page[0])에는 PG_head 플래그가 설정된다.

  • compound_head
    • compound 페이지의 헤더가 아닌 모든 tail 페이지에서 compound 헤더 페이지  디스크립터 포인터를 담고, bit0를 1로 설정한다.
  • compound_dtor
    • 첫 번째 tail 페이지(page[1])에 compound 페이지 소멸자 구분 id를 담는다.
    • 다음과 같은 compound 페이지 소멸자 id들 중 하나를 담는다.
  • compound_order
    • 첫 번째 tail 페이지(page[1])에 compound 페이지의 order가 담긴다.
  • compound_mapcount
    • 첫 번째 tail 페이지(page[1])에 매핑 카운트 수가 담긴다.
  • _compound_pad_1
    • 사용하지 않는다.
  • _compound_pad_2
    • 사용하지 않는다.
  • deferred_list

페이지 테이블(pgd, pud, pmd, pte)용 페이지

  • _pt_pad_1
    • 사용하지 않는다.
  • pmd_huge_pte
    • huge용 pte 테이블을 가리키는 page 디스크립터 포인터가 담긴다.
  • _pt_pad_2
    • 사용하지 않는다.
  • *pt_mm
    • x86 pgds only
  • *pt_frag_refcount
    • powerpc only
  • *ptl or ptl
    • 페이지 테이블 spinlock이다.
    • spinlock_t 사이즈가 unsigned long 단위에 포함되는 경우 ptl을 사용하고 그렇지 않은 경우 spinlock_t를 할당하고 이를 가리킬 때 *ptl을 사용한다.

존 디바이스 페이지

  • *pgmap
    • 존 디바이스를 위한 dev_pagemap 구조체 포인터가 담긴다.
  • hmm_data
    • hmm 디바이스 메모리용 드라이버 데이터가 담긴다.
  • _zd_pad_1
    • 사용하지 않는다.


include/linux/mm_types.h -3/3-

        union {         /* This union is 4 bytes in size. */
                 * If the page can be mapped to userspace, encodes the number
                 * of times this page is referenced by a page table.
                atomic_t _mapcount;

                 * If the page is neither PageSlab nor mappable to userspace,
                 * the value stored here may help determine what this page
                 * is used for.  See page-flags.h for a list of page types
                 * which are currently stored here.
                unsigned int page_type;

                unsigned int active;            /* SLAB */
                int units;                      /* SLOB */

        /* Usage count. *DO NOT USE DIRECTLY*. See page_ref.h */
        atomic_t _refcount;

        struct mem_cgroup *mem_cgroup;

         * On machines where all RAM is mapped into kernel address space,
         * we can simply calculate the virtual address. On machines with
         * highmem some memory is mapped into kernel virtual memory
         * dynamically, so we need a place to store that address.
         * Note that this field could be 16 bits on x86 ... 😉
         * Architectures with slow multiplication can define
         * WANT_PAGE_VIRTUAL in asm/page.h
#if defined(WANT_PAGE_VIRTUAL)
        void *virtual;                  /* Kernel virtual address (NULL if
                                           not kmapped, ie. highmem) */
#endif /* WANT_PAGE_VIRTUAL */

        int _last_cpupid;
} _struct_page_alignment;
  • _mapcount
    • 매핑 카운트
    • 여러 개의 페이지 테이블에서 이 페이지를 매핑하는 경우 매핑 카운터가 증가된다.
      • 공유 페이지를 여러 유저 프로세스에서 공유하는 경우 다중 매핑이 된다.
  • page_type
    • 유저 매핑된 페이지가 아니고 슬랩 페이지를 제외한 커널 페이지 타입을 지정한다.
    • 다음과 같은 페이지 타입이저장된다.
      • PG_buddy
      • PG_ballon
      • PG_kmemcg
      • PG_table
  • active
    • slab: 할당자에서 사용된다.
  • units
    • slob: 할당자에서 사용된다.
  • _refcount
    • 참조 카운터로 사용자가 직접 액세스하면 안된다.
  • *mem_cgroup
    • 메모리 cgroup을 사용할 때 해당 mem_cgroup 구조체 포인터를 지정한다.
  • *virtual
    • highmem을 사용하는 32비트 시스템의 특정 아키텍처에서 highmem 페이지가 커널 주소 공간에 매핑된 경우 커널 주소 공간의 가상 주소가 담긴다.
    • ARM32의 경우 HASHED_PAGE_VIRTUAL을 사용한다.
      • highmem 페이지를 매핑 시 page_address_map 구조체를 별도로 할당하여 사용하므로 이 멤버를 사용하지 않는다.
  • _last_cpupid
    • NUMA 밸런싱을 위해 사용되며 페이지의 플래그에 cpu 및 pid를 담지 못하는 경우 이 멤버를 사용하여 마지막 사용한 cpu 및 pid를 담는다.


페이지 플래그

 * Various page->flags bits:
 * PG_reserved is set for special pages, which can never be swapped out. Some
 * of them might not even exist...
 * The PG_private bitflag is set on pagecache pages if they contain filesystem
 * specific data (which is normally at page->private). It can be used by
 * private allocations for its own usage.
 * During initiation of disk I/O, PG_locked is set. This bit is set before I/O
 * and cleared when writeback _starts_ or when read _completes_. PG_writeback
 * is set before writeback starts and cleared when it finishes.
 * PG_locked also pins a page in pagecache, and blocks truncation of the file
 * while it is held.
 * page_waitqueue(page) is a wait queue of all tasks waiting for the page
 * to become unlocked.
 * PG_uptodate tells whether the page's contents is valid.  When a read
 * completes, the page becomes uptodate, unless a disk I/O error happened.
 * PG_referenced, PG_reclaim are used for page reclaim for anonymous and
 * file-backed pagecache (see mm/vmscan.c).
 * PG_error is set to indicate that an I/O error occurred on this page.
 * PG_arch_1 is an architecture specific page state bit.  The generic code
 * guarantees that this bit is cleared for a page when it first is entered into
 * the page cache.
 * PG_hwpoison indicates that a page got corrupted in hardware and contains
 * data with incorrect ECC bits that triggered a machine check. Accessing is
 * not safe since it may cause another machine check. Don't touch!

 * Don't use the *_dontuse flags.  Use the macros.  Otherwise you'll break
 * locked- and dirty-page accounting.
 * The page flags field is split into two parts, the main flags area
 * which extends from the low bits upwards, and the fields area which
 * extends from the high bits downwards.
 *  | FIELD | ... | FLAGS |
 *  N-1           ^       0
 *               (NR_PAGEFLAGS)
 * The fields area is reserved for fields mapping zone, node (for NUMA) and
 * SPARSEMEM section (for variants of SPARSEMEM that require section ids like
enum pageflags {
        PG_locked,              /* Page is locked. Don't touch. */
        PG_waiters,             /* Page has waiters, check its waitqueue. Must be bit #7 and in the
same byte as "PG_locked" */
        PG_owner_priv_1,        /* Owner use. If pagecache, fs may use*/
        PG_private,             /* If pagecache, has fs-private data */
        PG_private_2,           /* If pagecache, has fs aux data */
        PG_writeback,           /* Page is under writeback */
        PG_head,                /* A head page */
        PG_mappedtodisk,        /* Has blocks allocated on-disk */
        PG_reclaim,             /* To be reclaimed asap */
        PG_swapbacked,          /* Page is backed by RAM/swap */
        PG_unevictable,         /* Page is "unevictable"  */
        PG_mlocked,             /* Page is vma mlocked */
        PG_uncached,            /* Page has been mapped as uncached */
        PG_hwpoison,            /* hardware poisoned page. Don't touch */
#if defined(CONFIG_IDLE_PAGE_TRACKING) && defined(CONFIG_64BIT)

        /* Filesystems */
        PG_checked = PG_owner_priv_1,

        /* SwapBacked */
        PG_swapcache = PG_owner_priv_1, /* Swap page: swp_entry_t in private */

        /* Two page bits are conscripted by FS-Cache to maintain local caching
         * state.  These bits are set on pages belonging to the netfs's inodes
         * when those inodes are being locally cached.
        PG_fscache = PG_private_2,      /* page backed by cache */

        /* XEN */
        /* Pinned in Xen as a read-only pagetable page. */
        PG_pinned = PG_owner_priv_1,
        /* Pinned as part of domain save (see xen_mm_pin_all()). */
        PG_savepinned = PG_dirty,
        /* Has a grant mapping of another (foreign) domain's page. */
        PG_foreign = PG_owner_priv_1,

        /* SLOB */
        PG_slob_free = PG_private,

        /* Compound pages. Stored in first tail page's flags */
        PG_double_map = PG_private_2,

        /* non-lru isolated movable page */
        PG_isolated = PG_reclaim,



6 thoughts to “Mem_map”

  1. 제가 정확히 이해를 못한것 같은데요.
    page 구조체를 알고 있을때 해당 page 구조체가 가리키는 physical memory, 즉 page frame #는 어떻게 알수 있는지 궁금합니다.

  2. 안녕하세요? 문c 블로그의 문영일입니다.

    page_to_pfn() API를 사용하여 해당 페이지를 가리키는 page 구조체 포인터로 pfn(page frame number)을 알아올 수 있습니다.
    반대의 역할을 수행하는 API는 pfn_to_page() API 입니다.


  3. 안녕하세요 좋은글 잘 읽어보고 있습니다.
    mm-7b, mm-9a 그림이 없습니다.

  4. 안녕하세요. 혹시 리눅스 부팅 시에 메모리가 어떻게 초기화 되는지 알 수 있을까요? (mem_map 배열 등.) 저런 배열을 할당하려 해도 메모리 할당 정책이 필요할텐데.. 맨 처음에 어떻게 초기화 시키는지가 궁금합니다.

    1. 안녕하세요?

      리눅스 부팅 후 사용되는 대표적인 메모리 할당 시스템은 buddy, slab, per-cpu, cma, … 등이 있습니다.
      이를 초기화하여 운용하기 전에는 memblock을 사용하여 영역 정보만을 관리하고 있습니다.

      메모리 할당자를 사용하기 전에 memblock을 통해 미리 필요한 영역을 reserve하여 등록하고, 사용할 때에는 원시적으로 직접 해당 주소 영역에 직접 기록하는 방법을 사용합니다. 그런 후 추후 페이지 할당자인 buddy 시스템을 준비할 때 그 reserve 영역을 피한 나머지 메모리 영역만 buddy 시스템에 free 페이지로 등록하여 사용합니다.


댓글 남기기

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