mem_init()

<kernel v5.0>

free 메모리를 버디 시스템으로 이관

lowmem 및 highmem에 대한 모든 free 메모리에 대해 memblock 영역에서 버디 메모리 할당자로 이관한다.

 

mem_init() – ARM64

arch/arm64/mm/init.c

/*
 * mem_init() marks the free areas in the mem_map and tells us how much memory
 * is free.  This is done after various parts of the system have claimed their
 * memory after the kernel image.
 */
void __init mem_init(void)
{
        if (swiotlb_force == SWIOTLB_FORCE ||
            max_pfn > (arm64_dma_phys_limit >> PAGE_SHIFT))
                swiotlb_init(1);
        else
                swiotlb_force = SWIOTLB_NO_FORCE;

        set_max_mapnr(pfn_to_page(max_pfn) - mem_map);

#ifndef CONFIG_SPARSEMEM_VMEMMAP
        free_unused_memmap();
#endif
        /* this will put all unused low memory onto the freelists */
        memblock_free_all();

        kexec_reserve_crashkres_pages();

        mem_init_print_info(NULL);

        /*
         * Check boundaries twice: Some fundamental inconsistencies can be
         * detected at build time already.
         */
#ifdef CONFIG_COMPAT
        BUILD_BUG_ON(TASK_SIZE_32 > DEFAULT_MAP_WINDOW_64);
#endif

        if (PAGE_SIZE >= 16384 && get_num_physpages() <= 128) {
                extern int sysctl_overcommit_memory;
                /*
                 * On a machine this small we won't get anywhere without
                 * overcommit, so turn it on by default.
                 */
                sysctl_overcommit_memory = OVERCOMMIT_ALWAYS;
        }
}

기존 memblock에서 reserve한 공간을 제외한 빈 공간들을 모두 버디 시스템에 등록하여 버디 시스템을 사용할 준비를 수행한다.

  • 코드 라인 3~7에서 “swiotlb=force” 커널 파라미터가 지정되거나 max_pfn이 dma 영역을 초과한 경우 sw 방식의 iotlb를 사용을 위해 I/O TLB용 버퍼 메모리를 할당한다.
    • arm64 디폴트 커널은 CONFIG_SWIOTLB를 사용한다.
    • “swiotlb=<force|noforce>” early 파라미터로 설정된다.
    • 예) ” software IO TLB [mem 0xef400000-0xf3400000] (64MB) mapped at [ffffffc0ef400000-ffffffc0f33fffff]”
  • 코드 라인 9에서 싱글 노드를 사용하는 시스템인 경우에만 전역 max_mapnr에 mem_map[] 배열에 대한 인덱스 번호를 저장한다.
  • 코드 라인 11~13에서 vmemmap을 사용하지 않는 경우 sparse 메모리 모델 또는 discontiguous 메모리 모델에서 메모리 사이의 사용되지 않는 공간이 상당히 클 수 있다. 따라서 이에 대해 메모리 낭비가 발생하지 않도록 미사용 공간에 대한 mem_map[ ]을 페이지 단위로 reserved memblock에서 free시킨다. x86과 ARM64 등에서는 CONFIG_SPARSEMEM_VMEMMAP의 사용이 가능하다.
  • 코드 라인 15에서 free memblock 영역에 대해 모두 버디 메모리 할당자의 빈 페이지로 이관 등록한다. memblock을 더 이상 사용하지 않는 경우 reserved & memory memblock 관리 배열까지 버디로 free시킨다.
    • ARM32의 경우는 추가 코드로 highmem 메모리 영역에 대해 버디 시스템으로 이관하는 free_highpage( ) 함수를 호출하는 코드가 있지만, ARM64에서는 highmem이 없으므로 해당 코드를 호출하지 않는다.
  • 코드 라인 19에서 메모리 초기화 정보를 출력한다.
    • 예) ” Memory: 2611360K/3145728K available (16060K kernel code, 8910K rwdata, 10300K rodata, 1664K init, 9117K bss, 501600K reserved, 32768K cma-reserved)”
  • 코드 라인 29~36에서 페이지 사이즈가 16K 이상이면서 물리 페이지가 128개 이하인 경우에 한해 메모리 할당 시 오버 커밋을 허용하게 한다.

 

다음 그림은 vmemmap을 사용하는 ARM64 시스템에서 mem_init() 함수를 통해 free 메모리를  memblock 할당자에서 버디 메모리 할당자로 전환하는 모습을 보여준다.

 

mem_init() – ARM32

arch/arm/mm/init.c

/*
 * mem_init() marks the free areas in the mem_map and tells us how much
 * memory is free.  This is done after various parts of the system have
 * claimed their memory after the kernel image.
 */
void __init mem_init(void)
{
#ifdef CONFIG_HAVE_TCM
        /* These pointers are filled in on TCM detection */
        extern u32 dtcm_end;
        extern u32 itcm_end;
#endif

        set_max_mapnr(pfn_to_page(max_pfn) - mem_map);

        /* this will put all unused low memory onto the freelists */
        free_unused_memmap();
        memblock_free_all();

#ifdef CONFIG_SA1111
        /* now that our DMA memory is actually so designated, we can free it */
        free_reserved_area(__va(PHYS_OFFSET), swapper_pg_dir, -1, NULL);
#endif

        free_highpages();

        mem_init_print_info(NULL);

기존 memblock에서 reserve한 공간을 제외한 빈 공간들을 모두 버디 시스템에 등록하여 버디 시스템을 사용할 준비를 수행한다.

  • 코드 라인 9에서 싱글 노드 시스템에서만 전역 max_mapnr에 mem_map[] 배열에 대한 인덱스 번호를 저장한다.
  • 코드 라인 12에서 Sparse 메모리 모델 또는 Discontig 메모리 모델에서 메모리 사이의 사용되지 않는 공간이 상당히 클 수 있다. 따라서 이에 대해 메모리 낭비가 발생하지 않도록 미사용 공간에 대한 mem_map[]을 페이지 단위로 reserve memblock에서 free 시킨다.
  • 코드 라인 13에서 free memblock 영역에 대해 모두 Buddy memory allocator의 빈 페이지로 이관 등록한다.
    • memblock을 더 이상 사용하지 않는 경우 reserved & memory memblock 관리배열까지 Buddy로 free 시킨다.
  • 코드 라인 20에서 highmem 메모리 영역을 모두 Buddy memory allocator의 free 페이지로 이관 등록한다.

 

#define MLK(b, t) b, t, ((t) - (b)) >> 10
#define MLM(b, t) b, t, ((t) - (b)) >> 20
#define MLK_ROUNDUP(b, t) b, t, DIV_ROUND_UP(((t) - (b)), SZ_1K)

        pr_notice("Virtual kernel memory layout:\n"
                        "    vector  : 0x%08lx - 0x%08lx   (%4ld kB)\n"
#ifdef CONFIG_HAVE_TCM
                        "    DTCM    : 0x%08lx - 0x%08lx   (%4ld kB)\n"
                        "    ITCM    : 0x%08lx - 0x%08lx   (%4ld kB)\n"
#endif
                        "    fixmap  : 0x%08lx - 0x%08lx   (%4ld kB)\n"
                        "    vmalloc : 0x%08lx - 0x%08lx   (%4ld MB)\n"
                        "    lowmem  : 0x%08lx - 0x%08lx   (%4ld MB)\n"
#ifdef CONFIG_HIGHMEM
                        "    pkmap   : 0x%08lx - 0x%08lx   (%4ld MB)\n"
#endif
#ifdef CONFIG_MODULES
                        "    modules : 0x%08lx - 0x%08lx   (%4ld MB)\n"
#endif
                        "      .text : 0x%p" " - 0x%p" "   (%4td kB)\n"
                        "      .init : 0x%p" " - 0x%p" "   (%4td kB)\n"
                        "      .data : 0x%p" " - 0x%p" "   (%4td kB)\n"
                        "       .bss : 0x%p" " - 0x%p" "   (%4td kB)\n",

                        MLK(VECTORS_BASE, VECTORS_BASE + PAGE_SIZE),
#ifdef CONFIG_HAVE_TCM
                        MLK(DTCM_OFFSET, (unsigned long) dtcm_end),
                        MLK(ITCM_OFFSET, (unsigned long) itcm_end),
#endif
                        MLK(FIXADDR_START, FIXADDR_END),
                        MLM(VMALLOC_START, VMALLOC_END),
                        MLM(PAGE_OFFSET, (unsigned long)high_memory),
#ifdef CONFIG_HIGHMEM
                        MLM(PKMAP_BASE, (PKMAP_BASE) + (LAST_PKMAP) *
                                (PAGE_SIZE)),
#endif
#ifdef CONFIG_MODULES
                        MLM(MODULES_VADDR, MODULES_END),
#endif

                        MLK_ROUNDUP(_text, _etext),
                        MLK_ROUNDUP(__init_begin, __init_end),
                        MLK_ROUNDUP(_sdata, _edata),
                        MLK_ROUNDUP(__bss_start, __bss_stop));

#undef MLK
#undef MLM
#undef MLK_ROUNDUP

        /*
         * Check boundaries twice: Some fundamental inconsistencies can
         * be detected at build time already.
         */
#ifdef CONFIG_MMU
        BUILD_BUG_ON(TASK_SIZE                          > MODULES_VADDR);
        BUG_ON(TASK_SIZE                                > MODULES_VADDR);
#endif

#ifdef CONFIG_HIGHMEM
        BUILD_BUG_ON(PKMAP_BASE + LAST_PKMAP * PAGE_SIZE > PAGE_OFFSET);
        BUG_ON(PKMAP_BASE + LAST_PKMAP * PAGE_SIZE      > PAGE_OFFSET);
#endif
}

가상 메모리 레이아웃 정보를 출력한다.

  • 예) 다음과 같은 출력 정보를 보여준다.
Virtual kernel memory layout:
    modules : 0xffffff8000000000 - 0xffffff8008000000   (   128 MB)
    vmalloc : 0xffffff8008000000 - 0xffffffbdbfff0000   (   246 GB)
      .init : 0xffffff8009040000 - 0xffffff8009160000   (  1152 KB)
      .text : 0xffffff8008080000 - 0xffffff8008bf0000   ( 11712 KB)
    .rodata : 0xffffff8008bf0000 - 0xffffff8009040000   (  4416 KB)
      .data : 0xffffff8009160000 - 0xffffff80092f6008   (  1625 KB)
    vmemmap : 0xffffffbdc0000000 - 0xffffffbfc0000000   (     8 GB maximum)
              0xffffffbdc0008000 - 0xffffffbdc3e00000   (    61 MB actual)
    fixed   : 0xffffffbffe7fd000 - 0xffffffbffec00000   (  4108 KB)
    PCI I/O : 0xffffffbffee00000 - 0xffffffbfffe00000   (    16 MB)
    memory  : 0xffffffc000200000 - 0xffffffc0f8000000   (  3966 MB)

 

다음 그림은 early 메모리 할당자인 memblock 에서 버디 메모리 할당자로 free 메모리 관리가 전환되는 모습을 보여준다.

 


미사용 memmap 할당 해제

free_unused_memmap() – ARM32

arch/arm/mm/init.c

/*
 * The mem_map array can get very big.  Free the unused area of the memory map.
 */
static void __init free_unused_memmap(void)
{
        unsigned long start, prev_end = 0;
        struct memblock_region *reg;

        /*
         * This relies on each bank being in address order.
         * The banks are sorted previously in bootmem_init().
         */
        for_each_memblock(memory, reg) {
                start = memblock_region_memory_base_pfn(reg);

#ifdef CONFIG_SPARSEMEM
                /*
                 * Take care not to free memmap entries that don't exist
                 * due to SPARSEMEM sections which aren't present.
                 */
                start = min(start,
                                 ALIGN(prev_end, PAGES_PER_SECTION));
#else
                /*
                 * Align down here since the VM subsystem insists that the
                 * memmap entries are valid from the bank start aligned to
                 * MAX_ORDER_NR_PAGES.
                 */
                start = round_down(start, MAX_ORDER_NR_PAGES);
#endif
                /*
                 * If we had a previous bank, and there is a space
                 * between the current bank and the previous, free it.
                 */
                if (prev_end && prev_end < start)
                        free_memmap(prev_end, start);

                /*
                 * Align up here since the VM subsystem insists that the
                 * memmap entries are valid from the bank end aligned to
                 * MAX_ORDER_NR_PAGES.
                 */
                prev_end = ALIGN(memblock_region_memory_end_pfn(reg),
                                 MAX_ORDER_NR_PAGES);
        }

#ifdef CONFIG_SPARSEMEM
        if (!IS_ALIGNED(prev_end, PAGES_PER_SECTION))
                free_memmap(prev_end,
                            ALIGN(prev_end, PAGES_PER_SECTION));
#endif
}

mem_map[]에서 메모리 영역에 해당하지 않는 부분만 memblock에서 페이지 단위로 free하는데 메모리 모델에 따라 다음과 같이 동작한다.

  • Sparse 메모리 모델 (O)
    • 메모리와 메모리 사이의 hole에 대해서는 섹션이 구성되어 있지 않아 관련 mem_map[] 배열이 없어서 삭제할 mem_map[]이 없다.
    • 하나의 섹션을 가득 채우지 못한 메모리의 경우 남는 공간만큼의 mem_map[] 배열에 페이지 단위로 free 시킬 수 있다.
  • Discontig 메모리 모델 (X)
    • hole에 해당하는 mem_map[] 배열을 페이지 단위로 free시킬 수 있으나 arm에서 Discontig 메모리 모델을 사용하지 않게 되어 해당 사항 없다.
  • Flat 메모리 모델 (X)
    • hole이 없어 해당 사항 없다.

 

  • 코드 라인 10~11에서 memory memblock 엔트리 수 만큼 순회하며 시작 pfn 값을 알아온다.
    • PFN_UP(reg->base);
      • 물리 시작 주소에 PAGE_SIZE 단위로 round up한 후 pfn으로 변환
  • 코드 라인 18~19에서 Sparse memory model을 사용하는 경우 물리 시작 주소가 이전 기억해둔 memblock의 끝 주소를 섹션당 페이지 수(PAGES_PER_SECTION) 단위로 round up한 수를 초과하지 않게 한다.
  • 코드 라인 26에서 Sparse memory model이 아닌 경우 물리 시작 주소를 MAX_ORDER_NR_PAGES 단위로 round down 한다.
    • MAX_ORDER_NR_PAGES(1024)
  • 코드 라인 32~33에서 hole이 있는 경우 hole 만큼의 page를 관리하는 mem_map[]을 free 시킨다.
  • 코드 라인 40~41에서 memblock의 끝 주소를 round down하여 pfn으로 변환한 수를 MAX_ORDER_NR_PAGES 단위로 round up한 pfn 값
  • 코드 라인 45~47에서 Sparsemem에서 마지막 memory memblock의 끝이 PAGES_PER_SECTION 단위로 align되지 않은 경우 해당 영역에 해당하는 mem_map[]을 free 시킨다.

 

아래 그림은 두 개의 메모리 사이에 hole을 발생시켰고 각각의 섹션에 메모리가 일부만 채워진 경우 남는 공간에 대한 mem_map[] 배열에서 사용되지 않는 공간을 페이지 단위로 free 하는 것을  보여준다.

free_unused_memmap-1a

 

free_memmap()

arch/arm/mm/init.c

static inline void
free_memmap(unsigned long start_pfn, unsigned long end_pfn)
{
        struct page *start_pg, *end_pg;
        phys_addr_t pg, pgend;

        /*
         * Convert start_pfn/end_pfn to a struct page pointer.
         */
        start_pg = pfn_to_page(start_pfn - 1) + 1;
        end_pg = pfn_to_page(end_pfn - 1) + 1;

        /*
         * Convert to physical addresses, and
         * round start upwards and end downwards.
         */
        pg = PAGE_ALIGN(__pa(start_pg));
        pgend = __pa(end_pg) & PAGE_MASK;

        /*
         * If there are free pages between these,
         * free the section of the memmap array.
         */
        if (pg < pgend)
                memblock_free_early(pg, pgend - pg);
}

start_pfn ~ end_pfn에 해당하는 mem_map[] 영역을 reserve memblock에서 free(remove)한다.

  • start_pg = pfn_to_page(start_pfn – 1) + 1;
    • start_pfn으로 mem_map[]의 page 구조체 주소를 알아온다.
    • 인수로 주어진 start_pfn은 sparse memory의 경우 커널이 인식하지 못하는 unused 또는 hole 영역일 수 있기 때문에 이 주소를 사용하여 page 주소를 알아오려는 경우 잘못된 값을 알아올 수 있어서 패치를 하였다.
    • 참고: ARM: 5747/1: Fix the start_pg value in free_memmap()
  • end_pg = pfn_to_page(end_pfn – 1) + 1;
    • end_pfn으로 mem_map[]의 page 구조체 주소를 알아온다.
    • 역시 인수로 주어진 end_pfn은 sparse memory의 경우 커널이 인식하지 못하는 unused 또는 hole 영역일 수 있기 때문에 이 주소를 사용하여 page 주소를 알아오려는 경우 잘못된 값을 알아올 수 있어서 패치를 하였다
    • 참고: ARM: 6890/1: memmap: only free allocated memmap entries when using SPARSEMEM
  • pg = PAGE_ALIGN(__pa(start_pg));
    • start_pg를 물리주소로 변환하고 페이지 사이즈 단위로 round up 한다.
  • pgend = __pa(end_pg) & PAGE_MASK;
    • end_pg를 물리주소로 변환하고 페이지 사이즈 단위로 round down 한다.
  •  if (pg < pgend) memblock_free_early(pg, pgend – pg);
    • reserve memblock 영역에 등록된 mem_map[]의 unused 공간을 reserve memblock에서 free(remove) 한다.

 


free 메모리를 버디 시스템으로 이관

memblock_free_all()

mm/memblock.c

/**
 * memblock_free_all - release free pages to the buddy allocator
 *
 * Return: the number of pages actually released.
 */
unsigned long __init memblock_free_all(void)
{
        unsigned long pages;

        reset_all_zones_managed_pages();

        pages = free_low_memory_core_early();
        totalram_pages_add(pages);

        return pages;
}

memblock을 스캔하여 free 메모리 영역을 찾아 버디 시스템에 이관하는 과정을 알아본다. 이 함수에서는 모든 free lowmem 영역을 버디 시스템의 free_list에 이관 등록한다. 핫플러그 메모리를 사용하지 않는 대부분의 시스템은 버디 시스템이 활성화되면 memblock을 더 이상 사용하지 않게 되는데, 이때 reserved & memory memblock의 관리 배열을 더 이상 사용하지 않으므로 이에 대한 영역도 버디 시스템에 이관한다.

  • 코드 라인 5에서 모든 온라인 노드의 각 zone->managed_pages를 0으로 초기화한다. managed_pages 필드는 존에서 사용 가능한 free 페이지 수를 나타낸다.
  • 코드 라인 7에서 모든 free lowmem 영역들을 버디 시스템의 free_list에 이관 등록한다. memblock을 더 이상 사용하지 않는 경우 reserved & memory memblock 관리 배열도 버디 시스템의 free_list에 이관 등록한다.
  • 코드 라인 8에서 free된 페이지들을 전역 totalram_pages에 추가한다.

 

reset_all_zones_managed_pages()

mm/memblock.c

void __init reset_all_zones_managed_pages(void)
{
        struct pglist_data *pgdat;

        if (reset_managed_pages_done)
                return;

        for_each_online_pgdat(pgdat)
                reset_node_managed_pages(pgdat);

        reset_managed_pages_done = 1;
}

모든 online 노드의 각 zone->managed_pages를 0으로 초기화한다.

 

reset_node_managed_pages()

mm/memblock.c

void reset_node_managed_pages(pg_data_t *pgdat)
{
        struct zone *z;

        for (z = pgdat->node_zones; z < pgdat->node_zones + MAX_NR_ZONES; z++)
                atomic_long_set(&z->managed_pages, 0);
}

해당 노드의 모든 zone->managed_pages를 0으로 초기화한다.

 

커널 초기화 과정 이후 필요 없는 메모리 해제하기

free_low_memory_core_early()

mm/memblock.c

static unsigned long __init free_low_memory_core_early(void)
{
        unsigned long count = 0;
        phys_addr_t start, end;
        u64 i;

        memblock_clear_hotplug(0, -1);

        for_each_reserved_mem_region(i, &start, &end)
                reserve_bootmem_region(start, end);

        /*
         * We need to use NUMA_NO_NODE instead of NODE_DATA(0)->node_id
         *  because in some case like Node0 doesn't have RAM installed
         *  low ram will be on Node1
         */
        for_each_free_mem_range(i, NUMA_NO_NODE, MEMBLOCK_NONE, &start, &end,
                                NULL)
                count += __free_memory_core(start, end);

        return count;
}

모든 free lowmem 영역을 모두 버디 시스템의 free_list에 이관 등록한다. CONFIG_ARCH_DISCARD_MEMBLOCK 커널 옵션을 사용하는 경우 reserved & memory memblock의 관리 배열을 더 이상 사용하지 않으므로 이에 대한 영역도 버디 시스템에 이관한다.

  • 코드 라인 7에서 전체 memory memblock 영역에 대해 MEMBLOCK_HOTPLUG 비트를 클리어(clear)한다.
  • 코드 라인 9~10에서 전체 reserved memblock 영역의 모든 page 구조체에서 PG_reserved 비트를 설정하여 메모리가 이미 reserve되어 사용 중임을 마크한다.
  • 코드 라인 17~19에서 모든 free lowmem 영역을 버디 시스템의 free_list에 이관 등록한다.

 

reserve_bootmem_region()

mm/page_alloc.c

/*
 * Initialised pages do not have PageReserved set. This function is
 * called for each range allocated by the bootmem allocator and
 * marks the pages PageReserved. The remaining valid pages are later
 * sent to the buddy page allocator.
 */
void __meminit reserve_bootmem_region(phys_addr_t start, phys_addr_t end)
{
        unsigned long start_pfn = PFN_DOWN(start);
        unsigned long end_pfn = PFN_UP(end);

        for (; start_pfn < end_pfn; start_pfn++) {
                if (pfn_valid(start_pfn)) {
                        struct page *page = pfn_to_page(start_pfn);

                        init_reserved_page(start_pfn);

                        /* Avoid false-positive PageTail() */
                        INIT_LIST_HEAD(&page->lru);

                        /*
                         * no need for atomic set_bit because the struct
                         * page is not visible yet so nobody should
                         * access it yet.
                         */
                        __SetPageReserved(page);
                }
        }
}

요청 범위의 pfn 모두에 대해 page 구조체에서 PG_reserved 플래그를 설정한다. 추후 PG_reserved 플래그가 설정되지 않는 범위의 유효 페이지들에 대해서는 모두 버디 시스템으로 이관된다.

 

__free_memory_core()

mm/memblock.c

static unsigned long __init __free_memory_core(phys_addr_t start,
                                 phys_addr_t end)
{
        unsigned long start_pfn = PFN_UP(start);
        unsigned long end_pfn = min_t(unsigned long,
                                      PFN_DOWN(end), max_low_pfn);

        if (start_pfn > end_pfn)
                return 0;

        __free_pages_memory(start_pfn, end_pfn);

        return end_pfn - start_pfn;
}

페이지 단위 올림 정렬한 시작 주소(start) ~ 페이지 내림 정렬한 끝 주소(end)까지의 pfn에 대해 해당 영역을 버디 시스템의 free_list에 추가한다.

  • 코드 라인 4~6에서 시작 물리 주소와 끝 물리 주소로 pfn 값을 구한다.
  • 코드 라인 11에서 페이지를 해제하여 버디 시스템으로 보낸다.
  • 코드 라인 해제한 페이지 수를 리턴한다.

 

__free_pages_memory()

mm/memblock.c

static void __init __free_pages_memory(unsigned long start, unsigned long end)
{
        int order;

        while (start < end) {
                order = min(MAX_ORDER - 1UL, __ffs(start));

                while (start + (1UL << order) > end)
                        order--;

                memblock_free_pages(pfn_to_page(start), start, order);

                start += (1UL << order);
        }
}

free 요청한 페이지들에 대해 2order 단위로 잘라서 버디 시스템에 free 요청한다

  • 코드 라인 5~6에서 start~end pfn까지 순회하며 start pfn 값으로 처음 order를 결정한다.
    • 2^n 단위로 잘라낸다.
    • __ffs()는 lsb -> msb 순으로 1로 설정된 비트를 찾는다. 못 찾은 경우에는 -1을 리턴한다. 예를 들어, start가 0x10003인 경우 order는 0이 된다.
  • 코드 라인 8~9에서 start pfn에 2^order를 더한 페이지 번호가 end를 초과한다면 order를 1씩 감소시킨다.
  • 코드 라인 2^order 페이지 공간을 버디에 free시키고 start += 2^order를 한 후 다시 루프를 수행한다.

 

아래 그림은 0x10003 ~ 0x10013 pfn에 대해 버디에 free 할 때 5 조각으로 나누어 처리하는 과정을 보여준다.

__free_pages_memory-1

 


오더 페이지 해제하기

memblock_free_pages()

mm/page_alloc.c

void __init memblock_free_pages(struct page *page, unsigned long pfn,
                                                        unsigned int order)
{
        if (early_page_uninitialised(pfn))
                return;
        return __free_pages_boot_core(page, order);
}

요청한 2^order 페이지들을 해제한다.

  • 코드 라인 4~5에서 pfn에 해당하는 page 구조체가 초기화되지 않았다면 함수를 종료한다. page 구조체는 보통 부팅 초반에 싱글 스레드에 의해 초기화된다. 하지만 대용량 메모리를 가진 시스템에서는 이로 인해 부팅 속도 지연이 발생하게 된다. 따라서 이런 문제를 막기 위해  CONFIG_DEFERRED_STRUCT_PAGE_INIT 커널 설정을 사용하면 꼭 필요한 page 구조체만 초기화하고 나머지 page 구조체의 초기화는 뒤로 미룰 수가 있다.
  • 코드 라인 6에서 요청한 2^order 페이지를 버디 시스템으로 회수한다.

 

__free_pages_boot_core()

mm/page_alloc.c

void __init __free_pages_boot_core(struct page *page, unsigned int order)
{
        unsigned int nr_pages = 1 << order;
        struct page *p = page;
        unsigned int loop;

        prefetchw(p);
        for (loop = 0; loop < (nr_pages - 1); loop++, p++) {
                prefetchw(p + 1);
                __ClearPageReserved(p);
                set_page_count(p, 0);
        }
        __ClearPageReserved(p);
        set_page_count(p, 0);

        atomic_long_add(nr_pages, &page_zone(page)->managed_pages);
        set_page_refcounted(page);
        __free_pages(page, order);
}

free시킬 페이지에 대해 PG_reserved 비트를 클리어하고 페이지가 참조되지 않음으로 설정(_count = 0)한다. managed_pages에 free시킬 페이지만큼 추가하고 첫 페이지를 참조 설정(_count = 1)한 후 버디 시스템으로 반환한다.

  • 코드 라인 7에서 요청한 시작 page 구조체를 캐시에 미리 로드한다.
  • 코드 라인 8~9에서 처리할 페이지들에 대해 마지막 페이지를 제외하고 루프를 돌며 다음 page 구조체를 캐시에 미리 로드한다. page 구조체 p를 조작하기 전에 pregetchw(p + 1)을 호출하여 다음 page 구조체마저 캐시 라인에 미리 로드하면 그전 page의 구조체 전체에 대해 어토믹 연산 작업 수행 시 한 번에 성공할 확률이 높아지므로 성능 향상에 도움이 된다.
    • page 구조체 p에 해당하는 데이터를 캐시에 로드하고 조작할 p->_count는 p 주소와 12바이트가 떨어져 있어서 p를 캐시에 미리 로드해도 같은 캐시 라인에 p->_count 영역이 로드되어 있지 않을 확률이 있다. 어차피 높은 확률로 다음 페이지 구조체 데이터도 필요하므로 미리 로드를 해놓으면 p->_count에 접근 시 어토믹 연산이 한 번에 성공할 확률이 더 높아져 성능에 도움이 될 수 있다.
    • ARM64에서 L1 데이터 캐시는 최소 16바이트부터 존재한다. 참고로 커널 v4.7부터는 _count 필드가 _refcount로 이름이 변경되었다.
  • 코드 라인 10~11에서 page 구조체의 플래그에 Reserved 비트를 클리어하고 참조 카운터를 0으로 만든다.
  • 코드 라인 13~14에서 마지막 페이지를 제외하고 루프를 돌았으므로 마지막 페이지에 대해 Reserved 비트를 클리어하고 참조 카운터를 0으로 만든다.
  • 코드 라인 16~18에서 존의 managed_pages에 버디 시스템으로 회수한 페이지 수를 더하고, 대표 페이지의 참조 카운터를 1로 설정하고, 페이지 회수 API를 호출한다.
    • 페이지의 참조 카운터를 0으로 하지 않고 1로 설정한 이유는 _ _free_pages( ) 함수를 분석해보면 금방 알 수 있다. _ _free_pages( ) 함수는 커널에서 페이지 회수 시에 사용하며, 많이 사용되는 API로 이 함수를 호출할 때 참조 카운터를 1 감소시키는 코드가 내부에 있는데 이 값이 0이 되는 순간에 실제 요청한 페이지들을 버디 시스템으로 회수하게 한다.

 

다음 그림과 같이 prefetcw()를 사용하여 next page 구조체를 미리 prefetch하는 이유를 확인해보자.

 


highmem 페이지들을 버디 시스템으로 이관

free_highpages-1

 

free_highpages() – ARM32

arch/arm/mm/init.c

static void __init free_highpages(void)
{
#ifdef CONFIG_HIGHMEM
        unsigned long max_low = max_low_pfn;
        struct memblock_region *mem, *res;

        /* set highmem page free */
        for_each_memblock(memory, mem) {
                unsigned long start = memblock_region_memory_base_pfn(mem);
                unsigned long end = memblock_region_memory_end_pfn(mem);

                /* Ignore complete lowmem entries */
                if (end <= max_low)
                        continue;

                /* Truncate partial highmem entries */
                if (start < max_low)
                        start = max_low;

                /* Find and exclude any reserved regions */
                for_each_memblock(reserved, res) {
                        unsigned long res_start, res_end;

                        res_start = memblock_region_reserved_base_pfn(res);
                        res_end = memblock_region_reserved_end_pfn(res);

                        if (res_end < start)
                                continue;
                        if (res_start < start)
                                res_start = start;
                        if (res_start > end)
                                res_start = end;
                        if (res_end > end)
                                res_end = end;
                        if (res_start != start)
                                free_area_high(start, res_start);
                        start = res_end;
                        if (start == end)
                                break;
                }

                /* And now free anything which remains */
                if (start < end)
                        free_area_high(start, end);
        }
#endif
}

memblock에서 highmem에 해당하는 free 영역을 버디 시스템에 이관한다.

  • 코드 라인 8~10에서 memory memblock 수 만큼 순회하며 memblock 영역의 시작과 끝 pfn을 구한다.
    • 물리 시작 주소에 PAGE_SIZE 단위로 round up한 후 pfn으로 변환
    • 물리 끝 주소에 round down된 값을 pfn으로 변환
  • 코드 라인 13~14에서 memblock 엔트리가 lowmem인 경우는 skip 한다.
  • 코드 라인 16~17에서 노매핑 설정된 블럭은 skip 한다.
  • 코드 라인 20~21에서 memblock 엔트리가 lowmem/highmem 경계에 걸친 경우 lowmem 영역을 skip 한다.
  • 코드 라인 24~47에서 reserved 영역을 제외한 free 공간을 버디 시스템으로 이관한다.

 

free_area_high()

arch/arm/mm/init.c

#ifdef CONFIG_HIGHMEM
static inline void free_area_high(unsigned long pfn, unsigned long end)
{
        for (; pfn < end; pfn++)
                free_highmem_page(pfn_to_page(pfn));
}
#endif

pfn ~ end 까지 각각의 pfn에 해당하는 highmem 페이지를 버디 시스템에서 free 처리 한다.

 

free_highmem_page()

mm/page_alloc.c

#ifdef  CONFIG_HIGHMEM
/*
 * Free a highmem page into the buddy system, adjusting totalhigh_pages
 * and totalram_pages.
 */
void free_highmem_page(struct page *page) 
{
        __free_reserved_page(page);
        totalram_pages++;
        page_zone(page)->managed_pages++;
        totalhigh_pages++;
}
#endif

해당 highmem 페이지에서 reserved 플래그를 clear하고 _count=1로 대입한 후 버디 시스템에서 free 처리 한다. 관련 stat들 또한 증가시킨다.

 

__free_reserved_page()

include/linux/mm.h

/* Free the reserved page into the buddy system, so it gets managed. */
static inline void __free_reserved_page(struct page *page)
{
        ClearPageReserved(page);
        init_page_count(page);
        __free_page(page);
}

해당 highmem 페이지에서 reserved 플래그를 clear하고 _count=1로 대입한 후 버디 시스템에서 free 처리 한다.

 

get_num_physpages()

include/linux/mm.h

static inline unsigned long get_num_physpages(void)
{
        int nid;
        unsigned long phys_pages = 0;

        for_each_online_node(nid) 
                phys_pages += node_present_pages(nid);  

        return phys_pages;
}

전체 노드의 present(hole 제외) 페이지 수를 알아온다.

 

include/linux/mmzone.h

#define node_present_pages(nid) (NODE_DATA(nid)->node_present_pages)

 

참고

댓글 남기기