Memory Model -3- (Sparse Memory)

<kernel v5.15>

Sparse memory 모델은 섹션 메모리 단위로 mem_section 구조체를 통해 다음과 같은 자료를 관리한다.

  • mem_map
  • usemap (페이지블럭)

 

섹션

Sparse memory 모델에서 섹션은 메모리의 online/offlline(hotplug memory)을 관리하는 최소 메모리 크기 단위이다. 전체 메모리를 섹션들로 나누어 사용하는데 적절한 섹션 사이즈로 나누어 사용하는데 아키텍처 마다 다르다.

  • 보통 섹션 크기는 수십MB~수GB를 사용한다.
    • arm64의 경우 디폴트 값으로 1G를 사용다가 최근 128M로 변경하였다.
    • 주의: 본문 그림 등에서는 섹션 크기를 1G로 사용한 예를 보여주고 있음.
    • 주의: 매핑 테이블에서 사용하는 섹션(2M) 용어와 다름.

 

섹션 배열 2 가지 관리 방법

섹션에서 mem_map을 관리하는 다음 두 가지 방법을 알아본다.

  • static
    • 컴파일 타임에 1단계 mem_section[] 배열을 결정하여 사용하는 방법이다.
    • 주로 32bit 시스템에서 섹션 수가 적을 때 사용한다.
  • extream
    • 런타임에 1단계 **mem_section[] 포인터 배열을 할당하고, 2 단계는 필요 시마다 mem_section[] 배열을 할당하여 사용하는 방법으로 메모리 낭비를 막는 방법이다.
    • 주로 64bit 시스템에서 섹션 수가 많을 때 사용한다.

 

CONFIG_SPARSEMEM_STATIC

32bit ARM에서 Sparse Memory를 사용하는 Realview-PBX 보드가 섹션당 256MB 크기로 구성된 사례를 사용한다.

  • Realview-PBX
    • 3개의 메모리
      • 256MB @ 0x00000000 -> PAGE_OFFSET
      • 512MB @ 0x20000000 -> PAGE_OFFSET + 0x10000000
      • 256MB @ 0x80000000 -> PAGE_OFFSET + 0x30000000
    • MAX_PHYSMEM_BITS=32 (4G 메모리 크기)
    • SECTION_SIZE_BITS=28 (256MB 섹션 크기)
    • PFN_SECTION_SHIFT=(SECTION_SIZE_BITS – PAGE_SHIFT)=16
    • SECTIONS_PER_ROOT=1
    • SECTIONS_SHIFT=(MAX_PHYSMEM_BITS – SECTION_SIZE_BITS)=4
    • NR_MEM_SECTIONS=2^SECTIONS_SHIFT=16
    • PAGES_PER_SECTION=2^PFN_SECTION_SHIFT=64K
    • PAGE_SECTION_MASK=(~(PAGES_PER_SECTION-1))=0xffff_0000

 

CONFIG_SPARSEMEM_EXTREME

64bit ARM에서 Sparse Memory를 사용하는 경우의 사례로 1GB 크기로 구성된 사례를 사용한다.

  • arm64
    • 2개의 메모리
      • 2GB @ 0x0_8000_0000
      • 2GB @ 0x8_0000_0000
  • MAX_PHYSMEM_BITS=48 (256 TB 메모리 크기)
  • SECTION_SIZE_BITS=30 (1GB 섹션 크기)
  • PFN_SECTION_SHIFT=(SECTION_SIZE_BITS – PAGE_SHIFT)=18
  • SECTIONS_PER_ROOT=(PAGE_SIZE / sizeof (struct mem_section))=256
  • SECTIONS_SHIFT=(MAX_PHYSMEM_BITS – SECTION_SIZE_BITS)=18
  • NR_MEM_SECTIONS=2^SECTIONS_SHIFT=256K
  • PAGES_PER_SECTION=2^PFN_SECTION_SHIFT=256K
  • PAGE_SECTION_MASK=(~(PAGES_PER_SECTION-1))=0xffff_ffff_fffc_0000

 

usemap

usemap은 메모리 회수 매커니즘 중 하나인 compaction에서 전체 메모리를 스캔할 때 사용되는 페이지 블럭 당 4비트로 구성된다.

  • compaction에서 빠른 스캔을 위해 블럭 단위(arm64 디폴트=2M)로 묶어 사용한다.
  • 페이지 블럭 당 사용되는 4비트는 3비트의 mobility(migratype) 속성과 1 비트의 skip 비트로 구성된다.

 

VMEMMAP

32bit arm 에서는 사용하지 않는다. CONFIG_SPARSEMEM_VMEMMAP 커널 옵션을 사용할 수 있는 arm64를 포함하는 일부 64비트 아키텍처에서 Flat Memory 모델처럼 빠르게 운용할 수 있다.

  • 64비트 시스템에서 vmemmap 매핑 공간이 별도로 구성되어 있어 그 영역에 section_mem_map으로 구성된 mem_map들을 매핑한다.
  • vmemmap을 사용하는 경우 노드별 메모리에 분산되어 있는 페이지 descriptor로 serial하게 접근할 수 있어 page_to_pfn() 또는 pfn_to_page() 함수의 성능을 빠르게 얻을 수 있다.

 

다음 그림은 vmemmap이 VMEMMAP_START 아래의 PFN #0 위치에 대응하는 page 구조체의 위치를 가리키는 예를 보여준다.

  • VMEMMAP_START는 실제 DRAM이 위치한 PFN에 대한 page 구조체를 가리키고,
  • vmemmap은 PFN#0에 대한 page 구조체를 가리킨다.
    • 주의: DRAM 시작 주소가 0이 아니면 vmemmap은 VMEMMAP_START 아래에 위치하게 된다. 이 때 아래에 위치한 vmemmap의 주소는 실제 mem_map이 매핑된 공간이 아니므로 커널이 이 공간을 액세스하면 fault 발생하여 시스템이 멈춘다.)

 


Sparse 메모리 모델 초기화

다음 순서도는 Sparse 메모리 초기화에 대한 로직으로 다음과 같은 일들을 한다.

  • 노드별 usemap을 할당하고 임시로 사용되는 usemap_map에 연결한다.
  • CONFIG_SPARSEMEM_VMEMMAP 커널 옵션을 사용하면 mem_map을 vmemmap에 매핑하여 사용한다.
  • CONFIG_SPARSEMEM_VMEMMAP 커널 옵션을 사용하지 않는 경우 노드마다 활성화된 섹션들의 mem_map을 가능하면 한꺼번에 할당하다. (이전 버전의 x86 커널에 적용했었던 CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER 커널 옵션과 유사)

 

다음 그림은 sparse_init() 에서 만들어지는 mem_map, usemap, mem_section[] 들을 보여준다.

 

sparse_init()

mm/sparse.c

/*
 * Allocate the accumulated non-linear sections, allocate a mem_map
 * for each and record the physical to section mapping.
 */
void __init sparse_init(void)
{
        unsigned long pnum_end, pnum_begin, map_count = 1;
        int nid_begin;

        memblocks_present();

        pnum_begin = first_present_section_nr();
        nid_begin = sparse_early_nid(__nr_to_section(pnum_begin));

        /* Setup pageblock_order for HUGETLB_PAGE_SIZE_VARIABLE */
        set_pageblock_order();

        for_each_present_section_nr(pnum_begin + 1, pnum_end) {
                int nid = sparse_early_nid(__nr_to_section(pnum_end));

                if (nid == nid_begin) {
                        map_count++;
                        continue;
                }
                /* Init node with sections in range [pnum_begin, pnum_end) */
                sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count);
                nid_begin = nid;
                pnum_begin = pnum_end;
                map_count = 1;
        }
        /* cover the last node */
        sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count);
        vmemmap_populate_print_last();
}

sparse 물리 메모리 모델을 사용하기 위해 페이지 디스크립터들이 집합되어 있는 mem_map들을 섹션 단위로 할당하고 관리하기 위해 초기화하는 과정을 알아본다.
분산된 메모리를 섹션 단위로 관리하기 위해 각 섹션이 mem_map과 usemap을 할당하여 관리할 수 있도록 한다. usemap은 페이지 블록 단위를 결정한 후 페이지 블록당 4비트의 비트맵으로 mobility 특성을 관리한다.

  • 코드 라인 6에서 memblock에 등록된 메모리들을 모두 mem_section 연결하고, 섹션별로 활성화시킨다.
  • 코드 라인 8~9에서 시작 섹션 번호와 이에 대한 nid를 알아온다.
  • 코드 라인 12에서 전체 메모리에 대해 페이지 블록 크기 단위로 4비트의 mobility 특성을 기록하고 관리하기 위해 먼저 전역 변수 pageblock_order를 설정한다.
    • ARM64 시스템의 디폴트 설정은 pageblock_order 값에 9을 사용한다.
  • 코드 라인 14~20에서 present 섹션을 순회하며 동일 노드인 경우 map_count를 증가시켜 mem_map등을 한꺼번에 같은 노드에서 할당 받게 한다.
    • present 섹션을 순회 중일 때 pnum_end에는 순회중인 present 섹션 번호가 담긴다.
  • 코드 라인 22~25에서 바뀐 노드인 경우 해당 present  범위의 섹션들을 초기화한다.
  • 코드 라인 28에서 위의 루프에서 처리하지 않은 마지막 남은 노드에 대해서도 해당 present 범위의 섹션들을 초기화한다.
  • 코드 라인 29에서 arm과 arm64에서는 구현되지 않아 출력하는 메시지가 없다.

 


메모리 활성화

memblocks_present()

mm/sparse.c

/*
 * Mark all memblocks as present using memory_present().
 * This is a convenience function that is useful to mark all of the systems
 * memory as present during initialization.
 */
static void __init memblocks_present(void)
{
        unsigned long start, end;
        int i, nid;

        for_each_mem_pfn_range(i, MAX_NUMNODES, &start, &end, &nid)
                memory_present(nid, start, end);
}

memblock에 등록된 메모리들을 순회하며 mem_section 연결하고, 섹션별로 활성화시킨다.

 

memory_present()

mm/sparse.c

/* Record a memory area against a node. */
static void __init memory_present(int nid, unsigned long start, unsigned long end)
{
        unsigned long pfn;

#ifdef CONFIG_SPARSEMEM_EXTREME
        if (unlikely(!mem_section)) {
                unsigned long size, align;

                size = sizeof(struct mem_section*) * NR_SECTION_ROOTS;
                align = 1 << (INTERNODE_CACHE_SHIFT);
                mem_section = memblock_alloc(size, align);
                if (!mem_section)
                        panic("%s: Failed to allocate %lu bytes align=0x%lx\n",
                              __func__, size, align);
        }
#endif

        start &= PAGE_SECTION_MASK;
        mminit_validate_memmodel_limits(&start, &end);
        for (pfn = start; pfn < end; pfn += PAGES_PER_SECTION) {
                unsigned long section = pfn_to_section_nr(pfn);
                struct mem_section *ms;

                sparse_index_init(section, nid);
                set_section_nid(section, nid);

                ms = __nr_to_section(section);
                if (!ms->section_mem_map) {
                        ms->section_mem_map = sparse_encode_early_nid(nid) |
                                                        SECTION_IS_ONLINE;
                        __section_mark_present(ms, section);
                }
        }
}

memory_present() 함수는 CONFIG_HAVE_MEMORY_PRESENT 커널 옵션을 사용하는 경우에만 동작하며 각 섹션에 노드 id를 기록하는데, 자세한 내용은 코드를 보고 알아보기로 한다.

  • 코드 라인 6~15에서 CONFIG_SPARSEMEM_EXTREME 커널 옵션을 사용하는 경우 처음 mem_section이 초기화되지 않은 경우 mem_section[] 배열을 생성한다.
    • arm64 예) 4K 페이지, sizeof(struct mem_section)=16
      • NR_SECTION_ROOTS=1024
  • 코드 라인 18~19에서 요청한 범위의 시작 pfn을 섹션 단위로 내림 정렬한 주소로 변환한 후 해당 범위의 pfn이 실제로 지정할 수 있는 물리 메모리 범위에 포함되는지 검증하고 초과 시 그 주소를 강제로 제한한다.
  • 코드 라인 20~21에서 시작 pfn부터 섹션 단위로 증가시키며 이 값으로 section 번호를 구한다.
  • 코드 라인 24에서 CONFIG_SPARSEMEM_EXTREME 커널 옵션을 사용하는 경우에만 동작하며, 이 함수에서 해당 섹션에 대한 mem_section[]을 동적으로(dynamic) 할당받는다. CONFIG_SPARSEMEM_STATIC 커널 옵션을 사용하는 경우에는 이미 정적(static) 배열이 준비되어 있으므로 아무런 동작도 요구되지 않는다.
  • 코드 라인 25에서 NODE_NOT_IN_PAGE_FLAGS가 정의된 경우 별도의 전역 section_to_node_table[] 배열에 해당 섹션을 인덱스로 해당 노드 id를 가리키게 한다.
    • NODE_NOT_IN_PAGE_FLAGS 커널 옵션은 page 구조체의 flags 필드에 노드 번호를 저장할 비트가 부족한 32비트 아키텍처에서 사용되는 옵션이다.
  • 코드 라인 27~32에서 해당 섹션의 mem_section 구조체내의 section_mem_map에 노드 id와 online 및 present 플래그를 설정한다.

 

아래 그림은 memory_present() 함수가 호출되는 과정에서 mem_section[] 배열이 할당되는 것을 나타낸다.

  • 붉은 색 박스는 sparse_index_alloc() 함수에 의해 dynamic 하게 메모리 할당을 받는 것을 의미한다.
  • 푸른 색 박스는 컴파일 타임에 static하게 배열이 할당됨을 의미한다.

memory_present-1

 

mminit_validate_memmodel_limits()

mm/sparse.c

/* Validate the physical addressing limitations of the model */
void __meminit mminit_validate_memmodel_limits(unsigned long *start_pfn,
                                                unsigned long *end_pfn)
{
        unsigned long max_sparsemem_pfn = 1UL << (MAX_PHYSMEM_BITS-PAGE_SHIFT);

        /*
         * Sanity checks - do not allow an architecture to pass
         * in larger pfns than the maximum scope of sparsemem:
         */
        if (*start_pfn > max_sparsemem_pfn) {
                mminit_dprintk(MMINIT_WARNING, "pfnvalidation",
                        "Start of range %lu -> %lu exceeds SPARSEMEM max %lu\n",
                        *start_pfn, *end_pfn, max_sparsemem_pfn);
                WARN_ON_ONCE(1);
                *start_pfn = max_sparsemem_pfn;
                *end_pfn = max_sparsemem_pfn;
        } else if (*end_pfn > max_sparsemem_pfn) {
                mminit_dprintk(MMINIT_WARNING, "pfnvalidation",
                        "End of range %lu -> %lu exceeds SPARSEMEM max %lu\n",
                        *start_pfn, *end_pfn, max_sparsemem_pfn);
                WARN_ON_ONCE(1);
                *end_pfn = max_sparsemem_pfn;
        }   
}

인자로 사용된 시작 pfn, 끝 pfn 값이 물리 메모리 주소 최대 pfn 값을 초과하지 않도록 제한한다.

  • 코드 라인 10~16에서 시작 pfn이 max_sparsemem_pfn보다 크면 경고를 출력하고, start_pfn과 end_pfn에 max_sparsemem_pfn을 설정한다.
  • 코드 라인 17~23에서 끝 pfn이 max_sparsemem_pfn보다 크면 경고를 출력하고, end_pfn에 max_sparsemem_pfn을 설정한다.

 

섹션 인덱스 초기화

sparse_index_init()

mm/sparse.c

#ifdef CONFIG_SPARSEMEM_EXTREME
static int __meminit sparse_index_init(unsigned long section_nr, int nid)
{
        unsigned long root = SECTION_NR_TO_ROOT(section_nr);
        struct mem_section *section;

        /*
         * An existing section is possible in the sub-section hotplug
         * case. First hot-add instantiates, follow-on hot-add reuses
         * the existing section.
         *
         * The mem_hotplug_lock resolves the apparent race below.
         */
        if (mem_section[root])
                return 0;

        section = sparse_index_alloc(nid);
        if (!section)
                return -ENOMEM;

        mem_section[root] = section;

        return 0;
}
#endif

CONFIG_SPARSEMEM_EXTREME 커널 옵션을 사용하는 경우 dynamic하게 mem_section 테이블을 할당 받아 구성한다.

  • 코드 라인 4에서 섹션 번호로 루트 번호를 구한다.
  • 코드 라인 14~15에서 해당 루트 인덱스의 루트 섹션에 값이 존재하는 경우 이미 mem_section[] 테이블이 구성되었으므로 함수를 빠져나간다.
  • 코드 라인 17~19에서 해당 노드에서 2 단계용 섹션 테이블을 할당받아 구성한다. 핫플러그 메모리를 위해 각 mem_section[] 테이블은 해당 노드에 위치해야 한다.
  • 코드 라인 21에서 루트 번호에 해당하는 1단계 mem_section[] 포인터 배열에 새로 할당받은 2 단계 mem_section[] 테이블의 시작 주소를 설정한다.
  • 코드 라인 23에서 정상 결과 0을 반환한다.

 

sparse_index_alloc()

mm/sparse.c

#ifdef CONFIG_SPARSEMEM_EXTREME
static noinline struct mem_section __ref *sparse_index_alloc(int nid)
{
        struct mem_section *section = NULL;
        unsigned long array_size = SECTIONS_PER_ROOT *
                                   sizeof(struct mem_section);

        if (slab_is_available()) {
                        section = kzalloc_node(array_size, GFP_KERNEL, nid);
        } else {
                section = memblock_alloc_node(array_size, SMP_CACHE_BYTES,
                                              nid);
                if (!section)
                        panic("%s: Failed to allocate %lu bytes nid=%d\n",
                              __func__, array_size, nid);
        }

        return section;
}
#endif

CONFIG_SPARSEMEM_EXTREME 커널 옵션을 사용하는 경우 mem_section[] 테이블용 메모리를 할당 받는다.

  • 코드 라인 5~6에서 루트 엔트리는 1개의 페이지로 구성되며, 가득 구성될 mem_section 구조체 배열의 크기를 구한다. SECTIONS_PER_ROOT는 루트 엔트리당 mem_section 수를 의미한다. 이 상수는 다음과 같이 정의되어 있다.
    • #define SECTIONS_PER_ROOT (PAGE_SIZE / sizeof(struct mem_section))
      • 예) arm64: 4K / 16 bytes = 256개
  • 코드 라인 8~9에서 슬랩 메모리 할당자가 동작하는 경우 kzalloc( ) 함수를 통해 메모리를 할당한다.
  • 코드 라인 10~16에서 슬랩 메모리 할당자가 동작하지 않는 경우 해당 노드의 memblock에 할당한다
  • 코드 라인 18에서 할당 받은 mem_section 배열을 반환한다.

 

set_section_nid()

mm/sparse.c

#ifdef NODE_NOT_IN_PAGE_FLAGS
static void set_section_nid(unsigned long section_nr, int nid)
{
        section_to_node_table[section_nr] = nid;
}
#else /* !NODE_NOT_IN_PAGE_FLAGS */
static inline void set_section_nid(unsigned long section_nr, int nid)
{
}
#endif

NODE_NOT_IN_PAGE_FLAGS  옵션을 사용하는 경우 section_to_node_table[]에 섹션 번호에 1:1로 대응하는 노드 번호를 저장한다.

  • page 구조체 멤버 변수 flags에 노드 번호를 저장할 비트가 부족한 32비트 아키텍처에서 사용되는 옵션이다.

 

아래 그림은 set_section_nid() 함수를 통해 주어진 섹션 번호에 노드 번호를 저장한다.

set_section_nid-1

 

__nr_to_section()

mm/sparse.c

static inline struct mem_section *__nr_to_section(unsigned long nr)
{
#ifdef CONFIG_SPARSEMEM_EXTREME
        if (!mem_section)
                return NULL;
#endif
        if (!mem_section[SECTION_NR_TO_ROOT(nr)])
                return NULL;
        return &mem_section[SECTION_NR_TO_ROOT(nr)][nr & SECTION_ROOT_MASK];
}

섹션 번호에 해당하는 mem_section 구조체 정보를 알아온다.

 

아래 그림은 __nr_to_section() 함수를 사용하여 섹션 번호로 mem_section 구조체 정보를 알아오는 단계를 2 가지 예로 나타내었다.

__nr_to_section-1

 

__section_mark_present()

mm/sparse.c

/*
 * There are a number of times that we loop over NR_MEM_SECTIONS,
 * looking for section_present() on each.  But, when we have very
 * large physical address spaces, NR_MEM_SECTIONS can also be
 * very large which makes the loops quite long.
 *
 * Keeping track of this gives us an easy way to break out of
 * those loops early.
 */
unsigned long __highest_present_section_nr;
static void __section_mark_present(struct mem_section *ms,
                unsigned long section_nr)
{
        if (section_nr > __highest_present_section_nr)
                __highest_present_section_nr = section_nr;

        ms->section_mem_map |= SECTION_MARKED_PRESENT;
}

섹션 메모리가 존재함을 표시한다.

  • 코드 라인 5~6에서 전역 변수 __highest_present_section_nr를 가장 높은 섹션 번호로 갱신한다.
  • 코드 라인 8에서 mem_section을 가리키는 ms->section_mem_map에 SECTION_MARKED_PRESENT 플래그 비트를 추가한다.

 

for_each_present_section_nr()

mm/sparse.c

#define for_each_present_section_nr(start, section_nr)          \
        for (section_nr = next_present_section_nr(start-1);     \
             ((section_nr >= 0) &&                              \
              (section_nr <= __highest_present_section_nr));    \
             section_nr = next_present_section_nr(section_nr))

@start 섹션을 포함하여 마지막 present 섹션까지 present 섹션마다 순회한다. @section_nr는 출력 인자로 present 섹션 번호이다.

 

next_present_section_nr()

mm/sparse.c

static inline int next_present_section_nr(int section_nr)
{
        do {
                section_nr++;
                if (present_section_nr(section_nr))
                        return section_nr;
        } while ((section_nr <= __highest_present_section_nr));

        return -1;
}

요청한 @section_nr 다음 섹션번호부터 마지막 섹션까지 순회하며 present 섹션을 발견하면 해당 섹션 번호를 반환한다.

 


pageblock order 설정

set_pageblock_order()

mm/page_alloc.c

#ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE

/* Initialise the number of pages represented by NR_PAGEBLOCK_BITS */
void __init set_pageblock_order(void)
{
        unsigned int order;

        /* Check that pageblock_nr_pages has not already been setup */
        if (pageblock_order)
                return;

        if (HPAGE_SHIFT > PAGE_SHIFT)
                order = HUGETLB_PAGE_ORDER;
        else
                order = MAX_ORDER - 1;

        /*
         * Assume the largest contiguous order of interest is a huge page.
         * This value may be variable depending on boot parameters on IA64 and
         * powerpc.
         */
        pageblock_order = order;
}
#else /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */

/*
 * When CONFIG_HUGETLB_PAGE_SIZE_VARIABLE is not set, set_pageblock_order()
 * is unused as pageblock_order is set at compile-time. See
 * include/linux/pageblock-flags.h for the values of pageblock_order based on
 * the kernel config
 */
void __init set_pageblock_order(void)
{
}

#endif /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */

CONFIG_HUGETLB_PAGE_SIZE_VARIABLE 커널 옵션을 사용하는 경우에만 런타임에 pageblock_order를 설정한다.

 

pageblock_order

include/linux/pageblock-flags.h

#ifdef CONFIG_HUGETLB_PAGE

#ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE

/* Huge page sizes are variable */
extern unsigned int pageblock_order;

#else /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */

/* Huge pages are a constant size */
#define pageblock_order         HUGETLB_PAGE_ORDER

#endif /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */

#else /* CONFIG_HUGETLB_PAGE */

/* If huge pages are not used, group by MAX_ORDER_NR_PAGES */
#define pageblock_order         (MAX_ORDER-1)

#endif /* CONFIG_HUGETLB_PAGE */

페이지 블럭 오더 크기는 CONFIG_HUGETLB_PAGE 및 CONFIG_HUGETLB_PAGE_SIZE_VARIABLE 커널 옵션에 사용 유무에 따라 결정된다.

  • CONFIG_HUGETLB_PAGE 사용 시
    • CONFIG_HUGETLB_PAGE_SIZE_VARIABLE  커널 옵션 사용 시
      • pageblock_order를 런타임에 set_pageblock_order() 함수에서 결정한다.
    • CONFIG_HUGETLB_PAGE_SIZE_VARIABLE  커널 옵션 미 사용 시
      • 컴파일 타임에 HUGETLB_PAGE_ORDER 값으로 결정된다.
        • =HPAGE_SHIFT(21) – PAGE_SHIFT(12)=9 (arm64 디폴트 설정 사용 시)
  • CONFIG_HUGETLB_PAGE 커널 옵션을 사용하지 않을 경우
    • 컴파일 타임에 MAX_ORDER-1 값으로 결정된다.

 


노드별 sparse 초기화

sparse_init_nid()

mm/sparse.c

/*
 * Initialize sparse on a specific node. The node spans [pnum_begin, pnum_end)
 * And number of present sections in this node is map_count.
 */
static void __init sparse_init_nid(int nid, unsigned long pnum_begin,
                                   unsigned long pnum_end,
                                   unsigned long map_count)
{
        struct mem_section_usage *usage;
        unsigned long pnum;
        struct page *map;

        usage = sparse_early_usemaps_alloc_pgdat_section(NODE_DATA(nid),
                        mem_section_usage_size() * map_count);
        if (!usage) {
                pr_err("%s: node[%d] usemap allocation failed", __func__, nid);
                goto failed;
        }
        sparse_buffer_init(map_count * section_map_size(), nid);
        for_each_present_section_nr(pnum_begin, pnum) {
                unsigned long pfn = section_nr_to_pfn(pnum);

                if (pnum >= pnum_end)
                        break;

                map = __populate_section_memmap(pfn, PAGES_PER_SECTION,
                                nid, NULL);
                if (!map) {
                        pr_err("%s: node[%d] memory map backing failed. Some memory will not be available.",
                               __func__, nid);
                        pnum_begin = pnum;
                        sparse_buffer_fini();
                        goto failed;
                }
                check_usemap_section_nr(nid, usage);
                sparse_init_one_section(__nr_to_section(pnum), pnum, map, usage,
                                SECTION_IS_EARLY);
                usage = (void *) usage + mem_section_usage_size();
        }
        sparse_buffer_fini();
        return;
failed:
        /* We failed to allocate, mark all the following pnums as not present */
        for_each_present_section_nr(pnum_begin, pnum) {
                struct mem_section *ms;

                if (pnum >= pnum_end)
                        break;
                ms = __nr_to_section(pnum);
                ms->section_mem_map = 0;
        }
}

@nid 노드에 해당하는 @pnum_begin ~ @pnum_end 미만 까지의 섹션 범위들에 대해 @map_count 만큼의 present 섹션과 관련된 mem_map 및 usemap을 할당하고 초기화한다.

  • 코드 라인 9~14에서 @map_count 섹션 수 만큼의 usemap[] 배열을 노드 @nid에서 한꺼번에 할당한다.
  • 코드 라인 15에서 @map_count 섹션 수 만큼의 sparse 버퍼를 노드 @nid에서 한꺼번에 할당하여 준비해 놓는다.
  • 코드 라인 16~30에서 @pnum_begin 섹션 부터 끝 present 섹션까지 순회하며 해당 섹션의 mem_map을 할당받는다.
    • 가능하면 sparse 버퍼에서 할당받는다.
  • 코드 라인 31에서 할당받은 usemap과 노드 정보(pgdat)가 기록된 섹션과 같지 않으면 경고 메시지를 출력한다. usemap 정보가 같은 노드 공간에 구성되지 않으면 메모리 핫리무브(hot remove) 동작 시 노드별로 메모리를 제거해야 할 때 circular dependancy 문제가 발생할 수 있다.
  • 코드 라인 32~33에서 해당 섹션 하나를 초기화한다.
  • 코드 라인 36에서 할당하고 남은 sparse 버퍼를 할당 해제한다.

브라켓 표현법

begin이 1이고 end가 5라고 할 때

  • [begin, end]
    • begin 이상 ~ end 이하
    • 1~5
  • (begin, end)
    • bigin 초과 ~ end 미만
    • 2~4
  • [begin, end)
    • begin 이상 ~ end 미만
    • 1~4
  • (begin, end]
    • begin 초과, end 이하
    • 2~5

 

usemap 할당

sparse_early_usemaps_alloc_pgdat_section()

mm/sparse.c

#ifdef CONFIG_MEMORY_HOTREMOVE
static unsigned long * __init
sparse_early_usemaps_alloc_pgdat_section(struct pglist_data *pgdat,
                                         unsigned long size)
{
        struct mem_section_usage *usage;
        unsigned long goal, limit;
        int nid;
        /*
         * A page may contain usemaps for other sections preventing the
         * page being freed and making a section unremovable while
         * other sections referencing the usemap remain active. Similarly,
         * a pgdat can prevent a section being removed. If section A
         * contains a pgdat and section B contains the usemap, both
         * sections become inter-dependent. This allocates usemaps
         * from the same section as the pgdat where possible to avoid
         * this problem.
         */
        goal = pgdat_to_phys(pgdat) & (PAGE_SECTION_MASK << PAGE_SHIFT);
        limit = goal + (1UL << PA_SECTION_SHIFT);
        nid = early_pfn_to_nid(goal >> PAGE_SHIFT);
again:
        usage = memblock_alloc_try_nid(size, SMP_CACHE_BYTES, goal, limit, nid);
        if (!usage && limit) {
                limit = 0;
                goto again;
        }
        return usage;
}
#endif

CONFIG_MEMORY_HOTREMOVE 커널 옵션을 사용하는 경우 usemap을 할당한다. 가능하면 노드 정보(pgdat)가 담겨 있는 섹션에 usemap이 들어갈 수 있도록 메모리 할당을 시도한다. 실패한 경우 위치에 상관없이 다시 한번 할당을 시도한다.

  • 코드 라인 19~20에서 goal에는 노드에서의 섹션 시작 물리 주소를 구하고 limit를 1개의 섹션 크기로 제한한다.
  • 코드 라인 21에서 기존에 mem_map에 저장해놓은 섹션별 노드 정보를 가져온다.
  • 코드 라인 22~23에서 again: 레이블이다. 지정된 노드의 goal~limit 범위, 즉 노드 정보가 담겨 있는 섹션 영역 내에서 SMP_CACHE_BYTES align으로 size만큼의 memblock 공간 할당을 요청한다.
  • 코드 라인 24~27에서 한 번 시도해서 할당이 안 되면 limit를 0으로 만들어 다시 한번 시도한다.
  • 코드 라인 28에서 할당된 usemap을 반환한다.

 

mem_map용 Sparse 버퍼 할당/해제

노드에 해당하는 전체 present 섹션 메모리에 대한 mem_map[] 배열을 한꺼번에 할당을 시도한다. 이렇게 시도하여 할당이 된 경우 이 sparse 버퍼 메모리를 mem_map[] 배열로 사용한다. 만일 할당이 실패하는 경우 fallback되어 그냥 섹션별로 할당한다.

 

sparse_buffer_init()

mm/sparse.c

static void __init sparse_buffer_init(unsigned long size, int nid)
{
        WARN_ON(sparsemap_buf); /* forgot to call sparse_buffer_fini()? */
        sparsemap_buf =
                memblock_alloc_try_nid_raw(size, PAGE_SIZE,
                                                __pa(MAX_DMA_ADDRESS),
                                                MEMBLOCK_ALLOC_ACCESSIBLE, nid);
        sparsemap_buf_end = sparsemap_buf + size;
}

요청한 @size만큼 노드 @nid에서 페이지 단위로 sparse 버퍼를 할당한다.

 

sparse_buffer_alloc()

mm/sparse.c

void * __meminit sparse_buffer_alloc(unsigned long size)
{
        void *ptr = NULL;

        if (sparsemap_buf) {
                ptr = PTR_ALIGN(sparsemap_buf, size);
                if (ptr + size > sparsemap_buf_end)
                        ptr = NULL;
                else
                        sparsemap_buf = ptr + size;
        }
        return ptr;
}

Sparse 버퍼에서 @size 만큼의 메모리를 할당한다. (for mem_map)

 

sparse_buffer_fini()

mm/sparse.c

static void __init sparse_buffer_fini(void)
{
        unsigned long size = sparsemap_buf_end - sparsemap_buf;

        if (sparsemap_buf && size > 0)
                memblock_free_early(__pa(sparsemap_buf), size);
        sparsemap_buf = NULL;
}

사용하고 남은 sparse 버퍼를 할당 해제한다.

 

usemap 할당 섹션 또는 노드 체크

check_usemap_section_nr()

mm/sparse.c

#ifdef CONFIG_MEMORY_HOTREMOVE
static void __init check_usemap_section_nr(int nid,
                struct mem_section_usage *usage)
{
        unsigned long usemap_snr, pgdat_snr;
        static unsigned long old_usemap_snr;
        static unsigned long old_pgdat_snr;
        struct pglist_data *pgdat = NODE_DATA(nid);
        int usemap_nid;

        /* First call */
        if (!old_usemap_snr) {
                old_usemap_snr = NR_MEM_SECTIONS;
                old_pgdat_snr = NR_MEM_SECTIONS;
        }

        usemap_snr = pfn_to_section_nr(__pa(usage) >> PAGE_SHIFT);
        pgdat_snr = pfn_to_section_nr(pgdat_to_phys(pgdat) >> PAGE_SHIFT);
        if (usemap_snr == pgdat_snr)
                return;

        if (old_usemap_snr == usemap_snr && old_pgdat_snr == pgdat_snr)
                /* skip redundant message */
                return;

        old_usemap_snr = usemap_snr;
        old_pgdat_snr = pgdat_snr;

        usemap_nid = sparse_early_nid(__nr_to_section(usemap_snr));
        if (usemap_nid != nid) {
                pr_info("node %d must be removed before remove section %ld\n",
                        nid, usemap_snr);
                return;
        }
        /*
         * There is a circular dependency.
         * Some platforms allow un-removable section because they will just
         * gather other removable sections for dynamic partitioning.
         * Just notify un-removable section's number here.
         */
        pr_info("Section %ld and %ld (node %d) have a circular dependency on usemap and pgdat allocations\n",
                usemap_snr, pgdat_snr, nid);
}
#endif

CONFIG_MEMORY_HOTREMOVE 커널 옵션을 사용하는 경우 usemap 섹션은 pgdat가 위치한 섹션에 있거나 그렇지 않다면 다른 섹션이 모두 삭제될 때까지 usemap이 위치한 섹션은 삭제되면 안 된다. 따라서 이에 대한 관계를 메시지로 알아보기 위한 루틴이다. 할당받은 usemap과 노드 정보(pgdat)가 기록된 섹션과 같지 않으면 정보를 출력한다. usemap 정보가 같은 노드 공간에 구성되지 않으면 메모리 핫리무브(hot remove) 동작 시 노드별로 메모리를 제거해야 할 때 circular dependancy 문제가 발생할 수 있다.

 

  • 코드 라인 12~15에서 처음 호출 시 초깃값으로 진행 중인 섹션 번호를 담는다.
  • 코드 라인 17~20에서 usemap이 할당된 섹션과 노드 정보가 기록된 섹션이 같은 경우 정상이므로 함수를 빠져나간다.
  • 코드 라인 22~24에서 이미 한 번 진행하였던 섹션인 경우 skip 하기 위해 함수를 빠져나간다.
  • 코드 라인 26~27에서 같은 섹션 번호로 다시 한 번 진행하는 경우 skip 하기 위해 섹션 번호들을 기억해둔다.
  • 코드 라인 29~34에서 요청한 @nid에 usemap 노드가 없는 경우 use_map이 있는 섹션 정보를 먼저 hot remove 하여야 한다는 정보를 출력한다.
  • 코드 라인 41~42에서 요청한 @nid에 usemap이 있는 경우 메모리 핫리무브(hot remove) 동작 시 노드별로 메모리를 제거해야 할 때 circular dependancy 문제가 발생할 수 있다는 정보를 출력한다.

 

usemap_size()

mm/sparse.c

unsigned long usemap_size(void)
{
        return BITS_TO_LONGS(SECTION_BLOCKFLAGS_BITS) * sizeof(unsigned long);
}

usemap 사이즈를 리턴한다.

  •  SECTION_BLOCKFLAGS_BITS
    • 섹션당 pageblock 비트 수 (pageblock_order=9일 때)
      • arm64=2048
  • 예) arm64
    • 2048 / 8=256(byte)

 

sparse_mem_map_populate()

vmemmap을 사용하지 않을 때의 함수이다. arm64 디폴트 설정에서는 vmemmap을 사용하므로 이 함수를 사용하지 않는다.

mm/sparse.c

#ifndef CONFIG_SPARSEMEM_VMEMMAP
struct page __init *sparse_mem_map_populate(unsigned long pnum, int nid,
                struct vmem_altmap *altmap)
{
        unsigned long size = section_map_size();
        struct page *map = sparse_buffer_alloc(size);

        if (map)
                return map;

        map = memblock_alloc_try_nid(size,
                                          PAGE_SIZE, __pa(MAX_DMA_ADDRESS),
                                          MEMBLOCK_ALLOC_ACCESSIBLE, nid);
        return map;
}
#endif /* !CONFIG_SPARSEMEM_VMEMMAP */

CONFIG_SPARSEMEM_VMEMMAP 커널 옵션을 사용하지 않는 시스템에서 미리 할당한 sparse 버퍼를 사용하여 mem_map[]을 할당해준다. 만일 실패하는 경우 별도로 섹션마다 mem_map[] 배열을 memblock 할당 요청한다.

 

sparse_init_one_section()

mm/sparse.c

static void __meminit sparse_init_one_section(struct mem_section *ms,
                unsigned long pnum, struct page *mem_map,
                struct mem_section_usage *usage, unsigned long flags)
{
        ms->section_mem_map &= ~SECTION_MAP_MASK;
        ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum)
                | SECTION_HAS_MEM_MAP | flags;
        ms->usage = usage;
}

mem_map 영역을 엔코딩하여 mem_section에 연결하고 usemap 영역도 연결한다.

 


mem_map 주소의 엔코딩/디코딩

각 섹션을 관리하는 mem_section 구조체의 section_mem_map 멤버는 다음과 같이 2가지 정보를 담아사용한다. 단 동시에 사용되지는 않는다.

  • 부트 타임에 잠시 섹션에 대한 early 노드 번호를 담아 사용한다.
    • sparse_encode_early_nid() 및 sparse_early_nid() 함수를 사용한다.
    • mem_map이 활성화된 이후 노드 번호는 page->flags에 저장하여 사용된다.
  • 각 섹션이 관리하는 mem_map – 섹션에 대한 pfn 값에 몇 개의 플래그들을 엔코딩하여 사용한다.
    • sparse_encode_mem_map() 및 sparse_decode_mem_map() 함수를 사용한다.
    • 섹션에 대한 pfn 값을 빼서 저장하면 vmemmap을 사용하지 않는 시스템에서 pfn_to_page() 함수를 사용할 때 한 번의 산술연산이 절감되는 효과가 있다.
    • 예) 0x8000_0000 ~ 0xC000_0000 (1G)을 관리하는 섹션에 대한 mem_map 주소 – 0x80000(섹션 pfn) + 플래그 값으로 엔코딩한다.

 

early 노드 번호 엔코딩/디코딩

sparse_encode_early_nid()

mm/sparse.c

/*
 * During early boot, before section_mem_map is used for an actual
 * mem_map, we use section_mem_map to store the section's NUMA
 * node.  This keeps us from having to use another data structure.  The
 * node information is cleared just before we store the real mem_map.
 */
static inline unsigned long sparse_encode_early_nid(int nid)
{
        return (nid << SECTION_NID_SHIFT);
}

노드 번호는 SECTION_NID_SHIFT(6) 비트부터 사용되므로 그만큼 좌측으로 쉬프트한다.

 

sparse_early_nid()

mm/sparse.c

static inline int sparse_early_nid(struct mem_section *section)
{
        return (section->section_mem_map >> SECTION_NID_SHIFT);
}

mem_section 구조체 멤버 변수인 section_mem_map에서 노드 정보를 추출하여 리턴한다.

 

정규 mem_map 엔코딩/디코딩

sparse_encode_mem_map()

mm/sparse.c

/*
 * Subtle, we encode the real pfn into the mem_map such that
 * the identity pfn - section_mem_map will return the actual
 * physical page frame number.
 */
static unsigned long sparse_encode_mem_map(struct page *mem_map, unsigned long pnum)
{
        unsigned long coded_mem_map =
                (unsigned long)(mem_map - (section_nr_to_pfn(pnum)));
        BUILD_BUG_ON(SECTION_MAP_LAST_BIT > (1UL<<PFN_SECTION_SHIFT));
        BUG_ON(coded_mem_map & ~SECTION_MAP_MASK);
        return coded_mem_map;
}

할당 받은 mem_map의 주소 – 섹션에 해당하는 base pfn 값을 엔코딩 값으로 반환한다.

  • vmemmap을 사용하지 않는 경우 pfn_to_page() 함수에서 산술 연산을 한 번 제거하는 효과가 있다.

 

sparse_decode_mem_map()

mm/sparse.c

/*
 * Decode mem_map from the coded memmap
 */
struct page *sparse_decode_mem_map(unsigned long coded_mem_map, unsigned long pnum)
{
        /* mask off the extra low bits of information */
        coded_mem_map &= SECTION_MAP_MASK;
        return ((struct page *)coded_mem_map) + section_nr_to_pfn(pnum);
}

엔코딩된 mem_map 주소에서 플래그 정보를 제거한 후 디코딩하여 mem_map 주소만 반환한다.

 


VMEMMAP 관련

  • arm64에서는 CONFIG_SPARSEMEM_VMEMMAP을 기본적으로 사용한다.
  • 시스템 리소스가 충분하여 vmemmap을 사용하는 경우 pfn_to_page() 및 page_to_pfn() 함수의 동작이 가장 효과적으로 빨라진다.

 

__populate_section_memmap()

mm/sparse-vmemmap.c

struct page * __meminit __populate_section_memmap(unsigned long pfn,
                unsigned long nr_pages, int nid, struct vmem_altmap *altmap)
{
        unsigned long start = (unsigned long) pfn_to_page(pfn);
        unsigned long end = start + nr_pages * sizeof(struct page);

        if (WARN_ON_ONCE(!IS_ALIGNED(pfn, PAGES_PER_SUBSECTION) ||
                !IS_ALIGNED(nr_pages, PAGES_PER_SUBSECTION)))
                return NULL;

        if (vmemmap_populate(start, end, nid, altmap))
                return NULL;

        return pfn_to_page(pfn);
}

CONFIG_SPARSEMEM_VMEMMAP 커널 옵션을 사용하는 경우 mem_map 영역에 해당하는 @pfn을 vmemmap 영역에 @nr_pages만큼 매핑한다. 요청한 섹션으로 주소 범위를 구한 후 해당 주소 범위와 관련한 pgd, pud 및 pmd 테이블들에 구성되지 않은 테이블이 있는 경우 노드에서 페이지를 할당하여 구성하고 매핑한다.

  • sparse 버퍼에 할당된 mem_map 용 공간들을 pte 엔트리에 연결하여 매핑한다.

 

vmemmap_populate()

arch/arm64/mm/mmu.c

#if !ARM64_KERNEL_USES_PMD_MAPS
int __meminit vmemmap_populate(unsigned long start, unsigned long end, int node,
                struct vmem_altmap *altmap)
{
        WARN_ON((start < VMEMMAP_START) || (end > VMEMMAP_END));
        return vmemmap_populate_basepages(start, end, node, altmap);
}
#else   /* !ARM64_KERNEL_USES_PMD_MAPS */
int __meminit vmemmap_populate(unsigned long start, unsigned long end, int node,
                struct vmem_altmap *altmap)
{
        unsigned long addr = start;
        unsigned long next;
        pgd_t *pgdp;
        p4d_t *p4dp;
        pud_t *pudp;
        pmd_t *pmdp;

        WARN_ON((start < VMEMMAP_START) || (end > VMEMMAP_END));
        do {
                next = pmd_addr_end(addr, end);

                pgdp = vmemmap_pgd_populate(addr, node);
                if (!pgdp)
                        return -ENOMEM;

                p4dp = vmemmap_p4d_populate(pgdp, addr, node);
                if (!p4dp)
                        return -ENOMEM;

                pudp = vmemmap_pud_populate(p4dp, addr, node);
                if (!pudp)
                        return -ENOMEM;

                pmdp = pmd_offset(pudp, addr);
                if (pmd_none(READ_ONCE(*pmdp))) {
                        void *p = NULL;

                        p = vmemmap_alloc_block_buf(PMD_SIZE, node, altmap);
                        if (!p) {
                                if (vmemmap_populate_basepages(addr, next, node, altmap))
                                        return -ENOMEM;
                                continue;
                        }

                        pmd_set_huge(pmdp, __pa(p), __pgprot(PROT_SECT_NORMAL));
                } else
                        vmemmap_verify((pte_t *)pmdp, node, addr, next);
        } while (addr = next, addr != end);

        return 0;
}
#endif  /* !ARM64_KERNEL_USES_PMD_MAPS */

요청 주소 범위와 관련한 pgd, p4d, pud 및 pmd 테이블들에 구성되지 않은 테이블이 있는 경우 노드에서 페이지를 할당하여 구성하고 매핑한다.

  • 4K 페이지를 사용하는 경우 ARM64_KERNEL_USES_PMD_MAPS 값은 1이다.
  • @start: mem_map이  매핑될 시작 가상 주소(vmemmap 영역에 매핑)
  • @end: mem_map이 매핑될 끝 가상 주소 (vmemmap 영역에 매핑)

 

  • 코드 라인 6에서 mem_map을 4K 단위로 매핑을 수행한다.
  • 코드 라인 20~21에서 addr을 순회하며, pmd 단위로 매핑할 예정이므로 pmd 단위의 끝 주소를 구해 next에 대입한다.
  • 코드 라인 23~25에서 해당 가상 주소 위치에 대응하는 pgd 엔트리를 populate 한다.
  • 코드 라인 27~29에서 해당 가상 주소 위치에 대응하는 p4d 엔트리를 populate 한다.
  • 코드 라인 31~33에서 해당 가상 주소 위치에 대응하는 pud 엔트리를 populate 한다.
  • 코드 라인 35~46에서 pmd 엔트리 주소에 매핑되어 있지 않은 경우 PMD 사이즈(2M)의 메모리를 할당한 후 pmd_set_huge() 명령으로 매핑한다.
  • 코드 라인 47~48에서 이미 pmd 매핑이 되어 있는 경우 vmemmap_verify() 함수를 통해 mem_map이 다른 노드에 할당된 경우 경고 로그를 출력한다.
  • 코드 라인 49에서 대음 pmd 단위의 주소로 이동하고 루프를 계속한다.
  • 코드 라인 51에서 정상 결과 0을 반환한다.

 

vmemmap_populate_basepages()

mm/sparse-vmemmap.c

int __meminit vmemmap_populate_basepages(unsigned long start, unsigned long end,
                                         int node, struct vmem_altmap *altmap)
{
        unsigned long addr = start;
        pgd_t *pgd;
        p4d_t *p4d;
        pud_t *pud;
        pmd_t *pmd;
        pte_t *pte;

        for (; addr < end; addr += PAGE_SIZE) {
                pgd = vmemmap_pgd_populate(addr, node);
                if (!pgd)
                        return -ENOMEM;
                p4d = vmemmap_p4d_populate(pgd, addr, node);
                if (!p4d)
                        return -ENOMEM;
                pud = vmemmap_pud_populate(p4d, addr, node);
                if (!pud)
                        return -ENOMEM;
                pmd = vmemmap_pmd_populate(pud, addr, node);
                if (!pmd)
                        return -ENOMEM;
                pte = vmemmap_pte_populate(pmd, addr, node, altmap);
                if (!pte)
                        return -ENOMEM;
                vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);
        }

        return 0;
}

mem_map을 4K 단위로 매핑을 수행한다.

  • @start: mem_map이  매핑될 시작 가상 주소(vmemmap 영역에 매핑)
  • @end: mem_map이 매핑될 끝 가상 주소 (vmemmap 영역에 매핑)

 

  • 코드 라인 11에서 페이지 단위로 순회한다.
  • 코드 라인 12~14에서 해당 가상 주소 위치에 대응하는 pgd 엔트리를 populate 한다.
  • 코드 라인 15~17에서 해당 가상 주소 위치에 대응하는 p4d 엔트리를 populate 한다.
  • 코드 라인 18~20에서 해당 가상 주소 위치에 대응하는 pud 엔트리를 populate 한다.
  • 코드 라인 21~23에서 해당 가상 주소 위치에 대응하는 pmd 엔트리를 populate 한다.
  • 코드 라인 24~26에서 해당 가상 주소 위치에 대응하는 pte 엔트리에 @altmap을 매핑한다.
  • 코드 라인 27에서 vmemmap_verify() 함수를 통해 mem_map이 다른 노드에 할당된 경우 경고 로그를 출력한다.
  • 코드 라인 30에서 정상 결과 0을 반환한다.

 

vmemmap_verify()

mm/sparse-vmemmap.c

void __meminit vmemmap_verify(pte_t *pte, int node,
                                unsigned long start, unsigned long end)
{
        unsigned long pfn = pte_pfn(*pte);
        int actual_node = early_pfn_to_nid(pfn);

        if (node_distance(actual_node, node) > LOCAL_DISTANCE)
                pr_warn("[%lx-%lx] potential offnode page_structs\n",
                        start, end - 1);
}

@pte가 가리키는 mem_map 노드와 @node가 같은 로컬 노드에 있지 않고, remote distance로 떨어져 있는 경우 경고 로그를 출력한다.

 

다음 함수들의 코드 및 설명은 생략한다.

  • vmemmap_pgd_populate()
  • vmemmap_p4d_populate()
  • vmemmap_pud_populate()
  • vmemmap_pmd_populate()

 


구조체 및 주요 변수

mem_section[]

mm/sparse.c

#ifdef CONFIG_SPARSEMEM_EXTREME
extern struct mem_section **mem_section;
#else
extern struct mem_section mem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT];
#endif

mem_section은 섹션 별로 mem_map[] 배열과 usemap[] 배열 정보와 연결된다.

 

mem_section 구조체

include/linux/mmzone.h

struct mem_section {
        /*
         * This is, logically, a pointer to an array of struct
         * pages.  However, it is stored with some other magic.
         * (see sparse.c::sparse_init_one_section())
         *
         * Additionally during early boot we encode node id of
         * the location of the section here to guide allocation.
         * (see sparse.c::memory_present())
         *
         * Making it a UL at least makes someone do a cast
         * before using it wrong.
         */
        unsigned long section_mem_map;

        struct mem_section_usage *usage;
#ifdef CONFIG_PAGE_EXTENSION
        /*
         * If SPARSEMEM, pgdat doesn't have page_ext pointer. We use
         * section. (see page_ext.h about this.)
         */
        struct page_ext *page_ext;
        unsigned long pad;
#endif
        /*
         * WARNING: mem_section must be a power-of-2 in size for the
         * calculation and use of SECTION_ROOT_MASK to make sense.
         */
};
  • section_mem_map
    • 여기엔 실제 섹션에 대한 mem_map을 직접 가리키지 않고, 다음과 같이 엔코딩된 값을 사용한다.
      • 섹션의 mem_map을 가리키는 주소섹션에 대한 pfn 값에 플래그들을 엔코딩하여 사용된다. (mem_map은 페이지 단위로 정렬되어 사용된다.)
    • 다음은 엔코딩에 사용할 플래그 정보들이다.
      • bit0: SECTION_MARKED_PRESENT
        • 섹션에 메모리가 있는지 여부를 표현한다.
      • bit1: SECTION_HAS_MEM_MAP
        • 섹션에 mem_map이 연결되었는지 여부를 표현한다.
        • 연결된 mem_map은 섹션 base pfn 값을 빼고 저장된다(엔코딩)
      • bit2: SECTION_IS_ONLINE
        • online 섹션 여부를 표현한다.
      • bit3: SECTION_IS_EARLY
        • early 섹션 여부를 표현한다.
      • bit4: SECTION_TAINT_ZONE_DEVICE
        • 존 디바이스 여부를 표현한다.
      • bits[6~]: 노드 번호
        • 노드 번호와 mem_map 정보가 동시에 저장되지 않는다.
        • 노드 번호는 early 부트업 중에만 잠깐 사용된다. 이후 노드 번호는 page 구조체의 flags 멤버에서 관리한다.
  •  *usage
    • mem_section_usage 구조체를 가리킨다. (usemap과 subsection 비트맵을 관리한다)
  • *page_ext
    • 디버그를 위해 CONFIG_PAGE_EXTENSION 커널 옵션을 사용하였을 때 사용되며, page_ext 구조체들이 있는 곳을 가리킨다.

 

mem_section_usage 구조체

include/linux/mmzone.h

struct mem_section_usage {
#ifdef CONFIG_SPARSEMEM_VMEMMAP
        DECLARE_BITMAP(subsection_map, SUBSECTIONS_PER_SECTION);
#endif
        /* See declaration of similar field in struct zone */
        unsigned long pageblock_flags[0];
};
  • subsection_map
    • 섹션 메모리내에서 서브 섹션 단위(2M)의 메모리 존재 여부를 표현하는 비트맵
  • pageblock_flags[]
    • usemap을 가리키는 주소

 

기타

SECTION_SIZE_BITS 및 MAX_PHYSMEM_BITS

  • arm
    • 28,  32 (256M, 4G, Realview-PBX)
    • 26,  29 (64M, 512M, RPC)
    • 27, 32 (128M, 4G, SA1100)
  • 32bit arm with LPAE
    • 34, 36 (16G, 64G, Keystone)
  • arm64
    • 27, 48 (128M, 256T)
  • x86
    • 26, 32 (64M, 4G)
  • x86_32 with PAE
    • 29, 36 (512M, 64G)
  • x86_64
    • 27, 46 (128M, 64T)

 

참고

 

 

3 thoughts to “Memory Model -3- (Sparse Memory)”

  1. 안녕하세요
    usemap 그림이 있는 부분에서
    1개 섹션은 1GB(2^30)이고, usemap은 2M(2^21) 블럭으로 나누어서 4bits로 표현하므로
    하나의 usemap의 블럭 수는 512 = 1GB / 2M = 2^(30-21) = 2^9 인 것으로 이해했습니다.

    usemap그림 중에 가운데 있는 usemap(256B)에서 ‘B’가 의미하는 것이 어떤 것인지요?
    ‘B’가 블럭을 의미한다면 하나의 섹션에 2M인 512개의 블럭이 있으므로
    512B가 되는 하는 것이 아닐까요…?

    1. 이대로님 안녕하세요?

      권용범님이 말씀하신대로 B는 Byte입니다. (너무 짧게 표현해 죄송합니다)
      블럭당 4비트의 mobility를 표현하므로 512개 블럭 x 4bit = 256 Bytes 입니다.

      감사합니다.

  2. 안녕하세요.
    문영일님이 당분간 접속이 어렵다고 하시네요 (TT)
    4bit 가 512개 있으니 2048 bit가 되고 Byte 로 계산하려고 8을 나누니 256B(yte)가 됩니다.

댓글 남기기

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