mem_init()

lowmem 및 highmem에 대한 모든 free memblock 영역에 대해 Buddy memory allocator의 빈 페이지로 이관 등록한다. memblock을 더 이상 사용하지 않는 경우 reserved & memory memblock 관리배열까지 Buddy로 free 시킨다.

mem_init-1

 

free_highpages-1

 

mem_init()

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();
        free_all_bootmem();

#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);
  • set_max_mapnr(pfn_to_page(max_pfn) – mem_map);
    • UMA 시스템에서만 전역 max_mapnr에 mem_map[] 배열에 대한 인덱스 번호를 저장한다.
  • free_unused_memmap();
    • Sparse 메모리 모델 또는 Discontig 메모리 모델에서 메모리 사이의 사용되지 않는 공간이 상당히 클 수 있다. 따라서 이에 대해 메모리 낭비가 발생하지 않도록 미사용 공간에 대한 mem_map[]을 페이지 단위로 reserve memblock에서 free 시킨다.
  • free_all_bootmem();
    • free memblock 영역에 대해 모두 Buddy memory allocator의 빈 페이지로 이관 등록한다.
    • memblock을 더 이상 사용하지 않는 경우 reserved & memory memblock 관리배열까지 Buddy로 free 시킨다.
  • free_highpage();
    • 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(UL(CONFIG_VECTORS_BASE), UL(CONFIG_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
  • ARMv7 아키텍처를 사용하는 rpi2
 Virtual kernel memory layout:
     vector  : 0xffff0000 - 0xffff1000   (   4 kB)
     fixmap  : 0xffc00000 - 0xfff00000   (3072 kB)
     vmalloc : 0xbb800000 - 0xff000000   (1080 MB)
     lowmem  : 0x80000000 - 0xbb000000   ( 944 MB)
     modules : 0x7f000000 - 0x80000000   (  16 MB)
       .text : 0x80008000 - 0x8081b128   (8269 kB)
       .init : 0x8081c000 - 0x809e4000   (1824 kB)
       .data : 0x809e4000 - 0x80a5b6a4   ( 478 kB)
       .bss : 0x80a5b6a4 - 0x81336908   (9069 kB)
  • 인텔 i5 cpu를 사용하는 virtual-box에서 ubuntu 예)
virtual kernel memory layout:
    fixmap  : 0xfff15000 - 0xfffff000   ( 936 kB)
    pkmap   : 0xffc00000 - 0xffe00000   (2048 kB)
    vmalloc : 0xf83fe000 - 0xffbfe000   ( 120 MB)
    lowmem  : 0xc0000000 - 0xf7bfe000   ( 891 MB)
      .init : 0xc1a9d000 - 0xc1b7c000   ( 892 kB)
      .data : 0xc16f5d02 - 0xc1a9bcc0   (3735 kB)
      .text : 0xc1000000 - 0xc16f5d02   (7127 kB)

 

        /*
         * 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

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

 

다음 그림은 early memory allocator 인 memblock 에서 buddy memory allocator로 free 메모리 관리가 전환되는 모습을 보여준다.

mem_init-2

 

free_unused_memmap()

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이 없어 해당 사항 없다.

 

  • for_each_memblock(memory, reg) {
    • memory memblock 엔트리 수 만큼 루프
  • start = memblock_region_memory_base_pfn(reg);
    • PFN_UP(reg->base);
      • 물리 시작 주소에 PAGE_SIZE 단위로 round up한 후 pfn으로 변환
  • start = min(start, ALIGN(prev_end, PAGES_PER_SECTION));
    • Sparse memory model을 사용하는 경우 물리 시작 주소가 이전 기억해둔 memblock의 끝 주소를 PAGES_PER_SECTION 단위로 round up한 수를 초과하지 않게 한다.
    • PAGES_PER_SECTION
      • Sparse memory model의 섹션당 페이지 수
        • ARM 페이지 테이블 매핑에서 사용하는 섹션과 혼동하면 안된다.
  • start = round_down(start, MAX_ORDER_NR_PAGES);
    • Sparse memory model이 아닌 경우 물리 시작 주소를 MAX_ORDER_NR_PAGES 단위로 round down 한다.
    • MAX_ORDER_NR_PAGES
      • (1 << (MAX_ORDER – 1))
        • 1024
  • if (prev_end && prev_end < start) free_memmap(prev_end, start)
    • hole이 있는 경우 hole 만큼의 page를 관리하는 mem_map[]을 free 시킨다.
  • prev_end = ALIGN(memblock_region_memory_end_pfn(reg), MAX_ORDER_NR_PAGES);
    • memblock의 끝 주소를 round down하여 pfn으로 변환한 수를 MAX_ORDER_NR_PAGES 단위로 round up한 pfn 값
  • if (!IS_ALIGNED(prev_end, PAGES_PER_SECTION)) free_memmap(prev_end, ALIGN(prev_end, PAGES_PER_SECTION));
    • 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_all_bootmem()

이 함수는 CONFIG_NO_BOOTMEM 커널 옵션을 사용하는 소스를 분석한다. 즉 memblock을 바로 사용하는 mm/nobootmem.c 화일을 사용한다.

  • 최근에는 사용하지 않게된 bootmem을 사용하는 소스는 mm/bootmem.c를 사용하며 이 case는 추가로 분석하지 않는다. (deprecated)

mm/nobootmem.c

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

        reset_all_zones_managed_pages();

        /*
         * 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
         */
        pages = free_low_memory_core_early();
        totalram_pages += pages;

        return pages;
}

모든 free lowmem 영역을 모두 버디 시스템의 free_list에 이관 등록한다. hotplug memory를 사용하지 않는 대부분의 시스템은 버디 시스템이 활성화되면 memblock을 더 이상 사용하지 않게되는데 이 때 reserve & memory memblock의 관리 배열을 더 이상 사용하지 않으므로 이에 대한 영역도 버디 시스템에 이관한다.

  • reset_all_zones_managed_pages();
    • 모든 online 노드의 각 zone->managed_pages를 0으로 초기화한다.
    • zone->managed_pages
      • zone에서 사용가능한 free 페이지 수
  • pages = free_low_memory_core_early();
    • 모든 free lowmem 영역들을 버디 시스템의 free_list에 이관 등록한다.
    • memblock을 더 이상 사용하지 않는 경우 reserve & memory memblock 관리 배열도 버디 시스템의 free_list에 이관 등록한다.
  • totalram_pages += pages;
    • free된 페이지들을 전역 totalram_pages에 추가한다.

 

reset_all_zones_managed_pages()

mm/nobootmem.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/nobootmem.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++)
                z->managed_pages = 0;
}

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

 

free_low_memory_core_early()

mm/nobootmem.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_free_mem_range(i, NUMA_NO_NODE, &start, &end, NULL)
                count += __free_memory_core(start, end);

#ifdef CONFIG_ARCH_DISCARD_MEMBLOCK
        {
                phys_addr_t size;

                /* Free memblock.reserved array if it was allocated */
                size = get_allocated_memblock_reserved_regions_info(&start);
                if (size)
                        count += __free_memory_core(start, start + size);

                /* Free memblock.memory array if it was allocated */
                size = get_allocated_memblock_memory_regions_info(&start);
                if (size)
                        count += __free_memory_core(start, start + size);
        }
#endif

        return count;
}

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

  • memblock_clear_hotplug(0, -1);
    • 전체 memory memblock 영역에 대해 MEMBLOCK_HOTPLUG 비트를 clear 한다.
  • for_each_free_mem_range(i, NUMA_NO_NODE, &start, &end, NULL) count += __free_memory_core(start, end);
    • 모든 free lowmem 영역을 모두 버디 시스템의 free_list에 이관 등록한다.
  • size = get_allocated_memblock_reserved_regions_info(&start);
    • reserved memblock의 regions[] 배열의 사이즈
  • if (size) count += __free_memory_core(start, start + size);
    • reserved memblock의 regions[] 배열에 대한 영역을 모두 버디 시스템의 free_list에 이관 등록한다.
  • size = get_allocated_memblock_memory_regions_info(&start);
    • memory memblock의 regions[] 배열의 사이즈
  • if (size) count += __free_memory_core(start, start + size);
    • memory memblock의 regions[] 배열에 대한 영역을 모두 버디 시스템의 free_list에 이관 등록한다.

 

__free_memory_core()

mm/nobootmem.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;
}

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

 

__free_pages_memory()

mm/nobootmem.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--;

                __free_pages_bootmem(pfn_to_page(start), order);

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

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

  • while (start < end) {
    • start ~ end pfn 까지 루프를 돈다.
  •  order = min(MAX_ORDER – 1UL, __ffs(start));
    • 2^n 단위로 잘라낸다.
      • __ffs()
        • lsb -> msb 순으로 1로 설정된 비트를 찾는다. (based 0) 못찾은 경우는 -1을 반환한다.
    • 예) start=0x10003
      • order=0
  • while (start + (1UL << order) > end) order–;
    • start + 2^order를 더한 페이지 번호가 end를 초과하는 경우 order를 1씩 감소시킨다.
  • __free_pages_bootmem(pfn_to_page(start), order);
    • 2^order 페이지 공간을 버디에서 free 시킨다.
      • 버디 시스템의 free_list[order] 슬롯에 free 영역을 추가한다.
  • start += (1UL << order);
    • start += 2^order를 한 후 다시 루프를 수행한다.

 

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

__free_pages_memory-1

 

__free_pages_bootmem()

mm/page_alloc.c

void __init __free_pages_bootmem(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);

        page_zone(page)->managed_pages += nr_pages;
        set_page_refcounted(page);
        __free_pages(page, order);
}

free 시킬 페이지에 대해 Reserve 비트를 clear하고 페이지가 참조되지 않음으로 설정(_count <- 0)한다. managed_pages에 free시킬 페이지 만큼 추가하고 첫 페이지를 참조 설정(_count <- 1)한 후 버디 시스템에서 free하게 한다.

  • 조작할 page 구조체 p를 조작하기 전에 pregetchw(p+1)를 호출하여 캐시 라인에 미리 로드를 하게되면 atomic operation 들이 한 번에 성공할 확률이 높아져서 성능 향상에 도움이 된다.
    • page 구조체 p에 해당하는 데이터를 캐시에 로드할 때 p->_count는 p 주소와 12바이트가 떨어져 있어서 p를 캐시에 prefetch하여도 p->_count 영역이 캐시에 prefetch 안될 수도 있다. 따라서 어짜피 다음 페이지 구조체 데이터도 로드할 계획이므로 미리 로드를 해 놓으면 p->_count의 조작 시 atomic operation이 한 번에 성공할 확률이 높아져 성능에 도움이 될 수 있다.
    • L1 d-cache가 최소 16 bytes인 시스템이 있다.
    • rpi2: L1 d-cache line=32 bytes, L2 d-cache line=64 bytes

 

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

 

get_allocated_memblock_reserved_regions_info()

mm/memblock.c

#ifdef CONFIG_ARCH_DISCARD_MEMBLOCK
phys_addr_t __init_memblock get_allocated_memblock_reserved_regions_info(
                                        phys_addr_t *addr)
{
        if (memblock.reserved.regions == memblock_reserved_init_regions)
                return 0;

        *addr = __pa(memblock.reserved.regions);

        return PAGE_ALIGN(sizeof(struct memblock_region) *
                          memblock.reserved.max);
}
#endif

CONFIG_ARCH_DISCARD_MEMBLOCK 커널 옵션이 설정된 경우 memblock이 buddy 시스템으로 전환된 후에는 memblock을 사용하지 않게 된다. 따라서 이 memblock 관리 영역을 free 하기 위한 목적으로 주소와 사이즈를 반환한다.

 

free_highpages()

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 시킨다.

  • unsigned long max_low = max_low_pfn;
  • max lowmem pfn
  • for_each_memblock(memory, mem) {
    • memory memblock 수 만큼 루프
  • unsigned long start = memblock_region_memory_base_pfn(mem);
    • PFN_UP(reg->base);
      • 물리 시작 주소에 PAGE_SIZE 단위로 round up한 후 pfn으로 변환
  • unsigned long end = memblock_region_memory_end_pfn(mem);
    • PFN_DOWN(reg->base + reg->size);
      • 물리 끝 주소에 round down된 값을 pfn으로 변환
  • if (end <= max_low) continue;
    • memblock 엔트리가 lowmem인 경우는 무시
  • if (start < max_low) start = max_low;
    • memblock 엔트리가 lowmem/highmem 경계에 걸친 경우 lowmem 영역을 무시한다.

 

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)

 

참고

답글 남기기

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