Sparse Memory

Sparse memory 모델은 Flat memory 모델이 한 덩어리의 mem_map을 사용하는 것과 다르게 다음과 같이 여러 가지의 관리 맵을 사용한다.

  • usemap_map[] -> usemap을 관리
  • mem_section[] -> mem_map을 관리
  • map_map[] -> mem_map을 관리
  • section_to_node_table[]

 

섹션

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

  • 보통 섹션 크기는 수십MB~수GB를 사용한다.
    • arm64: default 값으로 1G
  • 매핑 테이블에서 사용하는 섹션(1M, 16M(수퍼섹션)이 아니므로 유의한다.

 

CONFIG_SPARSEMEM_STATIC

Sparse 메모리 모델에서 사용하는 mem_section 배열을 컴파일 타임에 static 하게 만들어 사용한다.

  • 주로 32bit 시스템에서 섹션 수가 적을 때 사용한다.

 

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

  • 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

mm-7b

 

CONFIG_SPARSEMEM_EXTREME

Sparse 메모리 모델에서 사용하는 mem_section 배열을 동적으로 할당받아 사용한다.

  • 주로 64bit 시스템에서 섹션 수가 많을 때 메모리 절약을 위해 사용한다.

 

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

  • 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

mm-9a

 

Sparse Memory with usemap

  • usemap_map은 전체 섹션 수 만큼 할당되고 각 엔트리는 hole이 아닌 메모리에 대해 usemap을 가리킨다.
  • usemap은 pageblock 단위 마다 4개의 비트를 관리한다.
  • usemap의 메모리 할당 시 연속된 메모리 섹션에 대해 한꺼번에 할당한다.

 

sparse_init-1b

 

CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER

  • 이 커널 옵션을 사용하는 경우 mem_map의 메모리 할당 시 연속된 메모리 섹션에 대해 한꺼번에 할당하게 한다.
  • 이 커널 옵션은 현재 x86_64 아키텍처에서만 사용된다.

sparse-2

 

CONFIG_SPARSEMEM_VMEMMAP

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

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

 

다음 그림은 4K 페이지 x 3 레벨의 페이지 변환을 사용하는 arm64 시스템에서 vmemmap이 구성되는 사례를 보여준다.

vmemmap-1b

 

arm_memory_present()

arch/arm/mm/init.c

static void __init arm_memory_present(void)
{
        struct memblock_region *reg;

        for_each_memblock(memory, reg)
                memory_present(0, memblock_region_memory_base_pfn(reg),
                               memblock_region_memory_end_pfn(reg));
}
  • memory memblock 들에 대해 memory_present() 함수를 호출하여 mem_section[] 매핑 배열을 초기화한다.
    • memblock_region_memory_base_pfn(reg)
      • PFN_UP(reg->base)
    • memblock_region_memory_end_pfn(reg)
      • PFN_DOWN(reg->base + reg->size)
  • CONFIG_SPARSEMEM 커널 옵션이 있을 때에만 동작하는 함수이다.

 

memory_present()

mm/sparse.c

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

        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_MARKED_PRESENT;
        }   
}
#endif

CONFIG_HAVE_MEMORY_PRESENT 커널 옵션이 있을 때에만 동작하는 함수로 mem_section[] 매핑 테이블을 할당 받고 초기화한다.

  • start &= PAGE_SECTION_MASK;
    • 시작 pfn 주소에 대해 섹션 부분만 남게 한다.
      • Realview-PBX: start &= 0xffff_0000
        • 섹션 크기는 256M
      • arm64: start &= 0xfffc_0000
        • 섹션 크기는 1G
  • mminit_validate_memmodel_limits(&start, &end);
    • 시작 pfn, 끝 pfn 값이 최대 pfn 수 이내가 되도록 조절한다.
    • Realview-PBX: 최대 pfn=2^20=0x10_0000=1M
    • arm64: 최대 pfn=2^36=0x10_0000_0000=64G
  • for (pfn = start; pfn < end; pfn += PAGES_PER_SECTION) {
    • start ~ end 까지 pfn을 PAGES_PER_SECTION 단위로 증가시키며 루프를 돈다.
    • Realview-PBX: 2^16=64K씩 증가
    • arm64: 2^18=256K씩 증가
  • unsigned long section = pfn_to_section_nr(pfn);
    • pfn 값으로 section 번호를 알아온다.
  • sparse_index_init(section, nid);
    • *mem_section[] 값이 매핑되어 있지 않은 경우 mem_section[] 매핑 테이블을 dynamic하게 할당 받아 초기화한다.
  • set_section_nid(section, nid);
    • NODE_NOT_IN_PAGE_FLAGS 옵션이 설정된 경우 노드 번호가 page 구조체의 flag에 없기 때문에 별도로 section_to_node_table[]을 사용하여 해당 섹션에 대하여 노드 번호를 설정한다.
  • ms = __nr_to_section(section);
    • 섹션 번호로 mem_section 구조체 정보를 알아온다.
  • if (!ms->section_mem_map) ms->section_mem_map = sparse_encode_early_nid(nid) |
    SECTION_MARKED_PRESENT;

    • 멤버 section_mem_map이 설정되어 있지 않은 경우 노드 번호 필드(bits3:2)와 섹션이 존재함을 의미하는 PRESENT(bit0) 값을 설정한다.

 

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

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

memory_present-1

 

mminit_validate_memmodel_limits()

mm/sparse.c

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 수 이내가 되도록 조절한다.

  • unsigned long max_sparsemem_pfn = 1UL << (MAX_PHYSMEM_BITS-PAGE_SHIFT);
    • 최대 pfn 수
    • Realview-PBX: 2^(32 – 12) = 2^20 = 1M
  • if (*start_pfn > max_sparsemem_pfn) {
    • 시작 pfn이 max_sparsemem_pfn 보다 크면 경고 출력을 하고 start_pfn과 end_pfn에 max_sparsemem_pfn을 대입한다.
  • } else if (*end_pfn > max_sparsemem_pfn) {
    • 끝 pfn이 max_sparsemem_pfn 보다 크면 경고 출력을 하고 end_pfn에 max_sparsemem_pfn을 대입한다

 

sparse_index_init()

mm/sparse.c

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;

        if (mem_section[root])
                return -EEXIST;

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

        mem_section[root] = section;

        return 0;
}

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

  •  unsigned long root = SECTION_NR_TO_ROOT(section_nr);
    • 섹션 번호로 루트 섹션 번호를 알아온다.
  • if (mem_section[root]) return -EEXIST;
    • 해당 루트 인덱스의 루트 섹션에 값이 존재하는 경우 이미 mem_section[] 테이블이 구성되었으므로 함수를 빠져나간다.
  • section = sparse_index_alloc(nid);
    • 해당 노드에 메모리의 섹션 테이블을 할당 받아 구성한다.
      • hotplug memory를 위해 각 mem_section[] 테이블은 해당 노드에 위치해야 한다.
  • if (!section) return -ENOMEM;
    • 메모리가 부족하여 할당이 실패하면 에러를 리턴한다.
  • mem_section[root] = section;
    • 루트 섹션에 할당 받은 mem_section[] 테이블의 시작 주소를 설정한다.

 

sparse_index_alloc()

mm/sparse.c

static struct mem_section noinline __init_refok *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()) {
                if (node_state(nid, N_HIGH_MEMORY))
                        section = kzalloc_node(array_size, GFP_KERNEL, nid);
                else
                        section = kzalloc(array_size, GFP_KERNEL);
        } else {
                section = memblock_virt_alloc_node(array_size, nid);
        }

        return section;
}

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

  • unsigned long array_size = SECTIONS_PER_ROOT * sizeof(struct mem_section);
    • 할당할 사이즈를 구한다. 항상 페이지 크기(4K) 보다 같거나 작게 얻어진다.
      • SECTIONS_PER_ROOT
        • 루트 엔트리 당 mem_section[] 배열의 크기
        • 예) arm64: 256개
      • sizeof(struct mem_section)
        • 예) arm64: 페이지 확장 정보(CONFIG_PAGE_EXTENSION)없이 16 bytes
  • if (slab_is_available()) {
    • slab 정규 메모리 할당자가 동작하는 경우
      • if (node_state(nid, N_HIGH_MEMORY)) section = kzalloc_node(array_size, GFP_KERNEL, nid);
        • 노드가 highmem인 경우 kzalloc_node() 함수를 통해 메모리를 할당한다.
      • else section = kzalloc(array_size, GFP_KERNEL);
        • 그렇지 않은 경우 kzalloc() 함수를 통해 lowmem에서 메모리를 할당한다.
  • else { section = memblock_virt_alloc_node(array_size, nid); }
    • slab 정규 메모리 할당자가 동작하지 않는 경우 해당 노드의 memblock에 할당한다.

 

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)
{
        if (!mem_section[SECTION_NR_TO_ROOT(nr)])
                return NULL;
        return &mem_section[SECTION_NR_TO_ROOT(nr)][nr & SECTION_ROOT_MASK];
}

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

  • if (!mem_section[SECTION_NR_TO_ROOT(nr)]) return NULL;
    • SECTION_NR_TO_ROOT()
      • 섹션 번호로 루트섹션 번호를 알아온다.
    • mem_section 값이 없으면 null을 리턴한다.
  • return &mem_section[SECTION_NR_TO_ROOT(nr)][nr & SECTION_ROOT_MASK];
    • mem_section 구조체 정보를 리턴한다.
    • SECTION_ROOT_MASK
      • CONFIG_SPARSEMEM_EXTREME 커널 옵션을 사용하지 않는 경우 항상 0이다.
      • 커널 옵션을 사용하는 경우 (SECTIONS_PER_ROOT-1)이다.
        • arm64: 255

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

__nr_to_section-1

 

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(2) 만큼 좌측으로 쉬프트한다.

 

sparse_init()

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

  • 노드별 usemap을 할당하고 usemap_map에 매핑한다.
  • CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER 옵션을 사용 시 노드별 mem_map을 할당하고 map_map에 매핑한다.
  • CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER 옵션을 사용하지 않는 경우 섹션별 mem_map을 할당한다.
  • 할당된 mem_map을 mem_section에 매핑한다.
  • 추가적으로 CONFIG_SPARSEMEM_VMEMMAP 커널 옵션을 사용하면 mem_map 대신 vmemmap을 만들어 사용한다.

sparse_init-1a

 

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

sparse_init-5a

 

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;
        struct page *map;
        unsigned long *usemap;
        unsigned long **usemap_map;
        int size;
#ifdef CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER
        int size2;
        struct page **map_map;
#endif

        /* see include/linux/mmzone.h 'struct mem_section' definition */
        BUILD_BUG_ON(!is_power_of_2(sizeof(struct mem_section)));

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

        /*
         * map is using big page (aka 2M in x86 64 bit)
         * usemap is less one page (aka 24 bytes)
         * so alloc 2M (with 2M align) and 24 bytes in turn will
         * make next 2M slip to one more 2M later.
         * then in big system, the memory will have a lot of holes...
         * here try to allocate 2M pages continuously.
         *
         * powerpc need to call sparse_init_one_section right after each
         * sparse_early_mem_map_alloc, so allocate usemap_map at first.
         */
        size = sizeof(unsigned long *) * NR_MEM_SECTIONS;
        usemap_map = memblock_virt_alloc(size, 0);
        if (!usemap_map)
                panic("can not allocate usemap_map\n");
        alloc_usemap_and_memmap(sparse_early_usemaps_alloc_node,
                                                        (void *)usemap_map);

#ifdef CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER
        size2 = sizeof(struct page *) * NR_MEM_SECTIONS;
        map_map = memblock_virt_alloc(size2, 0);
        if (!map_map)
                panic("can not allocate map_map\n");
        alloc_usemap_and_memmap(sparse_early_mem_maps_alloc_node,
                                                        (void *)map_map);
#endif
  • set_pageblock_order();
    • 전역 변수 pageblock_order를 2가지 방법 중 하나로 설정한다.
      • HPAGE_SHIFT가 PAGE_SHIFT보다 큰 경우 HUGETLB_PAGE_ORDER로 설정하고 그렇지 않은 경우 MAX_ORDER(11)-1로 설정한다.

usemap_map[] 배열 영역 할당 및 각 노드에 대한 usemap 영역 할당 및 매핑을 한다.

  • size = sizeof(unsigned long *) * NR_MEM_SECTIONS;
    • 섹션 수 x 포인터 길이 만큼의 사이즈
  • usemap_map = memblock_virt_alloc(size, 0);
    • usemap_map[] 배열용 메모리를 할당 받는다.
  • alloc_usemap_and_memmap(sparse_early_usemaps_alloc_node, (void *)usemap_map);
    • 한 개 노드에 대한 usemap을 할당 받고 usemap_map[]에 매핑한다.

CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER 커널 옵션을 사용하는 경우 map_map[] 배열 영역 할당 및 각 노드에 대한 mem_map 영역 할당 및 매핑을 한다.

  • size2 = sizeof(struct page *) * NR_MEM_SECTIONS;
    • 섹션 수 x page 구조체 길이 만큼의 사이즈
  • map_map = memblock_virt_alloc(size2, 0);
    • map_map[] 배열용 메모리를 할당 받는다.
  • alloc_usemap_and_memmap(sparse_early_mem_maps_alloc_node, (void *)map_map);
    • 한 개 노드에 대한 mem_map을 할당 받고 map_map[]에 매핑한다.

(A) 아래 그림은 usemap_map 할당 및 노드별 usemap 할당을 하는 것을 보여준다.

sparse_init-2

 

        for (pnum = 0; pnum < NR_MEM_SECTIONS; pnum++) {
                if (!present_section_nr(pnum))
                        continue;

                usemap = usemap_map[pnum];
                if (!usemap)
                        continue;

#ifdef CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER
                map = map_map[pnum];
#else
                map = sparse_early_mem_map_alloc(pnum);
#endif
                if (!map)
                        continue;

                sparse_init_one_section(__nr_to_section(pnum), pnum, map,
                                                                usemap);
        }

        vmemmap_populate_print_last();

#ifdef CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER
        memblock_free_early(__pa(map_map), size2);
#endif
        memblock_free_early(__pa(usemap_map), size);
}
  • for (pnum = 0; pnum < NR_MEM_SECTIONS; pnum++) {
    • 섹션 수 만큼 루프를 돈다.
  • if (!present_section_nr(pnum)) continue;
    • 섹션 번호에 해당하는 mem_section->section_mem_map에 PRESENT 비트가 설정되어 있는 경우가 아니면 다음 섹션을 계속한다.
  • map = map_map[pnum];
    • CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER 커널 옵션을 사용하는 경우 page 포인터인 map 변수에 해당 섹션에 대한 map_map[] 엔트리를 대입한다.
  • map = sparse_early_mem_map_alloc(pnum);
    • CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER 커널 옵션을 사용하지 않는 경우 아무것도 수행하지 않는다.
  • sparse_init_one_section(__nr_to_section(pnum), pnum, map, usemap);

 

(B) 아래 그림은  map_map 할당 및 노드별 mem_map 할당을 하는 것을 보여준다.

  • CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER 커널 옵션을 사용하지 않는 경우 mem_map과 map_map을 할당하지 않고 다음 (C) 그림과 같이 mem_map을 섹션 별로 할당 받아 mem_section에 매핑한다.
  • 결국 위 커널 옵션을 사용하는 경우 생성되는 mem_map은 map_map과 mem_section에서 같이 사용된다.

sparse_init-3

 

#ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE
/* Initialise the number of pages represented by NR_PAGEBLOCK_BITS */
void __paginginit 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 __paginginit set_pageblock_order(void)
{
}
#endif /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */

 

(C) 아래 그림은 노드별 mem_map 할당 후 mem_section에 매핑을 하는 것을 보여준다.

  • 만일 CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER 커널 옵션을 사용하는 경우는 이미 (B) 그림에서와 같이 이미 먼저 mem_map이 할당되어 있으므로 섹션 별 mem_map의 할당을 하지 않도록 한다.

sparse_init-4

 

alloc_usemap_and_memmap()

mm/sparse.c

/**
 *  alloc_usemap_and_memmap - memory alloction for pageblock flags and vmemmap
 *  @map: usemap_map for pageblock flags or mmap_map for vmemmap
 */
static void __init alloc_usemap_and_memmap(void (*alloc_func)
                                        (void *, unsigned long, unsigned long,
                                        unsigned long, int), void *data)
{
        unsigned long pnum;
        unsigned long map_count;
        int nodeid_begin = 0;
        unsigned long pnum_begin = 0;

        for (pnum = 0; pnum < NR_MEM_SECTIONS; pnum++) {
                struct mem_section *ms;

                if (!present_section_nr(pnum))
                        continue;
                ms = __nr_to_section(pnum);
                nodeid_begin = sparse_early_nid(ms);
                pnum_begin = pnum;
                break;
        }
        map_count = 1;
        for (pnum = pnum_begin + 1; pnum < NR_MEM_SECTIONS; pnum++) {
                struct mem_section *ms;
                int nodeid;

                if (!present_section_nr(pnum))
                        continue;
                ms = __nr_to_section(pnum);
                nodeid = sparse_early_nid(ms);
                if (nodeid == nodeid_begin) {
                        map_count++;
                        continue;
                }
                /* ok, we need to take cake of from pnum_begin to pnum - 1*/
                alloc_func(data, pnum_begin, pnum,
                                                map_count, nodeid_begin);
                /* new start, update count etc*/
                nodeid_begin = nodeid;
                pnum_begin = pnum;
                map_count = 1;
        }
        /* ok, last chunk */
        alloc_func(data, pnum_begin, NR_MEM_SECTIONS,
                                                map_count, nodeid_begin);
}

이 함수는 usemap과 mem_map에 대하여 노드별 할당을 위해 시작 섹션과 끝 섹션 및 hole을  제외한 섹션 수를 파악하여 각각의 할당 함수를 호출하기 위해 사용된다.

mem_section[] 배열을 통해 hole을 제외한 시작 노드 번호와 시작 섹션 번호를 알아온다.

  • for (pnum = 0; pnum < NR_MEM_SECTIONS; pnum++) {
    • 섹션 수 만큼 루프를 돈다.
  • if (!present_section_nr(pnum)) continue;
    • 해당 mem_section이 매핑이 되어 있지 않으면 해당 섹션이 hole이라 판단하여 다음 섹션으로 계속한다.
  • ms = __nr_to_section(pnum);
    • pnum 섹션 번호에 대응하는 mem_section 구조체 정보를 알아온다.
  • nodeid_begin = sparse_early_nid(ms);
    • mem_section 구조체내에 있는 노드 번호를 알아와서 node 시작 번호로 한다.

mem_section[] 배열을 통해 .

  • for (pnum = pnum_begin + 1; pnum < NR_MEM_SECTIONS; pnum++) {
    • 시작 섹션 번호+1 부터 루프를 돈다.
  • if (!present_section_nr(pnum)) continue;
    • 해당 섹션이 hole이면 다음 섹션으로 계속한다.
  • ms = __nr_to_section(pnum);
    • pnum 섹션 번호에 대응하는 mem_section 구조체 정보를 알아온다.
  • nodeid = sparse_early_nid(ms);
    • mem_section 구조체내에 있는 노드 번호를 알아온다
  • if (nodeid == nodeid_begin) {
    • 만일 알아온 노드 번호와 시작 노드 번호가 동일하면 map_count를 증가시키고 다음 섹션으로 게속한다.
    • map_count
      • 같은 노드안의 hole을 제외한 섹션 수
  • alloc_func(data, pnum_begin, pnum, map_count, nodeid_begin);
    • 노드 번호가 바뀌면 sparse_early_usemaps_alloc_node() 함수를 호출하고 노드 시작 번호를 다시 현재 노드 번호로 바꾼다. 또한 시작 섹션 번호를 현재 섹션 번호로 바꾸고 map_count를 1로 설정한다.
      • sparse_early_usemaps_alloc_node()
        • usemap을 가능하면 pgdat가 있는 섹션 공간 내의 memblock에 할당 받는다
  • 루프가 완료되면 마지막으로 sparse_early_usemaps_alloc_node() 함수를 호출한다.

 

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에서 노드 정보를 추출하여 리턴한다.

 

sparse_early_usemaps_alloc_node()

mm/sparse.c

static void __init sparse_early_usemaps_alloc_node(void *data,
                                 unsigned long pnum_begin,
                                 unsigned long pnum_end,
                                 unsigned long usemap_count, int nodeid)
{
        void *usemap;
        unsigned long pnum;
        unsigned long **usemap_map = (unsigned long **)data;
        int size = usemap_size();

        usemap = sparse_early_usemaps_alloc_pgdat_section(NODE_DATA(nodeid),
                                                          size * usemap_count);
        if (!usemap) {
                printk(KERN_WARNING "%s: allocation failed\n", __func__);
                return;
        }

        for (pnum = pnum_begin; pnum < pnum_end; pnum++) {
                if (!present_section_nr(pnum))
                        continue;
                usemap_map[pnum] = usemap;
                usemap += size;
                check_usemap_section_nr(nodeid, usemap_map[pnum]);
        }
}

usemap을 가능하면 pgdat가 있는 섹션 공간 내의 memblock에 할당 받는다.

  • int size = usemap_size();
    • usemap 사이즈를 알아온다.
  • usemap = sparse_early_usemaps_alloc_pgdat_section(NODE_DATA(nodeid), size * usemap_count);
    • 지정된 노드에 가능하면 노드 정보가 기록된 섹션에 size * usemap_count 수 만큼의 공간을 할당받는다.
  • for (pnum = pnum_begin; pnum < pnum_end; pnum++) {
    • 시작 섹션 번호부터 끝 섹션 번호까지 루프를 돈다.
  • if (!present_section_nr(pnum)) continue;
    • 해당 섹션이 hole인 경우 다음 섹션을 계속한다.
  • usemap_map[pnum] = usemap;
    • usemap_map에 할당된 usemap 주소를 대입한다.
  • check_usemap_section_nr(nodeid, usemap_map[pnum]);
    • 할당 받은 usemap의 섹션이 노드 정보(pgdat)가 기록된 섹션과 같지 않으면 경고 메시지를 출력한다.

 

usemap_size()

mm/sparse.c

unsigned long usemap_size(void)
{
        unsigned long size_bytes;
        size_bytes = roundup(SECTION_BLOCKFLAGS_BITS, 8) / 8;
        size_bytes = roundup(size_bytes, sizeof(unsigned long));
        return size_bytes;
}

usemap 사이즈를 리턴한다.

  •  SECTION_BLOCKFLAGS_BITS
    • 섹션당 pageblock 비트 수
      • arm64=1024
  • 예) arm64
    • 1024 / 8=128(byte)

 

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)
{
        unsigned long goal, limit;
        unsigned long *p;
        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 = __pa(pgdat) & (PAGE_SECTION_MASK << PAGE_SHIFT);
        limit = goal + (1UL << PA_SECTION_SHIFT);
        nid = early_pfn_to_nid(goal >> PAGE_SHIFT);
again:
        p = memblock_virt_alloc_try_nid_nopanic(size,
                                                SMP_CACHE_BYTES, goal, limit,
                                                nid);
        if (!p && limit) {
                limit = 0;
                goto again;
        }
        return p;
}
#endif

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

  • goal = __pa(pgdat) & (PAGE_SECTION_MASK << PAGE_SHIFT);
    • pgdat가 담겨있는 섹션 시작 물리 주소
  • limit = goal + (1UL << PA_SECTION_SHIFT);
    • 1개의 섹션 크기로 제한
  • p = memblock_virt_alloc_try_nid_nopanic(size, SMP_CACHE_BYTES, goal, limit, nid);
    • 지정된 노드의 goal ~ limit 범위 즉 노드 정보가 담겨 있는 섹션 영역내에서 SMP_CACHE_BYTES align으로 size 만큼의 memblock 공간 할당을 요청
  • if (!p && limit) { limit = 0; goto again; }
    • 한 번 시도해서 할당이 안되면 limit를 0으로 만들어 다시 한 번의 기회를 갖고 시도한다.

 

check_usemap_section_nr()

mm/sparse.c

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

        usemap_snr = pfn_to_section_nr(__pa(usemap) >> PAGE_SHIFT);
        pgdat_snr = pfn_to_section_nr(__pa(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) {
                printk(KERN_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.
         */
        printk(KERN_INFO "Section %ld and %ld (node %d)", usemap_snr,
               pgdat_snr, nid);
        printk(KERN_CONT
               " have a circular dependency on usemap and pgdat allocations\n");
}
#else
static void __init check_usemap_section_nr(int nid, unsigned long *usemap)
{
}
#endif /* CONFIG_MEMORY_HOTREMOVE */

CONFIG_MEMORY_HOTREMOVE 커널 옵션이 사용된 경우 usemap 섹션은 pgdat가 위치한 섹션에 있거나 그렇지 않은 경우 다른 섹션이 모두 삭제될 때 까지 usemap이 위치한 섹션은 삭제되면 안된다. 따라서 이에 대한 관계를 메시지로 알아보기 위한 루틴이다. 참고: memory hotplug: allocate usemap on the section with pgdat

  • static unsigned long old_usemap_snr = NR_MEM_SECTIONS;
    • old_usemap_snr
      • usemap이 존재하는 섹션 번호를 일단 전체 섹션 수로 초기화한다.
  • static unsigned long old_pgdat_snr = NR_MEM_SECTIONS;
    • old_pgdat_snr
      • 역시 노드 정보(pgdat)가 존재하는 섹션 번호를 일단 전체 섹션 수로 초기화한다.
  • usemap_snr = pfn_to_section_nr(__pa(usemap) >> PAGE_SHIFT);
    • usemap이 위치한 섹션 번호를 알아온다.
  • pgdat_snr = pfn_to_section_nr(__pa(pgdat) >> PAGE_SHIFT);
    • pgdat가 위치한 섹션 번호를 알아온다.
  • if (usemap_snr == pgdat_snr) return;
    • 같은 섹션에 존재하면 리턴한다.
  • if (old_usemap_snr == usemap_snr && old_pgdat_snr == pgdat_snr) return;
    • 아래 메시지가 중복되어 출력되지 않도록 block한다.
  • usemap_nid = sparse_early_nid(__nr_to_section(usemap_snr));
    • usemap이 위치한 섹션의 노드 번호를 알아온다.
  • if (usemap_nid != nid) {
    • usemap이 위치한 노드 번호가 지정된 노드 번호와 다른 경우 섹션을 제거하기 전에 지정된 노드가 먼저 제거되어야 한다는 에러 메시지를 출력하고 리턴한다.
  • 그 외에 circular dependency가 걸려 있다는 경고 메시지를 출력한다.

 

sparse_early_mem_maps_alloc_node()

mm/sparse.c

#ifdef CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER
static void __init sparse_early_mem_maps_alloc_node(void *data,
                                 unsigned long pnum_begin,
                                 unsigned long pnum_end,
                                 unsigned long map_count, int nodeid)
{
        struct page **map_map = (struct page **)data;
        sparse_mem_maps_populate_node(map_map, pnum_begin, pnum_end,
                                         map_count, nodeid);
}
#endif

CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER 커널 옵션이 사용되면 mem_map 영역을 만들어 사용할 수 있도록 한다.

map_map[] 배열의 각 엔트리는 hole이 아닌 경우 mem_map의 첫 번째 엔트리를 가리킨다.

mem_map은 sparse_mem_maps_populate_node() -> sparse_mem_maps_populate_node() 함수를 통해 할당된다.

 

sparse_mem_maps_populate_node()

mm/sparse.c

#ifndef CONFIG_SPARSEMEM_VMEMMAP
void __init sparse_mem_maps_populate_node(struct page **map_map,
                                          unsigned long pnum_begin,
                                          unsigned long pnum_end,
                                          unsigned long map_count, int nodeid)
{
        void *map;
        unsigned long pnum;
        unsigned long size = sizeof(struct page) * PAGES_PER_SECTION;

        map = alloc_remap(nodeid, size * map_count);
        if (map) {
                for (pnum = pnum_begin; pnum < pnum_end; pnum++) {
                        if (!present_section_nr(pnum))
                                continue;
                        map_map[pnum] = map;
                        map += size;
                }
                return;
        }

        size = PAGE_ALIGN(size);
        map = memblock_virt_alloc_try_nid(size * map_count,
                                          PAGE_SIZE, __pa(MAX_DMA_ADDRESS),
                                          BOOTMEM_ALLOC_ACCESSIBLE, nodeid);
        if (map) {
                for (pnum = pnum_begin; pnum < pnum_end; pnum++) {
                        if (!present_section_nr(pnum))
                                continue;
                        map_map[pnum] = map;
                        map += size;
                }
                return;
        }

        /* fallback */
        for (pnum = pnum_begin; pnum < pnum_end; pnum++) {
                struct mem_section *ms;

                if (!present_section_nr(pnum))
                        continue;
                map_map[pnum] = sparse_mem_map_populate(pnum, nodeid);
                if (map_map[pnum])
                        continue;
                ms = __nr_to_section(pnum);
                printk(KERN_ERR "%s: sparsemem memory map backing failed "
                        "some memory will not be available.\n", __func__);
                ms->section_mem_map = 0;
        }
}
#endif /* !CONFIG_SPARSEMEM_VMEMMAP */

리매핑이 필요한 tile 아키텍처에서 map_map[]에 대해 hole이 아닌 섹션에 대해 mem_map의 할당과 매핑이 이루어진다.

  • unsigned long size = sizeof(struct page) * PAGES_PER_SECTION;
    • PAGES_PER_SECTION
      • 섹션당 페이지 수
  • map = alloc_remap(nodeid, size * map_count);
    • tile 아키텍처만 사용되며 그 외의 아키텍처는 null을 리턴한다.
  • for (pnum = pnum_begin; pnum < pnum_end; pnum++) {
    • tile 아키텍처에서 map이 재할당된 경우 pnum_bigin ~ pnum_end 까지 섹션 번호를 1씩 증가시키며 루프를 돌며 hole이 아닌 섹션에 대해 map_map[pnum]에 map을 가리키게 하고 map에 사이즈를 더한다. 루프가 끝나면 종료한다.

리매핑이 필요 없는 아키텍처에서 사이즈 x 섹션 수 크기만큼을 할당 받은 후 map_map[]의 매핑이 이루어진다.

  • size = PAGE_ALIGN(size);
    • 사이즈를 페이지 단위로 round up한다.
  • map = memblock_virt_alloc_try_nid(size * map_count, PAGE_SIZE,  __pa(MAX_DMA_ADDRESS), BOOTMEM_ALLOC_ACCESSIBLE, nodeid);
    • 사이즈 * map_count 만큼 MAX_DMA_ADDRESS ~ lowmem 영역 범위에 memblock 할당 요청을 한다.

노드에 대해 필요한 섹션에 대한  영역 전체 크기에 대해 memblock 할당 요청이 실패한 경우 각 섹션에 필요한 작은 단위로 mem_map을 다시 할당 받는다.

  • for (pnum = pnum_begin; pnum < pnum_end; pnum++) {
    • 시작 섹션 번호 부터 끝 섹션 번호 까지 루프를 돌며
  • if (!present_section_nr(pnum)) continue;
    • hole 영역인 경우 계속
  • map_map[pnum] = sparse_mem_map_populate(pnum, nodeid);
    • mem_section의 멤버변수 section_mem_map에  SECTION_MARKED_PRESENT이 존재하는 경우 해당 섹션 번호에 대한 영역 만큼만 MAX_DMA_ADDRESS ~ lowmem 영역 범위에서 mem_map 공간을 memblock에 할당받는다.
  • if (map_map[pnum]) continue;
    • 성공한 경우 다음 섹션 번호로 계속한다.
  • ms = __nr_to_section(pnum);
    • 섹션 번호로 mem_section 구조체 정보를 알아온다.
  • ms->section_mem_map = 0;
    • 실패한 경우 해당 섹션에 대해 mem_section 매핑을 disable한다.

 

sparse_mem_map_populate()

mm/sparse.c

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

        map = alloc_remap(nid, sizeof(struct page) * PAGES_PER_SECTION);
        if (map)
                return map;

        size = PAGE_ALIGN(sizeof(struct page) * PAGES_PER_SECTION);
        map = memblock_virt_alloc_try_nid(size,
                                          PAGE_SIZE, __pa(MAX_DMA_ADDRESS),
                                          BOOTMEM_ALLOC_ACCESSIBLE, nid);
        return map;
}
#endif /* !CONFIG_SPARSEMEM_VMEMMAP */
  • alloc_remap()
    • sparse_mem_maps_populate_node() 함수 참고
  • 지정된 노드의 memblock에서 page 구조체 x 섹션당 page 크기만큼의 사이즈로 MAX_DMA_ADDRESS ~ lowmem 영역에서 할당을 시도한다.

 

VMEMMAP 관련

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

 

sparse_early_mem_map_alloc()

mm/sparse.c

static struct page __init *sparse_early_mem_map_alloc(unsigned long pnum)
{
        struct page *map;
        struct mem_section *ms = __nr_to_section(pnum);
        int nid = sparse_early_nid(ms);

        map = sparse_mem_map_populate(pnum, nid);
        if (map)
                return map;

        pr_err("%s: sparsemem memory map backing failed some memory will not be available\n",
               __func__);
        ms->section_mem_map = 0;
        return NULL;
}

 

 

 

sparse_mem_map_populate()

mm/sparse-vmemmap.c

struct page * __meminit sparse_mem_map_populate(unsigned long pnum, int nid)
{
        unsigned long start;
        unsigned long end;
        struct page *map;

        map = pfn_to_page(pnum * PAGES_PER_SECTION);
        start = (unsigned long)map;
        end = (unsigned long)(map + PAGES_PER_SECTION);

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

        return map;
}

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

 

vmemmap_populate()

arch/arm64/mm/mmu.c

int __meminit vmemmap_populate(unsigned long start, unsigned long end, int node)
{
        unsigned long addr = start;
        unsigned long next;
        pgd_t *pgd;
        pud_t *pud;
        pmd_t *pmd;

        do {
                next = pmd_addr_end(addr, end);

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

                pud = vmemmap_pud_populate(pgd, addr, node);
                if (!pud)
                        return -ENOMEM;

                pmd = pmd_offset(pud, addr);
                if (pmd_none(*pmd)) {
                        void *p = NULL;

                        p = vmemmap_alloc_block_buf(PMD_SIZE, node);
                        if (!p)
                                return -ENOMEM;

                        set_pmd(pmd, __pmd(__pa(p) | PROT_SECT_NORMAL));
                } else
                        vmemmap_verify((pte_t *)pmd, node, addr, next);
        } while (addr = next, addr != end);

        return 0;
}

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

 

vmemmap_populate_print_last()

mm/sparse.c

void __weak __meminit vmemmap_populate_print_last(void)
{
}

arch/x86/mm/init_64.c

#ifdef CONFIG_SPARSEMEM_VMEMMAP
void __meminit vmemmap_populate_print_last(void)
{
        if (p_start) {
                printk(KERN_DEBUG " [%lx-%lx] PMD -> [%p-%p] on node %d\n",
                        addr_start, addr_end-1, p_start, p_end-1, node_start);
                p_start = NULL;
                p_end = NULL;
                node_start = 0;
        }
}
#endif

 

sparse_init_one_section()

mm/sparse.c

static int __meminit sparse_init_one_section(struct mem_section *ms,
                unsigned long pnum, struct page *mem_map,
                unsigned long *pageblock_bitmap)
{
        if (!present_section(ms))
                return -EINVAL;

        ms->section_mem_map &= ~SECTION_MAP_MASK;
        ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum) |
                                                        SECTION_HAS_MEM_MAP;
        ms->pageblock_flags = pageblock_bitmap;

        return 1;
}

mem_map 영역을 mem_section에 매핑하고 usemap 영역도 매핑한다.

 

/*
 * 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)
{
        return (unsigned long)(mem_map - (section_nr_to_pfn(pnum)));
}

할당 받은 mem_map의 주소 – 섹션 변호로 알아온 pfn 값을 엔코딩 값으로 반환한다.

  • 추후 mem_map 주소에서 mem_map 주소와 pfn 값 두 가지를 디코딩하여 사용할 목적으로 엔코딩해 놓는다.

 

구조체 및 주요 변수

 

mem_section[]

mm/sparse.c

/*
 * Permanent SPARSEMEM data:
 *
 * 1) mem_section       - memory sections, mem_map's for valid memory
 */     
#ifdef CONFIG_SPARSEMEM_EXTREME
struct mem_section *mem_section[NR_SECTION_ROOTS]
        ____cacheline_internodealigned_in_smp;
#else
struct mem_section mem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT]
        ____cacheline_internodealigned_in_smp;
#endif
EXPORT_SYMBOL(mem_section);
  • mem_section은 page 구조체 배열로 구성된 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;

        /* See declaration of similar field in struct zone */
        unsigned long *pageblock_flags;
#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을 가리키는 주소
      • pageblock_order 단위로 align한 위치를 가리킨다.
    • lsb 2개 비트를 추가 정보로 사용한다.
      • bit0: SECTION_MARKED_PRESENT
        • present_section() 함수로 섹션의 존재 유무를 알 수 있다.
      • bit1: SECTION_HAS_MEM_MAP
        • CONFIG_HAVE_ARCH_PFN_VALID 커널 옵션을 사용하면  pfn_valid() 함수로 페이지의 valid  유무를 알 수 있다.
      • bits[3~]: 노드 번호
        • 원래는 페이지 프레임을 관리하는 page 구조체의 멤버 변수 flags에 노드 번호를 기록하게 되어 있는데 32bit 시스템에서는 여유 있는 비트가 없을 수도 있다. 이러한 경우 NODE_NOT_IN_PAGE_FLAGS 커널 옵션을 사용하는 경우 섹션 정보에 노드 번호를 기록하여 섹션 별로 노드 번호를 알 수 있게 한다.
  • pageblock_flags
    • usemap을 가리키는 주소
      • 가리키는 주소는 엔코딩하여 사용된다.

 

기타

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

 

참고

 

 

 

답글 남기기

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