Mem_map (page API)

API 계속 추가中 !!!

 

Compound page

  • 리눅스는 buddy 메모리 할당자를 통해 페이지 할당을 하는 경우 최대 2^(MAX_ORDER-1) 만큼의 페이지를 할당할 수 있는데 일반적인 high order 페이지 할당과 compound page 할당은 약간의 차이를 가진다.
  • 4단계의 페이지 테이블 매핑을 사용하는 리눅스에서 각 페이지는 PTE 레벨에서 매핑을 담당하여 사용한다.
  • 성능을 향상시키기 위해 리눅스는 대량의 페이지(high order 페이지)를 할당 받는 경우 PMD 레벨에서 huge TLB를 사용하여 더 빠른 access를 사용할 수 있게 매핑을 하고 그러한 페이지가 사용되었다는 표식을 page->flags의 필드에서 PG_compound(PG_head) 비트를 사용하여 표시하였다.
    • ARM은 PMD 레벨에 섹션 페이지를 연결하여 사용한다.
  • CONFIG_PAGEFLAGS_EXTENDED 커널 옵션을 사용하는 경우 PG_compound 비트를 사용하는 대신 PG_head 및 PG_tail 비트를 사용하였다.
    • PG_head: compound page의 선두 페이지
    • PG_tail: compound page 이면서 선두 페이지가 아닌 페이지
  • 2015년 12월 kernel v.4.6-rc1 에서 CONFIG_PAGEFLAGS_EXTENDED 옵션과 PG_compound, PG_tail 이 없어지고 PG_head 만 남겨지게 되었다.

 

compound_order()

include/linux/mm.h

static inline int compound_order(struct page *page)
{
        if (!PageHead(page))
                return 0;
        return page[1].compound_order;
}
  • 요청 페이지가 compound page인 경우 페이지의 구성이 기본 PAGE_SIZE 단위를 사용한 것이 아니라 huge TLB를 사용하게 된 경우이므로 다음 페이지의 compund_order 값을 리턴한다.
    • 예) PAGE_SIZE=4K, compound_order=9
      • size=2M

 

is_highmem_idx()

include/linux/mmzone.h

static inline int is_highmem_idx(enum zone_type idx)
{
#ifdef CONFIG_HIGHMEM
        return (idx == ZONE_HIGHMEM ||
                (idx == ZONE_MOVABLE && zone_movable_is_highmem())); 
#else
        return 0;
#endif
}

 

zone_movable_is_highmem()

include/linux/mmzone.h

static inline int zone_movable_is_highmem(void)
{
#if defined(CONFIG_HIGHMEM) && defined(CONFIG_HAVE_MEMBLOCK_NODE_MAP)
        return movable_zone == ZONE_HIGHMEM;
#elif defined(CONFIG_HIGHMEM)
        return (ZONE_MOVABLE - 1) == ZONE_HIGHMEM;
#else
        return 0;
#endif
}

 

zone_idx()

include/linux/mmzone.h

/*
 * zone_idx() returns 0 for the ZONE_DMA zone, 1 for the ZONE_NORMAL zone, etc.
 */
#define zone_idx(zone)          ((zone) - (zone)->zone_pgdat->node_zones)
  • zone 인덱스 번호를 리턴한다.
    • 예) ZONE_DMA, ZONE_NORMAL을 사용하는 경우 0과 1이 리턴된다.
    • 예) ZONE_NORMAL만 사용하는 경우 0이 리턴된다.

 

for_each_migratetype_order()

include/linux/mmzone.h

#define for_each_migratetype_order(order, type) \
        for (order = 0; order < MAX_ORDER; order++) \
                for (type = 0; type < MIGRATE_TYPES; type++)
  • buddy 메모리 할당자가 사용하는 MAX_ORDER(11)  수 만큼 루프를 돈다.
  • 메모리 hotplug에 대한 이주 플래그 관리를 담당하는 MIGRATE_TYPES 만큼 루프를 돈다.

 

set_page_links()

include/linux/mm.h

static inline void set_page_links(struct page *page, enum zone_type zone,
        unsigned long node, unsigned long pfn) 
{
        set_page_zone(page, zone);
        set_page_node(page, node);
#ifdef SECTION_IN_PAGE_FLAGS
        set_page_section(page, pfn_to_section_nr(pfn));
#endif
}
  • page->flags에 zone, node 및 section 정보를 설정한다.
 * No sparsemem or sparsemem vmemmap: |       NODE     | ZONE |             ... | FLAGS |
 *      " plus space for last_cpupid: |       NODE     | ZONE | LAST_CPUPID ... | FLAGS |
 * classic sparse with space for node:| SECTION | NODE | ZONE |             ... | FLAGS |
 *      " plus space for last_cpupid: | SECTION | NODE | ZONE | LAST_CPUPID ... | FLAGS |
 * classic sparse no space for node:  | SECTION |     ZONE    | ... | FLAGS |

 

set_page_zone()

include/linux/mm.h

static inline void set_page_zone(struct page *page, enum zone_type zone)
{
        page->flags &= ~(ZONES_MASK << ZONES_PGSHIFT);
        page->flags |= (zone & ZONES_MASK) << ZONES_PGSHIFT;
}
  • page->flags에 zone 정보를 설정한다.

 

set_page_node()

include/linux/mm.h

static inline void set_page_node(struct page *page, unsigned long node)
{
        page->flags &= ~(NODES_MASK << NODES_PGSHIFT);
        page->flags |= (node & NODES_MASK) << NODES_PGSHIFT;
}
  • page->flags에 노드 정보를 설정한다.

 

set_page_section()

include/linux/mm.h

#ifdef SECTION_IN_PAGE_FLAGS
static inline void set_page_section(struct page *page, unsigned long section)
{
        page->flags &= ~(SECTIONS_MASK << SECTIONS_PGSHIFT);
        page->flags |= (section & SECTIONS_MASK) << SECTIONS_PGSHIFT;
}
#endif
  • page->flags에 섹션 정보를 설정한다.

 

PageReserved(), SetPageReserved(), ClearPageReserved(), __ClearPageReserved()

include/linux/page-flags.h

PAGEFLAG(Reserved, reserved) __CLEARPAGEFLAG(Reserved, reserved)
  • PageReserved(), SetPageReserved(), ClearPageReserved() 및 __ClearPageReserved() static inline 함수가 만들어진다.

 

#define PAGEFLAG(uname, lname) TESTPAGEFLAG(uname, lname)               \
        SETPAGEFLAG(uname, lname) CLEARPAGEFLAG(uname, lname)
  • 아래 매크로를 사용하여 PageXXX(), SetPageXXX() 및 ClearPageXXX() static inline 함수가 만들어진다.

 

/*
 * Macros to create function definitions for page flags
 */
#define TESTPAGEFLAG(uname, lname)                                      \
static inline int Page##uname(const struct page *page)                  \
                        { return test_bit(PG_##lname, &page->flags); }

#define SETPAGEFLAG(uname, lname)                                       \
static inline void SetPage##uname(struct page *page)                    \
                        { set_bit(PG_##lname, &page->flags); }

#define CLEARPAGEFLAG(uname, lname)                                     \
static inline void ClearPage##uname(struct page *page)                  \
                        { clear_bit(PG_##lname, &page->flags); }

 

#define __CLEARPAGEFLAG(uname, lname)                                   \
static inline void __ClearPage##uname(struct page *page)                \
                        { __clear_bit(PG_##lname, &page->flags); }
  • test_bit()
    • &page->flags의 PG_xxxxx 번호 비트가 set되었는지 여부를 알아온다.
  • set_bit()
    • &page->flags의 PG_xxxxx 번호 비트를 atomic하게 set 한다.
  • clear_bit()
    • &page->flags의 PG_xxxxx 번호 비트를 atomic하게 clear 한다.
  • __clear_bit()
    • &page->flags의 PG_xxxxx 번호 비트를 clear 한다. (non-atomic)

 

set_pageblock_flags_group()

linux/pageblock-flags.h

#define set_pageblock_flags_group(page, flags, start_bitidx, end_bitidx) \
        set_pfnblock_flags_mask(page, flags, page_to_pfn(page),         \
                        end_bitidx,                                     \
                        (1 << (end_bitidx - start_bitidx + 1)) - 1)

 

set_pfnblock_flags_mask()

mm/page_alloc.c

/**
 * set_pfnblock_flags_mask - Set the requested group of flags for a pageblock_nr_pages block of pages
 * @page: The page within the block of interest
 * @flags: The flags to set
 * @pfn: The target page frame number
 * @end_bitidx: The last bit of interest
 * @mask: mask of bits that the caller is interested in
 */
void set_pfnblock_flags_mask(struct page *page, unsigned long flags,
                                        unsigned long pfn,
                                        unsigned long end_bitidx,
                                        unsigned long mask)
{
        struct zone *zone;      
        unsigned long *bitmap;
        unsigned long bitidx, word_bitidx;
        unsigned long old_word, word;

        BUILD_BUG_ON(NR_PAGEBLOCK_BITS != 4);

        zone = page_zone(page);
        bitmap = get_pageblock_bitmap(zone, pfn);
        bitidx = pfn_to_bitidx(zone, pfn);
        word_bitidx = bitidx / BITS_PER_LONG;
        bitidx &= (BITS_PER_LONG-1);

        VM_BUG_ON_PAGE(!zone_spans_pfn(zone, pfn), page);

        bitidx += end_bitidx;
        mask <<= (BITS_PER_LONG - bitidx - 1);
        flags <<= (BITS_PER_LONG - bitidx - 1); 

        word = ACCESS_ONCE(bitmap[word_bitidx]);
        for (;;) {
                old_word = cmpxchg(&bitmap[word_bitidx], word, (word & ~mask) | flags);
                if (word == old_word)
                        break;
                word = old_word;
        }            
}

 

get_pfnblock_flags_mask()

mm/page_alloc.c

/**
 * get_pfnblock_flags_mask - Return the requested group of flags for the pageblock_nr_pages block of pages              
 * @page: The page within the block of interest
 * @pfn: The target page frame number
 * @end_bitidx: The last bit of interest to retrieve
 * @mask: mask of bits that the caller is interested in
 *              
 * Return: pageblock_bits flags
 */
unsigned long get_pfnblock_flags_mask(struct page *page, unsigned long pfn,
                                        unsigned long end_bitidx,
                                        unsigned long mask)
{
        struct zone *zone;
        unsigned long *bitmap;
        unsigned long bitidx, word_bitidx;
        unsigned long word;

        zone = page_zone(page);
        bitmap = get_pageblock_bitmap(zone, pfn);
        bitidx = pfn_to_bitidx(zone, pfn);
        word_bitidx = bitidx / BITS_PER_LONG;
        bitidx &= (BITS_PER_LONG-1);

        word = bitmap[word_bitidx];
        bitidx += end_bitidx;
        return (word >> (BITS_PER_LONG - bitidx - 1)) & mask;
}

 

get_pageblock_bitmap()

mm/page_alloc.c

/* Return a pointer to the bitmap storing bits affecting a block of pages */
static inline unsigned long *get_pageblock_bitmap(struct zone *zone,
                                                        unsigned long pfn)
{
#ifdef CONFIG_SPARSEMEM
        return __pfn_to_section(pfn)->pageblock_flags;
#else
        return zone->pageblock_flags;
#endif /* CONFIG_SPARSEMEM */
}
  • zone이 관리하는 pageblock가 저장된 pfn을 알아온다.

 

__pfn_to_section()

include/linux/mmzone.h

static inline struct mem_section *__pfn_to_section(unsigned long pfn) 
{
        return __nr_to_section(pfn_to_section_nr(pfn));
}

pfn 값에 대응하는 mem_section 구조체 정보를 리턴한다.

  •  pfn_to_section_nr()
    • pfn 값으로 섹션 번호를 알아온다.
  • __nr_to_section()
    • 섹션 번호로 mem_section 구조체 정보를 리턴한다.

 

pfn_to_section_nr()

include/linux/mmzone.h

#define pfn_to_section_nr(pfn) ((pfn) >> PFN_SECTION_SHIFT)
  • pfn의 섹션(Sparse) 인덱스를 리턴한다.
    • 예) Realview-PBX
      • 섹션 사이즈가 256M 단위(PFN_SECTION_SHIFT=16)이므로 섹션 번호는 0~15까지의 결과

 

__nr_to_section()

include/linux/mmzone.h

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 구조체 정보를 리턴한다.

 

SECTION_NR_TO_ROOT()

include/linux/mmzone.h

#define SECTION_NR_TO_ROOT(sec) ((sec) / SECTIONS_PER_ROOT)
  • 섹션 번호로 ROOT 번호를 리턴한다.

 

#ifdef CONFIG_SPARSEMEM_EXTREME
#define SECTIONS_PER_ROOT       (PAGE_SIZE / sizeof (struct mem_section))
#else
#define SECTIONS_PER_ROOT       1
#endif
  • ROOT 하나 당 섹션 수
    • PAGE_SIZE(4K)에 mem_section 구조체가 들어갈 수 있는 수

 

pfn_to_bitidx()

mm/page_alloc.c

static inline int pfn_to_bitidx(struct zone *zone, unsigned long pfn)
{
#ifdef CONFIG_SPARSEMEM
        pfn &= (PAGES_PER_SECTION-1);
        return (pfn >> pageblock_order) * NR_PAGEBLOCK_BITS;
#else
        pfn = pfn - round_down(zone->zone_start_pfn, pageblock_nr_pages);
        return (pfn >> pageblock_order) * NR_PAGEBLOCK_BITS;
#endif /* CONFIG_SPARSEMEM */
}

pfn에 대한 pageblock에서 비트 인덱스를 반환한다.

 

present_section_nr()

include/linux/mmzone.h

static inline int present_section_nr(unsigned long nr)
{
        return present_section(__nr_to_section(nr));
}

섹션 번호에 해당하는 mem_section이 준비되어 있는지 확인한다. 준비되어 있지 않은 경우 해당 섹션은 hole을 의미한다.

  • __nr_to_section()
    • 섹션 번호로 mem_section 구조체 정보를 알아온다.
  • present_section()
    • mem_section 구조체 정보에 섹션이 존재하는지 확인한다.

 

present_section()

include/linux/mmzone.h

static inline int present_section(struct mem_section *section)
{
        return (section && (section->section_mem_map & SECTION_MARKED_PRESENT));
}

mem_section 구조체 정보에 섹션이 존재하는지 확인한다.

  • SECTION_MARKED_PRESENT 식별 비트가 설정되어 있는지 확인한다.

 

__section_mem_map_addr()

include/linux/mmzone.h

static inline struct page *__section_mem_map_addr(struct mem_section *section)
{
        unsigned long map = section->section_mem_map;
        map &= SECTION_MAP_MASK;
        return (struct page *)map;
}

헤당 Sparse memory 섹션에 대한 mem_map 주소를 반환한다.

include/linux/mmzone.h

/*
 * We use the lower bits of the mem_map pointer to store
 * a little bit of information.  There should be at least
 * 3 bits here due to 32-bit alignment.
 */
#define SECTION_MARKED_PRESENT  (1UL<<0)
#define SECTION_HAS_MEM_MAP     (1UL<<1)
#define SECTION_MAP_LAST_BIT    (1UL<<2)
#define SECTION_MAP_MASK        (~(SECTION_MAP_LAST_BIT-1))
#define SECTION_NID_SHIFT       2

 

page_is_buddy()

mm/page_alloc.c

/*
 * This function checks whether a page is free && is the buddy
 * we can do coalesce a page and its buddy if
 * (a) the buddy is not in a hole &&
 * (b) the buddy is in the buddy system &&
 * (c) a page and its buddy have the same order &&
 * (d) a page and its buddy are in the same zone.
 *
 * For recording whether a page is in the buddy system, we set ->_mapcount
 * PAGE_BUDDY_MAPCOUNT_VALUE.
 * Setting, clearing, and testing _mapcount PAGE_BUDDY_MAPCOUNT_VALUE is
 * serialized by zone->lock.
 *
 * For recording page's order, we use page_private(page).
 */
static inline int page_is_buddy(struct page *page, struct page *buddy,
                                                        unsigned int order)
{
        if (!pfn_valid_within(page_to_pfn(buddy)))
                return 0;

        if (page_is_guard(buddy) && page_order(buddy) == order) {
                if (page_zone_id(page) != page_zone_id(buddy))
                        return 0;

                VM_BUG_ON_PAGE(page_count(buddy) != 0, buddy);

                return 1;
        }

        if (PageBuddy(buddy) && page_order(buddy) == order) {
                /*
                 * zone check is done late to avoid uselessly
                 * calculating zone/node ids for pages that could
                 * never merge.
                 */
                if (page_zone_id(page) != page_zone_id(buddy))
                        return 0;

                VM_BUG_ON_PAGE(page_count(buddy) != 0, buddy);

                return 1;
        }
        return 0;
}

페이지가 인수 order로 설정된 buddy인지 여부를 반환한다.

 

rmv_page_order()

mm/page_alloc.c

static inline void rmv_page_order(struct page *page)
{
        __ClearPageBuddy(page);
        set_page_private(page, 0);
}

include/linux/mm.h

#define set_page_private(page, v)       ((page)->private = (v))

페이지의 _mapcount를 -1로 설정하고 order bit를 나타내는 페이지의 private에 0을 대입한다.

 

__ClearPageBuddy()

include/linux/mm.h

static inline void __ClearPageBuddy(struct page *page)
{
        VM_BUG_ON_PAGE(!PageBuddy(page), page);
        atomic_set(&page->_mapcount, -1);
}

페이지의 _mapcount를 -1로 설정한다.

 

page_order()

mm/internal.h

/*
 * This function returns the order of a free page in the buddy system. In
 * general, page_zone(page)->lock must be held by the caller to prevent the
 * page from being allocated in parallel and returning garbage as the order.
 * If a caller does not hold page_zone(page)->lock, it must guarantee that the
 * page cannot be allocated or merged in parallel. Alternatively, it must
 * handle invalid values gracefully, and use page_order_unsafe() below.
 */
static inline unsigned long page_order(struct page *page)
{
        /* PageBuddy() must be checked by the caller */
        return page_private(page);
}

include/linux/mm.h

#define page_private(page)              ((page)->private)

페이지의 order bit인 private를 반환한다.

 

page_zone_id()

include/linux/mm.h

/*
 * The identification function is mainly used by the buddy allocator for
 * determining if two pages could be buddies. We are not really identifying
 * the zone since we could be using the section number id if we do not have
 * node id available in page flags.
 * We only guarantee that it will return the same value for two combinable
 * pages in a zone.
 */
static inline int page_zone_id(struct page *page)
{
        return (page->flags >> ZONEID_PGSHIFT) & ZONEID_MASK;
}

페이지에서 zone id를 추출하여 반환한다.

 

PageBuddy()

include/linux/mm.h

/*
 * PageBuddy() indicate that the page is free and in the buddy system
 * (see mm/page_alloc.c).
 *
 * PAGE_BUDDY_MAPCOUNT_VALUE must be <= -2 but better not too close to
 * -2 so that an underflow of the page_mapcount() won't be mistaken
 * for a genuine PAGE_BUDDY_MAPCOUNT_VALUE. -128 can be created very
 * efficiently by most CPU architectures.
 */             
#define PAGE_BUDDY_MAPCOUNT_VALUE (-128)
        
static inline int PageBuddy(struct page *page)
{
        return atomic_read(&page->_mapcount) == PAGE_BUDDY_MAPCOUNT_VALUE;
}

페이지가 버디 시스템에서 free되어 관리되는지 여부를 반환한다.

 

page_count()

include/linux/mm.h

static inline int page_count(struct page *page)
{
        return atomic_read(&compound_head(page)->_count);
}

요청 페이지의 _count 값을 알아온다. 만일 compound page인 경우 선두 페이지에서 _count 값을 알아온다.

 

page vs pfn 변환

include/asm-generic/memory_model.h

#define page_to_pfn __page_to_pfn

다음 4가지 커널 옵션 설정에 따라 함수가 선택된다.

  • CONFIG_FLATMEM
#define __pfn_to_page(pfn)      (mem_map + ((pfn) - ARCH_PFN_OFFSET))
#define __page_to_pfn(page)     ((unsigned long)((page) - mem_map) + \
                                 ARCH_PFN_OFFSET)
  • CONFIG_DISCONTIGMEM
#define __pfn_to_page(pfn)                      \
({      unsigned long __pfn = (pfn);            \       
        unsigned long __nid = arch_pfn_to_nid(__pfn);  \
        NODE_DATA(__nid)->node_mem_map + arch_local_page_offset(__pfn, __nid);\
})              

#define __page_to_pfn(pg)                                               \
({      const struct page *__pg = (pg);                                 \
        struct pglist_data *__pgdat = NODE_DATA(page_to_nid(__pg));     \
        (unsigned long)(__pg - __pgdat->node_mem_map) +                 \
         __pgdat->node_start_pfn;                                       \
})
  • CONFIG_SPARSEMEM
/*
 * Note: section's mem_map is encoded to reflect its start_pfn.
 * section[i].section_mem_map == mem_map's address - start_pfn;
 */
#define __page_to_pfn(pg)                                       \
({      const struct page *__pg = (pg);                         \
        int __sec = page_to_section(__pg);                      \
        (unsigned long)(__pg - __section_mem_map_addr(__nr_to_section(__sec))); \
})      

#define __pfn_to_page(pfn)                              \
({      unsigned long __pfn = (pfn);                    \
        struct mem_section *__sec = __pfn_to_section(__pfn);    \
        __section_mem_map_addr(__sec) + __pfn;          \
})
  • CONFIG_SPARSEMEM_VMEMMAP
/* memmap is virtually contiguous.  */
#define __pfn_to_page(pfn)      (vmemmap + (pfn))
#define __page_to_pfn(page)     (unsigned long)((page) - vmemmap)

 

기타

SECTION_BLOCKFLAGS_BITS

include/linux/mmzone.h

#define SECTION_BLOCKFLAGS_BITS \
        ((1UL << (PFN_SECTION_SHIFT - pageblock_order)) * NR_PAGEBLOCK_BITS)

섹션 당 pageblock 비트 수

  • NR_PAGEBLOCK_BITS
    • pageblock에 대해 필요한 비트 수=4
  • PFN_SECTION_SHIFT
    • 섹션 길이 표현에 필요한 비트 수 – 페이지 길이 표현에 필요한 비트 수를 뺀 값
      • arm64: 섹션 길이=30(1GB 표현) bits – 12(4KB 표현) bits = 18
  • 예) arm64에서 섹션 크기=1G, pageblock_order=10인 경우
    • 2^(18-10) * 4 = 1024개

 

페이지 Flags

include/linux/page-flags.h

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

/*
 * Don't use the *_dontuse flags.  Use the macros.  Otherwise you'll break
 * locked- and dirty-page accounting.
 *
 * The page flags field is split into two parts, the main flags area
 * which extends from the low bits upwards, and the fields area which
 * extends from the high bits downwards.
 *
 *  | FIELD | ... | FLAGS |
 *  N-1           ^       0
 *               (NR_PAGEFLAGS)
 *
 * The fields area is reserved for fields mapping zone, node (for NUMA) and
 * SPARSEMEM section (for variants of SPARSEMEM that require section ids like
 * SPARSEMEM_EXTREME with !SPARSEMEM_VMEMMAP).
 */

 

enum pageflags {
        PG_locked,              /* Page is locked. Don't touch. */
        PG_error,
        PG_referenced,
        PG_uptodate,
        PG_dirty,
        PG_lru,
        PG_active,
        PG_slab,
        PG_owner_priv_1,        /* Owner use. If pagecache, fs may use*/
        PG_arch_1,
        PG_reserved,
        PG_private,             /* If pagecache, has fs-private data */
        PG_private_2,           /* If pagecache, has fs aux data */
        PG_writeback,           /* Page is under writeback */
#ifdef CONFIG_PAGEFLAGS_EXTENDED
        PG_head,                /* A head page */
        PG_tail,                /* A tail page */
#else
        PG_compound,            /* A compound page */
#endif
        PG_swapcache,           /* Swap page: swp_entry_t in private */
        PG_mappedtodisk,        /* Has blocks allocated on-disk */
        PG_reclaim,             /* To be reclaimed asap */
        PG_swapbacked,          /* Page is backed by RAM/swap */
        PG_unevictable,         /* Page is "unevictable"  */
#ifdef CONFIG_MMU
        PG_mlocked,             /* Page is vma mlocked */
#endif
#ifdef CONFIG_ARCH_USES_PG_UNCACHED
        PG_uncached,            /* Page has been mapped as uncached */
#endif
#ifdef CONFIG_MEMORY_FAILURE
        PG_hwpoison,            /* hardware poisoned page. Don't touch */
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
        PG_compound_lock,
#endif
        __NR_PAGEFLAGS,

        /* Filesystems */
        PG_checked = PG_owner_priv_1,

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

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

        /* SLOB */
        PG_slob_free = PG_private,
};

 

 

참고

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)

 

참고

 

 

 

free_area_init_node()

다음 그림은 노드 및 Zone 구조체 정보를 초기화하는 흐름을 보여준다. 32/64 bit  및 NUMA 아키텍처 사용 여부에 따라 처리 흐름을 유의해야 한다.

  • arm64 아키텍처 커널 v4.7-rc1 부터 NUMA 설정을 지원하며, 그 전 버전이나 arm 아키텍처의 경우 별도의 NUMA 패치를 적용해야 한다.
    • 이 때 CONFIG_NUMA 및 CONFIG_HAVE_MEMBLOCK_NODE_MAP이 활성화된다.

free_area_init_node-1

 

free_area_init_node()

mm/page_alloc.c

void __paginginit free_area_init_node(int nid, unsigned long *zones_size,
                unsigned long node_start_pfn, unsigned long *zholes_size)
{
        pg_data_t *pgdat = NODE_DATA(nid);
        unsigned long start_pfn = 0;
        unsigned long end_pfn = 0;

        /* pg_data_t should be reset to zero when it's allocated */
        WARN_ON(pgdat->nr_zones || pgdat->classzone_idx);

        pgdat->node_id = nid;
        pgdat->node_start_pfn = node_start_pfn;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
        get_pfn_range_for_nid(nid, &start_pfn, &end_pfn);
        pr_info("Initmem setup node %d [mem %#018Lx-%#018Lx]\n", nid,
                (u64)start_pfn << PAGE_SHIFT, ((u64)end_pfn << PAGE_SHIFT) - 1);
#endif
        calculate_node_totalpages(pgdat, start_pfn, end_pfn,
                                  zones_size, zholes_size);

        alloc_node_mem_map(pgdat);
#ifdef CONFIG_FLAT_NODE_MEM_MAP
        printk(KERN_DEBUG "free_area_init_node: node %d, pgdat %08lx, node_mem_map %08lx\n",
                nid, (unsigned long)pgdat,
                (unsigned long)pgdat->node_mem_map);
#endif 

        free_area_init_core(pgdat, start_pfn, end_pfn,
                            zones_size, zholes_size);
}
  • pg_data_t *pgdat = NODE_DATA(nid);
    • 노드에 속한 페이지를 관리하는 구조체 포인터를 알아온다.
      • UMA 아키텍처: &contig_page_data
  • CONFIG_HAVE_MEMBLOCK_NODE_MAP
    • NUMA 시스템이 관리하는 memblock 노드 맵
  • get_pfn_range_for_nid(nid, &start_pfn, &end_pfn);
    • 지정된 노드의 memory memblock에서 파편화되지 않은 페이지 프레임 시작 번호와 끝 번호를 알아온다.
  • calculate_node_totalpages(pgdat, start_pfn, end_pfn, zones_size, zholes_size);
    • zones_size[]와 zholes_size[] 정보를 노드 정보에 기록한다.
      • ZONE_MOVABLE이 있는 경우 메모리가 존재하는 마지막 zone의 영역이 조정된다.
  • alloc_node_mem_map(pgdat);
    • page[] 구조체로 구성된 mem_map을 위해 memblock 할당을 한다
    • 이 루틴은 Sparse 메모리 모델이 아닌 경우에 동작된다.
      • Spasrse 메모리 모델을 사용하는 경우 mem_map은 이미 sparse_init() 함수에서 section_mem_map[]으로 구성되었다.
  • free_area_init_core(pgdat, start_pfn, end_pfn, zones_size, zholes_size);
    • 지정된 노드의 zone 관리를 위해 zone 구조체와 관련된 정보들을 설정한다. 그리고 usemap을 할당하고 초기화하며, 버디 시스템에 사용하는 free_area[], lruvec, pcp 등도 초기화한다.

 

free_area_init_nodes()

NUMA 아키텍처에서는 곧장 free_area_init_node() 함수를 호출하지 않고 먼저 이 함수를 호출한다.

mm/page_alloc.c

/**
 * free_area_init_nodes - Initialise all pg_data_t and zone data
 * @max_zone_pfn: an array of max PFNs for each zone
 *
 * This will call free_area_init_node() for each active node in the system.
 * Using the page ranges provided by memblock_set_node(), the size of each
 * zone in each node and their holes is calculated. If the maximum PFN
 * between two adjacent zones match, it is assumed that the zone is empty.
 * For example, if arch_max_dma_pfn == arch_max_dma32_pfn, it is assumed
 * that arch_max_dma32_pfn has no pages. It is also assumed that a zone
 * starts where the previous one ended. For example, ZONE_DMA32 starts
 * at arch_max_dma_pfn.
 */
void __init free_area_init_nodes(unsigned long *max_zone_pfn)
{
        unsigned long start_pfn, end_pfn;
        int i, nid; 

        /* Record where the zone boundaries are */
        memset(arch_zone_lowest_possible_pfn, 0,
                                sizeof(arch_zone_lowest_possible_pfn));
        memset(arch_zone_highest_possible_pfn, 0,
                                sizeof(arch_zone_highest_possible_pfn));
        arch_zone_lowest_possible_pfn[0] = find_min_pfn_with_active_regions();
        arch_zone_highest_possible_pfn[0] = max_zone_pfn[0];
        for (i = 1; i < MAX_NR_ZONES; i++) {
                if (i == ZONE_MOVABLE)
                        continue;
                arch_zone_lowest_possible_pfn[i] =
                        arch_zone_highest_possible_pfn[i-1];
                arch_zone_highest_possible_pfn[i] =
                        max(max_zone_pfn[i], arch_zone_lowest_possible_pfn[i]);
        }
        arch_zone_lowest_possible_pfn[ZONE_MOVABLE] = 0; 
        arch_zone_highest_possible_pfn[ZONE_MOVABLE] = 0; 

        /* Find the PFNs that ZONE_MOVABLE begins at in each node */
        memset(zone_movable_pfn, 0, sizeof(zone_movable_pfn));
        find_zone_movable_pfns_for_nodes();

모든 노드 및 ZONE 정보를 초기화하고 구성한다.

 

  • arch_zone_lowest_possible_pfn[0] = find_min_pfn_with_active_regions();
    • 모든 노드 메모리중 첫 pfn을 알아온다.
  • arch_zone_highest_possible_pfn[0] = max_zone_pfn[0];
    • 첫 zone의 end 주소를 대입한다.
  • arch_zone_lowest_possible_pfn[ZONE_MOVABLE] = 0; arch_zone_highest_possible_pfn[ZONE_MOVABLE] = 0;
    • zone 별 관리되는 ZONE_MOVABLE은 항상 0이다.
    • ZONE_MOVABLE은 노드별(zone_movable_pfn[])로 관리된다.
  • find_zone_movable_pfns_for_nodes();
    • 노드별 ZONE_MOVABLE에 대한 시작 pfn 값을 zone_movable_pfn[]에 담아온다.

 

아래 그림과 같이 zone 영역을 산출한다. (arch_zone_lowest_possible_pfn[] & arch_zone_highest_possible_pfn[])

  • arch_zone_lowest_possible_pfn[ZONE_MOVABLE] 및 arch_zone_highest_possible_pfn[ZONE_MOVABLE]은 언제나 0이다.
    • zone movable에 대한 값은 노드별로 관리한다.

free_area_init_nodes-1a

 

        /* Print out the zone ranges */
        pr_info("Zone ranges:\n");
        for (i = 0; i < MAX_NR_ZONES; i++) {
                if (i == ZONE_MOVABLE)
                        continue;
                pr_info("  %-8s ", zone_names[i]);
                if (arch_zone_lowest_possible_pfn[i] ==
                                arch_zone_highest_possible_pfn[i])
                        pr_cont("empty\n");
                else
                        pr_cont("[mem %#018Lx-%#018Lx]\n",
                                (u64)arch_zone_lowest_possible_pfn[i]
                                        << PAGE_SHIFT,
                                ((u64)arch_zone_highest_possible_pfn[i]
                                        << PAGE_SHIFT) - 1);
        }

        /* Print out the PFNs ZONE_MOVABLE begins at in each node */
        pr_info("Movable zone start for each node\n");
        for (i = 0; i < MAX_NUMNODES; i++) {
                if (zone_movable_pfn[i])
                        pr_info("  Node %d: %#018Lx\n", i,
                               (u64)zone_movable_pfn[i] << PAGE_SHIFT);
        }

        /* Print out the early node map */
        pr_info("Early memory node ranges\n");
        for_each_mem_pfn_range(i, MAX_NUMNODES, &start_pfn, &end_pfn, &nid)
                pr_info("  node %3d: [mem %#018Lx-%#018Lx]\n", nid,
                        (u64)start_pfn << PAGE_SHIFT,
                        ((u64)end_pfn << PAGE_SHIFT) - 1);

 

        /* Initialise every node */
        mminit_verify_pageflags_layout();
        setup_nr_node_ids();
        for_each_online_node(nid) {
                pg_data_t *pgdat = NODE_DATA(nid);
                free_area_init_node(nid, NULL,
                                find_min_pfn_for_node(nid), NULL);

                /* Any memory on that node */
                if (pgdat->node_present_pages)
                        node_set_state(nid, N_MEMORY);
                check_for_memory(pgdat, nid);
        }
}
  • mminit_verify_pageflags_layout()
    • page->flags에 들어갈 섹션 비트 수, 노드 비트 수, zone 비트 수 등을 점검한다.
  • setup_nr_node_ids()
    • 마지막 possible 노드 + 1을 전역 nr_node_ids에 대입한다.
  • for_each_online_node(nid) { pg_data_t *pgdat = NODE_DATA(nid); free_area_init_node(nid, NULL, find_min_pfn_for_node(nid), NULL);
    • 모든 online 노드에 대해 노드를 초기화 한다.

 

find_zone_movable_pfns_for_nodes()

mm/page_alloc.c

/*
 * Find the PFN the Movable zone begins in each node. Kernel memory
 * is spread evenly between nodes as long as the nodes have enough
 * memory. When they don't, some nodes will have more kernelcore than
 * others
 */
static void __init find_zone_movable_pfns_for_nodes(void)
{
        int i, nid;
        unsigned long usable_startpfn;
        unsigned long kernelcore_node, kernelcore_remaining;
        /* save the state before borrow the nodemask */
        nodemask_t saved_node_state = node_states[N_MEMORY];
        unsigned long totalpages = early_calculate_totalpages();
        int usable_nodes = nodes_weight(node_states[N_MEMORY]);
        struct memblock_region *r;

        /* Need to find movable_zone earlier when movable_node is specified. */
        find_usable_zone_for_movable();

        /*
         * If movable_node is specified, ignore kernelcore and movablecore
         * options.
         */
        if (movable_node_is_enabled()) {
                for_each_memblock(memory, r) {
                        if (!memblock_is_hotpluggable(r))
                                continue;

                        nid = r->nid;

                        usable_startpfn = PFN_DOWN(r->base);
                        zone_movable_pfn[nid] = zone_movable_pfn[nid] ?
                                min(usable_startpfn, zone_movable_pfn[nid]) :
                                usable_startpfn;
                }

                goto out2;
        }

노드별 ZONE_MOVABLE에 대한 시작 pfn 값을 zone_movable_pfn[]에 담아온다.

 

  • unsigned long totalpages = early_calculate_totalpages();
    • 모든 노드의 페이지 수를 알아온다.
  • int usable_nodes = nodes_weight(node_states[N_MEMORY]);
    • 메모리가 있는 노드의 수를 알아온다.
  •  find_usable_zone_for_movable();
    • 모든 zone에서 메모리가 있는 마지막 zone을 알아와서 전역 변수 movable_zone에 대입한다.
    • ZONE_MOVABLE은 마지막 zone의 일부 또는 전부를 사용하여 구성하게 된다.

 

Hot-plug용 ZONE_MOVABLE을 구성하기 위해 노드별 zone movable의 시작 주소를 전역 zone_movable_pfn[] 배열에 산출한다.

  • if (movable_node_is_enabled()) {
    • CONFIG_MOVABLE_NODE 커널 옵션을 사용하면서 “movable_node” 커널 파라메터를 사용한 경우 true를 반환한다.
  • for_each_memblock(memory, r) {
    • 모든 memory memblock의 영역에 대해 루프를 돈다.
  • if (!memblock_is_hotpluggable(r)) continue;
    • 해당 memory memblock 영역이 hotplug 설정이 없는 경우 skip
  • usable_startpfn = PFN_DOWN(r->base); zone_movable_pfn[nid] = zone_movable_pfn[nid] ? min(usable_startpfn, zone_movable_pfn[nid]) : usable_startpfn;
    • hotplug 설정이 있는 memblock의 시작 주소를 zone_movable_pfn[]에 대입하되 zone_movable_pfn[] 값보다 큰 경우에 한정 한다.

 

        /*
         * If movablecore=nn[KMG] was specified, calculate what size of
         * kernelcore that corresponds so that memory usable for
         * any allocation type is evenly spread. If both kernelcore
         * and movablecore are specified, then the value of kernelcore
         * will be used for required_kernelcore if it's greater than
         * what movablecore would have allowed.
         */
        if (required_movablecore) {
                unsigned long corepages;

                /*
                 * Round-up so that ZONE_MOVABLE is at least as large as what
                 * was requested by the user
                 */
                required_movablecore =
                        roundup(required_movablecore, MAX_ORDER_NR_PAGES);
                corepages = totalpages - required_movablecore;

                required_kernelcore = max(required_kernelcore, corepages);
        }

        /* If kernelcore was not specified, there is no ZONE_MOVABLE */
        if (!required_kernelcore)
                goto out;

        /* usable_startpfn is the lowest possible pfn ZONE_MOVABLE can be at */
        usable_startpfn = arch_zone_lowest_possible_pfn[movable_zone];

“kernelcore=” 또는 “movablecore=”를 사용하여 ZONE_MOVABLE에 대한 요청 페이지 수가 있는 경우를 처리한다.

  • if (required_movablecore) {
    • “movablecore=” 커널 파라메터로 ZONE_MOVABLE의 요청 페이지 수가 지정된 경우
  • required_movablecore = roundup(required_movablecore, MAX_ORDER_NR_PAGES);
    • ZONE_MOVABLE을 구성할 요청 페이지 수를 MAX_ORDER_NR_PAGES 단위로 정렬한다.
  • corepages = totalpages – required_movablecore; required_kernelcore = max(required_kernelcore, corepages);
    • 전체 페이지 수에 맞게 required_kernelcore 페이지 수를 증가시킨다.

 

다음 그림은 required_kernelcore가 실제 메모리 페이지 수보다 작게 지정된 경우 재 조정된 모습을 보여준다.

find_zone_movable_pfns_for_nodes-3

 

restart:
        /* Spread kernelcore memory as evenly as possible throughout nodes */
        kernelcore_node = required_kernelcore / usable_nodes;
        for_each_node_state(nid, N_MEMORY) {
                unsigned long start_pfn, end_pfn;

                /*
                 * Recalculate kernelcore_node if the division per node
                 * now exceeds what is necessary to satisfy the requested
                 * amount of memory for the kernel
                 */
                if (required_kernelcore < kernelcore_node)
                        kernelcore_node = required_kernelcore / usable_nodes;

                /*
                 * As the map is walked, we track how much memory is usable
                 * by the kernel using kernelcore_remaining. When it is
                 * 0, the rest of the node is usable by ZONE_MOVABLE
                 */
                kernelcore_remaining = kernelcore_node;

                /* Go through each range of PFNs within this node */
                for_each_mem_pfn_range(i, nid, &start_pfn, &end_pfn, NULL) {
                        unsigned long size_pages;

                        start_pfn = max(start_pfn, zone_movable_pfn[nid]);
                        if (start_pfn >= end_pfn)
                                continue;

                        /* Account for what is only usable for kernelcore */
                        if (start_pfn < usable_startpfn) {
                                unsigned long kernel_pages;
                                kernel_pages = min(end_pfn, usable_startpfn)
                                                                - start_pfn;

                                kernelcore_remaining -= min(kernel_pages,
                                                        kernelcore_remaining);
                                required_kernelcore -= min(kernel_pages,
                                                        required_kernelcore);

                                /* Continue if range is now fully accounted */
                                if (end_pfn <= usable_startpfn) {

                                        /*
                                         * Push zone_movable_pfn to the end so
                                         * that if we have to rebalance
                                         * kernelcore across nodes, we will
                                         * not double account here
                                         */
                                        zone_movable_pfn[nid] = end_pfn;
                                        continue;
                                }
                                start_pfn = usable_startpfn;
                        }

                        /*
                         * The usable PFN range for ZONE_MOVABLE is from
                         * start_pfn->end_pfn. Calculate size_pages as the
                         * number of pages used as kernelcore
                         */
                        size_pages = end_pfn - start_pfn;
                        if (size_pages > kernelcore_remaining)
                                size_pages = kernelcore_remaining;
                        zone_movable_pfn[nid] = start_pfn + size_pages;

                        /*
                         * Some kernelcore has been met, update counts and
                         * break if the kernelcore for this node has been
                         * satisfied
                         */
                        required_kernelcore -= min(required_kernelcore,
                                                                size_pages);
                        kernelcore_remaining -= size_pages;
                        if (!kernelcore_remaining)
                                break;
                }
        }

        /*
         * If there is still required_kernelcore, we do another pass with one
         * less node in the count. This will push zone_movable_pfn[nid] further
         * along on the nodes that still have memory until kernelcore is
         * satisfied
         */
        usable_nodes--;
        if (usable_nodes && required_kernelcore > usable_nodes)
                goto restart;

out2:
        /* Align start of ZONE_MOVABLE on all nids to MAX_ORDER_NR_PAGES */
        for (nid = 0; nid < MAX_NUMNODES; nid++)
                zone_movable_pfn[nid] =
                        roundup(zone_movable_pfn[nid], MAX_ORDER_NR_PAGES);

out:
        /* restore the node_state */
        node_states[N_MEMORY] = saved_node_state;
}

다음 그림은 “kernelcore=”로 지정된 페이지 수를 가용 노드 만큼 나누어 배치를 하고 이에 대한 끝 주소를 zone_movable_pfn[] 배열에 저장하는 모습을 보여준다. 이 값은 추후 각 노드별 ZONE_MOVABLE이 시작되는 주소를 설정하는데 사용된다.

find_zone_movable_pfns_for_nodes-4

 

다음 4개의 그림은 NUMA 시스템에서 “kernelcore=” 커널 파라메터 값에 따라 ZONE_MOVABLE 영역이 지정되는 모습을 보여준다.

find_zone_movable_pfns_for_nodes-5

find_zone_movable_pfns_for_nodes-6

find_zone_movable_pfns_for_nodes-7

find_zone_movable_pfns_for_nodes-8a

 

find_usable_zone_for_movable()

mm/page_alloc.c

/*
 * This finds a zone that can be used for ZONE_MOVABLE pages. The
 * assumption is made that zones within a node are ordered in monotonic
 * increasing memory addresses so that the "highest" populated zone is used
 */
static void __init find_usable_zone_for_movable(void)
{
        int zone_index;
        for (zone_index = MAX_NR_ZONES - 1; zone_index >= 0; zone_index--) {
                if (zone_index == ZONE_MOVABLE)
                        continue;

                if (arch_zone_highest_possible_pfn[zone_index] >
                                arch_zone_lowest_possible_pfn[zone_index])
                        break;
        }

        VM_BUG_ON(zone_index == -1);
        movable_zone = zone_index;
}

가용 zone에서 가장 높이 위치한 zone 인덱스 값을 전역 변수 movable_zone에 저장한다.

 

find_usable_zone_for_movable-1

 

get_pfn_range_for_nid()

mm/page_alloc.c

/**
 * get_pfn_range_for_nid - Return the start and end page frames for a node
 * @nid: The nid to return the range for. If MAX_NUMNODES, the min and max PFN are returned. * @start_pfn: Passed by reference. On return, it will have the node start_pfn.
 * @end_pfn: Passed by reference. On return, it will have the node end_pfn.
 *
 * It returns the start and end page frame of a node based on information
 * provided by memblock_set_node(). If called for a node
 * with no available memory, a warning is printed and the start and end
 * PFNs will be 0.
 */
void __meminit get_pfn_range_for_nid(unsigned int nid,
                        unsigned long *start_pfn, unsigned long *end_pfn)
{
        unsigned long this_start_pfn, this_end_pfn;
        int i;

        *start_pfn = -1UL;
        *end_pfn = 0;

        for_each_mem_pfn_range(i, nid, &this_start_pfn, &this_end_pfn, NULL) {
                *start_pfn = min(*start_pfn, this_start_pfn);
                *end_pfn = max(*end_pfn, this_end_pfn);
        }

        if (*start_pfn == -1UL)
                *start_pfn = 0;
}
  • for_each_mem_pfn_range(i, nid, &this_start_pfn, &this_end_pfn, NULL) {
    • 해당 노드 번호의 memory memblock 에서 해당 페이지가 파편화되지 않고 온전히 포함된 페이지 번호만을 찾아 시작 pfn과 끝 pfn으로 알아온다.
      • 만일 노드 지정에 MAX_NUMNODES가 지정되면 DRAM의 min_pfn과 max_pfn이 리턴된다.
      • 참고: for_each_mem_pfn_range() 함수 -> Memblock – (2) | 문c
  • *start_pfn = min(*start_pfn, this_start_pfn);
    • 검색된 시작 페이지 번호 값에서 가장 낮은 페이지 번호
  • *end_pfn = max(*end_pfn, this_end_pfn);
    • 검색된 끝 페이지 번호 값에서 가장 큰 페이지 번호

 

calculate_node_totalpages()

mm/page_alloc.c

static void __meminit calculate_node_totalpages(struct pglist_data *pgdat,
                                                unsigned long node_start_pfn,
                                                unsigned long node_end_pfn,
                                                unsigned long *zones_size,
                                                unsigned long *zholes_size)
{
        unsigned long realtotalpages, totalpages = 0;
        enum zone_type i;

        for (i = 0; i < MAX_NR_ZONES; i++)
                totalpages += zone_spanned_pages_in_node(pgdat->node_id, i,
                                                         node_start_pfn,
                                                         node_end_pfn,
                                                         zones_size);
        pgdat->node_spanned_pages = totalpages;

        realtotalpages = totalpages;
        for (i = 0; i < MAX_NR_ZONES; i++)
                realtotalpages -=
                        zone_absent_pages_in_node(pgdat->node_id, i,
                                                  node_start_pfn, node_end_pfn,
                                                  zholes_size);
        pgdat->node_present_pages = realtotalpages;
        printk(KERN_DEBUG "On node %d totalpages: %lu\n", pgdat->node_id,
                                                        realtotalpages);
}
  •  for (i = 0; i < MAX_NR_ZONES; i++)
    • zone 수 만큼 루프를 돈다.
  • totalpages += zone_spanned_pages_in_node(pgdat->node_id, i, node_start_pfn, node_end_pfn, zones_size);
    • 현재 노드에 대해 요청 zone 범위의 총 페이지 수
  •  pgdat->node_spanned_pages = totalpages;
    • 노드 정보에 기록한다.
  • 위 루프와 같은 방식으로 현재 노드의 요청 zone에서 실제 사용가능한 총 페이지 수를 알아와 노드 정보에 기록한다.

calculate_node_totalpages-1b

 

zone_spanned_pages_in_node()

mm/page_alloc.c

#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
/*
 * Return the number of pages a zone spans in a node, including holes
 * present_pages = zone_spanned_pages_in_node() - zone_absent_pages_in_node()
 */
static unsigned long __meminit zone_spanned_pages_in_node(int nid,
                                        unsigned long zone_type,
                                        unsigned long node_start_pfn,
                                        unsigned long node_end_pfn,
                                        unsigned long *ignored)
{
        unsigned long zone_start_pfn, zone_end_pfn;

        /* Get the start and end of the zone */
        zone_start_pfn = arch_zone_lowest_possible_pfn[zone_type];
        zone_end_pfn = arch_zone_highest_possible_pfn[zone_type];
        adjust_zone_range_for_zone_movable(nid, zone_type,
                                node_start_pfn, node_end_pfn,
                                &zone_start_pfn, &zone_end_pfn);

        /* Check that this node has pages within the zone's required range */
        if (zone_end_pfn < node_start_pfn || zone_start_pfn > node_end_pfn)
                return 0;

        /* Move the zone boundaries inside the node if necessary */
        zone_end_pfn = min(zone_end_pfn, node_end_pfn);
        zone_start_pfn = max(zone_start_pfn, node_start_pfn);

        /* Return the spanned pages */
        return zone_end_pfn - zone_start_pfn;
}

#else /* CONFIG_HAVE_MEMBLOCK_NODE_MAP */
static inline unsigned long __meminit zone_spanned_pages_in_node(int nid,
                                        unsigned long zone_type,
                                        unsigned long node_start_pfn,
                                        unsigned long node_end_pfn,
                                        unsigned long *zones_size)
{
        return zones_size[zone_type];
}
#endif /* CONFIG_HAVE_MEMBLOCK_NODE_MAP */

해당 노드에 대해 요청 zone의 hole을 포함한 페이지 수를 알아온다. NUMA 시스템에서는 ZONE_MOVABLE을 사용하는 경우가 있으므로 이 때 highest zone의 영역의 일정 양을 나누어 사용하므로 이에 대한 페이지 수 계산을 해야 한다. 상기 코드는 NUMA를 예로 하였고 UMA의 경우 간단하므로 설명은 생략한다.

  • zone_start_pfn = arch_zone_lowest_possible_pfn[zone_type];
    •  zone이 시작되는 pfn 값
  • zone_end_pfn = arch_zone_highest_possible_pfn[zone_type];
    •  zone이 끝나는 pfn 값
  • adjust_zone_range_for_zone_movable(nid, zone_type, node_start_pfn, node_end_pfn, &zone_start_pfn, &zone_end_pfn);
    • ZONE_MOVABLE이 사용되는 경우 실제 movable 가능한 페이지 영역을 기준으로 zone 들의 영역을 조정한다.
  • zone_end_pfn = min(zone_end_pfn, node_end_pfn);
    • zone 끝 pfn 값이 노드 끝 pfn 값을 초과하지 않도록 한다.
  • zone_start_pfn = max(zone_start_pfn, node_start_pfn);
    • zone 시작 pfn 값이 노드 시작 pfn 값보다 작지 않도록 한다.
  • return zone_end_pfn – zone_start_pfn;
    • 재조정된 zone의 hole을 포함한 페이지 수를 리턴한다.

 

adjust_zone_range_for_zone_movable()

page_alloc.c

/*
 * The zone ranges provided by the architecture do not include ZONE_MOVABLE
 * because it is sized independent of architecture. Unlike the other zones,
 * the starting point for ZONE_MOVABLE is not fixed. It may be different
 * in each node depending on the size of each node and how evenly kernelcore
 * is distributed. This helper function adjusts the zone ranges
 * provided by the architecture for a given node by using the end of the
 * highest usable zone for ZONE_MOVABLE. This preserves the assumption that
 * zones within a node are in order of monotonic increases memory addresses
 */
static void __meminit adjust_zone_range_for_zone_movable(int nid,
                                        unsigned long zone_type,
                                        unsigned long node_start_pfn,
                                        unsigned long node_end_pfn,
                                        unsigned long *zone_start_pfn,
                                        unsigned long *zone_end_pfn)
{
        /* Only adjust if ZONE_MOVABLE is on this node */
        if (zone_movable_pfn[nid]) {
                /* Size ZONE_MOVABLE */
                if (zone_type == ZONE_MOVABLE) {
                        *zone_start_pfn = zone_movable_pfn[nid];
                        *zone_end_pfn = min(node_end_pfn,
                                arch_zone_highest_possible_pfn[movable_zone]);

                /* Adjust for ZONE_MOVABLE starting within this range */
                } else if (*zone_start_pfn < zone_movable_pfn[nid] &&
                                *zone_end_pfn > zone_movable_pfn[nid]) {
                        *zone_end_pfn = zone_movable_pfn[nid];

                /* Check if this whole range is within ZONE_MOVABLE */
                } else if (*zone_start_pfn >= zone_movable_pfn[nid])
                        *zone_start_pfn = *zone_end_pfn;
        }
}

실제 movable 가능한 페이지 영역을 기준으로 현재 zone의 영역을 다음과 같이 조정 한다.

  • 처리할 zone이 ZONE_MOVABLE인 경우 실제 movable 가능한 페이지 범위로 조정한다.
  • 처리할 zone이 ZONE_MOVABLE이 아닌 경우 movable 가능한 페이지 범위와 겹치지 않도록 조정한다.

 

ZONE_MOVABLE 영역을 설정한다.

  • if (zone_movable_pfn[nid]) {
    • 해당 노드에 ZONE_MOVEABLE이 있는 경우
  • if (zone_type == ZONE_MOVABLE) {
    • 현재 zone이 ZONE_MOVABLE인 경우
  • *zone_start_pfn = zone_movable_pfn[nid];
    • zone 시작 pfn 에 zone_movable_pfn[nid] 값을 대입한다. 
  • *zone_end_pfn = min(node_end_pfn, arch_zone_highest_possible_pfn[movable_zone]);
    • zone 끝 pfn을 moveable 가능한 페이지 번호로 줄인다.

 

현재 노드의 메모리 일부가 ZONE_MOVABLE로 구성한 경우 last zone의 끝 주소를 조정한다.

  • } else if (*zone_start_pfn < zone_movable_pfn[nid] && *zone_end_pfn > zone_movable_pfn[nid]) { *zone_end_pfn = zone_movable_pfn[nid];
    • 요청 zone이 ZONE_MOVABLE이 아니면서 요청 zone이 movable 영역보다 높은 경우 잘못 요청한 경우 이므로 zone 시작 pfn에 zone 끝 pfn 값을 대입하여 요청 zone의 size가 0이되게 하여 이 zone을 처리하지 않게 한다. 
    • 노드 내에서 ZONE_MOVABLE 상위에 다른 노드가 존재할 수 없다.

 

다음 3 개의 그림은 노드별 ZONE_MOVABLE의 영역을 확정하기 위해 zone_movable_pfn[] 배열 값을 산출하는 것을 보여준다.

find_zone_movable_pfns_for_nodes-1c

 

find_zone_movable_pfns_for_nodes-2a

 

find_zone_movable_pfns_for_nodes-9

 

zone_absent_pages_in_node()

mm/page_alloc.c

#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
static unsigned long __meminit zone_absent_pages_in_node(int nid,
                                        unsigned long zone_type,
                                        unsigned long node_start_pfn,
                                        unsigned long node_end_pfn,
                                        unsigned long *ignored)
{
        unsigned long zone_low = arch_zone_lowest_possible_pfn[zone_type];
        unsigned long zone_high = arch_zone_highest_possible_pfn[zone_type];
        unsigned long zone_start_pfn, zone_end_pfn;

        zone_start_pfn = clamp(node_start_pfn, zone_low, zone_high);
        zone_end_pfn = clamp(node_end_pfn, zone_low, zone_high);

        adjust_zone_range_for_zone_movable(nid, zone_type,
                        node_start_pfn, node_end_pfn,
                        &zone_start_pfn, &zone_end_pfn);
        return __absent_pages_in_range(nid, zone_start_pfn, zone_end_pfn);
}

#else /* CONFIG_HAVE_MEMBLOCK_NODE_MAP */
static inline unsigned long __meminit zone_absent_pages_in_node(int nid,
                                                unsigned long zone_type,
                                                unsigned long node_start_pfn,
                                                unsigned long node_end_pfn,
                                                unsigned long *zholes_size)
{
        if (!zholes_size)
                return 0;

        return zholes_size[zone_type];
}

#endif /* CONFIG_HAVE_MEMBLOCK_NODE_MAP */

해당 노드에 대해 요청 zone에서 빈 공간(hole) 페이지 수를 알아온다.

  • CONFIG_HAVE_MEMBLOCK_NODE_MAP 옵션 사용 여부에 따라 빌드되어 호출되는 함수가 다르다.
    • NUMA 시스템에서 옵션을 사용하지 않는 경우(멀티 노드이지만 주소가 연속된 노드로 구성)와 UMA 시스템에서는 해당 노드에서 관리하는 zone의 페이지들이 모두 연속적이어서 특별히 계산 없이 zholes_size[]에 있는 값을 리턴한다.
    • 옵션을 사용하는 경우는 노드 내에 memory block들이 연속되지 않을 때에 빈 공간(hole) 페이지 수를 계산하기 위해 사용한다.
  • unsigned long zone_low = arch_zone_lowest_possible_pfn[zone_type];
    • zone 시작 pfn
  • unsigned long zone_high = arch_zone_highest_possible_pfn[zone_type];
    • zone 끝 pfn
  • zone_start_pfn = clamp(node_start_pfn, zone_low, zone_high);
    •  node_start_pfn 값을 zone 영역으로 들어가도록 조정하여 zone 시작 pfn으로 대입한다.
  • zone_end_pfn = clamp(node_end_pfn, zone_low, zone_high);
    • node_end_pfn 값을 zone 영역으로 들어가도록 조정하여 zone 끝 pfn으로 대입한다.
  • adjust_zone_range_for_zone_movable(nid, zone_type, node_start_pfn, node_end_pfn, &zone_start_pfn, &zone_end_pfn);
    • 실제 movable 가능한 페이지 영역을 기준으로 zone 들의 영역을 조정한다.
  • return __absent_pages_in_range(nid, zone_start_pfn, zone_end_pfn);
    • 지정된 영역내에서 빈 공간(hole) 페이지 수를 리턴한다.

 

__absent_pages_in_range()

mm/page_alloc.c

/*
 * Return the number of holes in a range on a node. If nid is MAX_NUMNODES,
 * then all holes in the requested range will be accounted for.
 */
unsigned long __meminit __absent_pages_in_range(int nid,
                                unsigned long range_start_pfn,
                                unsigned long range_end_pfn)
{
        unsigned long nr_absent = range_end_pfn - range_start_pfn;
        unsigned long start_pfn, end_pfn;
        int i;

        for_each_mem_pfn_range(i, nid, &start_pfn, &end_pfn, NULL) {
                start_pfn = clamp(start_pfn, range_start_pfn, range_end_pfn);
                end_pfn = clamp(end_pfn, range_start_pfn, range_end_pfn);
                nr_absent -= end_pfn - start_pfn;
        }
        return nr_absent;
}
  • unsigned long nr_absent = range_end_pfn – range_start_pfn;
    • nr_absent  초기값을 영역에 대한 페이지 수로 한다.
  • for_each_mem_pfn_range(i, nid, &start_pfn, &end_pfn, NULL) {
    • 노드 번호에 해당하는 memory memblock에서 파편화된 페이지를 제외한 즉 온전한 페이지가 1개 이상인 경우의 시작 pfn과 끝 pfn 값을 루프를 돌며 하나씩 알아온다
  • start_pfn = clamp(start_pfn, range_start_pfn, range_end_pfn);
    • 영역에 들어가게 start_pfn 값을 바꾼다.
  • end_pfn = clamp(end_pfn, range_start_pfn, range_end_pfn);
    • 영역에 들어가게 end_pfn 값을 바꾼다.
  • nr_absent -= end_pfn – start_pfn;
    • 영역에 포함된 페이지 수를 빼면 사용할 수 없는 페이지 수가 계산된다.

아래 그림은 함수 처리 예를 보인다. (실제 NUMA 시스템 예제가 아니다.)

__absent_pages_in_range-1b

 

alloc_node_mem_map()

mm/page_alloc.c

static void __init_refok alloc_node_mem_map(struct pglist_data *pgdat)
{
        /* Skip empty nodes */
        if (!pgdat->node_spanned_pages)
                return;

#ifdef CONFIG_FLAT_NODE_MEM_MAP
        /* ia64 gets its own node_mem_map, before this, without bootmem */
        if (!pgdat->node_mem_map) {
                unsigned long size, start, end;
                struct page *map;

                /*
                 * The zone's endpoints aren't required to be MAX_ORDER
                 * aligned but the node_mem_map endpoints must be in order
                 * for the buddy allocator to function correctly.
                 */
                start = pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES - 1);
                end = pgdat_end_pfn(pgdat);
                end = ALIGN(end, MAX_ORDER_NR_PAGES);
                size =  (end - start) * sizeof(struct page);
                map = alloc_remap(pgdat->node_id, size);
                if (!map)
                        map = memblock_virt_alloc_node_nopanic(size,
                                                               pgdat->node_id);
                pgdat->node_mem_map = map + (pgdat->node_start_pfn - start);
        }
#ifndef CONFIG_NEED_MULTIPLE_NODES
        /*
         * With no DISCONTIG, the global mem_map is just set as node 0's
         */
        if (pgdat == NODE_DATA(0)) {
                mem_map = NODE_DATA(0)->node_mem_map;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
                if (page_to_pfn(mem_map) != pgdat->node_start_pfn)
                        mem_map -= (pgdat->node_start_pfn - ARCH_PFN_OFFSET);
#endif /* CONFIG_HAVE_MEMBLOCK_NODE_MAP */
        }
#endif
#endif /* CONFIG_FLAT_NODE_MEM_MAP */
}

page[] 구조체로 구성된 mem_map을 위해 memblock 할당을 한다.

  • if (!pgdat->node_spanned_pages) return;
    • 노드에 사용 가능한 메모리 페이지 영역이 없는 경우 리턴한다.
  • CONFIG_FLAT_NODE_MEM_MAP
    • rpi2 같이 UMA 시스템에서 flat하게 연속된 메모리를 사용하는 경우
  • if (!pgdat->node_mem_map) {
    • page[] 구조체 배열을 가리키는 값이 없으면
  • start = pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES – 1);
  • end = pgdat_end_pfn(pgdat);
    • pgdat->node_start_pfn + pgdat->node_spanned_pages 값
  • end = ALIGN(end, MAX_ORDER_NR_PAGES);
    • 끝 pfn을 1K 단위 round up
    • mem_map은 나중에 buddy 메모리 할당자에서 사용되어야 하는데 MAX_ORDER에 align되어 사용되어야 하기 때문에 end 값의 align 단위를 조절한다.
  • size =  (end – start) * sizeof(struct page);
    • 필요한 페이지 구조체 배열의 크기
  •  map = alloc_remap(pgdat->node_id, size);
    • TILE 아키텍처가 아닌 경우 아무것도 수행하지 않고 null을 리턴한다.
  • if (!map) map = memblock_virt_alloc_node_nopanic(size, pgdat->node_id);
    • TILE 아키텍처가 아닌 경우 map은 항상 null이므로 memblock을 지정된 node에서 size 만큼 할당받는다.
  • pgdat->node_mem_map = map + (pgdat->node_start_pfn – start);
  • if (pgdat == NODE_DATA(0)) { mem_map = NODE_DATA(0)->node_mem_map;
    • 연속된 메모리를 사용하는 경우 전역 변수 mem_map은 (&contig_page_data)->node_mem_map을 대입한다.
  • if (page_to_pfn(mem_map) != pgdat->node_start_pfn) mem_map -= (pgdat->node_start_pfn – ARCH_PFN_OFFSET);
    • mem_map의 pfn 값이 노드 시작 pfn이 아닌 경우 mem_map을 ARCH_PFN_OFFSET 만큼 뺀다.
    • 커널 v4.4-rc1에서 2M 이하의 flatmem을 사용하는 특정 시스템에서 문제가 있어 패치를 하였다.

 

다음 그림은 지정된 노드에 대한 mem_map[]을 할당 받아 노드 구조체의 node_mem_map에 연결시키는 모습을 보여준다.

alloc_node_mem_map-1a

 

free_area_init_core()

mm/page_alloc.c

/*
 * Set up the zone data structures:
 *   - mark all pages reserved
 *   - mark all memory queues empty
 *   - clear the memory bitmaps
 *
 * NOTE: pgdat should get zeroed by caller.
 */
static void __paginginit free_area_init_core(struct pglist_data *pgdat,
                unsigned long node_start_pfn, unsigned long node_end_pfn,
                unsigned long *zones_size, unsigned long *zholes_size)
{
        enum zone_type j;
        int nid = pgdat->node_id;
        unsigned long zone_start_pfn = pgdat->node_start_pfn;
        int ret;

        pgdat_resize_init(pgdat);
#ifdef CONFIG_NUMA_BALANCING
        spin_lock_init(&pgdat->numabalancing_migrate_lock);
        pgdat->numabalancing_migrate_nr_pages = 0;
        pgdat->numabalancing_migrate_next_window = jiffies;
#endif
        init_waitqueue_head(&pgdat->kswapd_wait);
        init_waitqueue_head(&pgdat->pfmemalloc_wait);
        pgdat_page_ext_init(pgdat);

        for (j = 0; j < MAX_NR_ZONES; j++) {
                struct zone *zone = pgdat->node_zones + j;
                unsigned long size, realsize, freesize, memmap_pages;

                size = zone_spanned_pages_in_node(nid, j, node_start_pfn,
                                                  node_end_pfn, zones_size);
                realsize = freesize = size - zone_absent_pages_in_node(nid, j,
                                                                node_start_pfn,
                                                                node_end_pfn,
                                                                zholes_size);

지정된 노드의 zone 관리를 위해 zone 구조체와 관련된 정보들을 설정한다. 그리고 usemap을 할당하고 초기화하며, 버디 시스템에 사용하는 free_area[], lruvec, pcp 등도 초기화한다.

 

  •  pgdat_resize_init(pgdat);
    • spin_lock_init(&pgdat->node_size_lock);
      • node_size_lock 초기화
  • CONFIG_NUMA_BALANCING
    • NUMA 노드간 task 및 메모리 등 밸런스를 맞추는 옵션
  • spin_lock_init(&pgdat->numabalancing_migrate_lock);
    • numabalancing_migrate_lock 초기화
  • pgdat->numabalancing_migrate_nr_pages = 0;
    • numa 밸런싱을 위해 이주를 한 페이지 수를 0으로 대입
  • pgdat->numabalancing_migrate_next_window = jiffies;
    • numa 밸런싱을 위해 이주를 위한 다음 window 값에 현재 클럭값을 대입
  • init_waitqueue_head(&pgdat->kswapd_wait);
    • kswapd_wait queue 초기화
  • init_waitqueue_head(&pgdat->pfmemalloc_wait);
    • pfmemalloc_wait queue 초기화
  • pgdat_page_ext_init(pgdat);
    • CONFIG_SPARSEMEM 커널 옵션이 설정되어 있지 않으면 pgdat->node_page_ext = NULL;
  • for (j = 0; j < MAX_NR_ZONES; j++) {
    • zone 수 만큼 루프를 돈다.
  • size = zone_spanned_pages_in_node(nid, j, node_start_pfn, node_end_pfn, zones_size);
    • 현재 노드의 j zone에서 spanned(유효 memory memblock 페이지의 시작과 끝) 페이지 수를 알아온다.
  • realsize = freesize = size – zone_absent_pages_in_node(nid, j, node_start_pfn, node_end_pfn, zholes_size);
    • 현재 노드의 j zone에서 실제 사용할 수 있는(hole을 제외한) memory memblock 페이지 수를 알아온다.

 

                /*
                 * Adjust freesize so that it accounts for how much memory
                 * is used by this zone for memmap. This affects the watermark
                 * and per-cpu initialisations
                 */
                memmap_pages = calc_memmap_size(size, realsize);
                if (!is_highmem_idx(j)) {
                        if (freesize >= memmap_pages) {
                                freesize -= memmap_pages;
                                if (memmap_pages)
                                        printk(KERN_DEBUG
                                               "  %s zone: %lu pages used for memmap\n",
                                               zone_names[j], memmap_pages);
                        } else
                                printk(KERN_WARNING
                                        "  %s zone: %lu pages exceeds freesize %lu\n",
                                        zone_names[j], memmap_pages, freesize);
                }

                /* Account for reserved pages */
                if (j == 0 && freesize > dma_reserve) {
                        freesize -= dma_reserve;
                        printk(KERN_DEBUG "  %s zone: %lu pages reserved\n",
                                        zone_names[0], dma_reserve);
                }

                if (!is_highmem_idx(j))
                        nr_kernel_pages += freesize;
                /* Charge for highmem memmap if there are enough kernel pages */
                else if (nr_kernel_pages > memmap_pages * 2)
                        nr_kernel_pages -= memmap_pages;
                nr_all_pages += freesize;

                zone->spanned_pages = size;
                zone->present_pages = realsize;
  • memmap_pages = calc_memmap_size(size, realsize);
    • page 구조체 배열이 들어갈 페이지 수를 구한다.
  • if (!is_highmem_idx(j)) {
    • j zone이 highmem에 있지 않은 경우
  • if (freesize >= memmap_pages) { freesize -= memmap_pages;
    • freesize에서 memmap에 사용될 pages 수 만큼을 감소시킨다.
  • if (j == 0 && freesize > dma_reserve) { freesize -= dma_reserve;
    • freesize에서 dma_reserve에 사용될 pages 수 만큼을 감소시킨다.
  • if (!is_highmem_idx(j)) nr_kernel_pages += freesize;
    • j zone이 highmem에 없으면 전역 변수 nr_kernel_pages 에 freesize를 추가한다.
  • else if (nr_kernel_pages > memmap_pages * 2) nr_kernel_pages -= memmap_pages;
    • nr_kernel_pages가 memmap_pages * 2 보다 큰 경우 nr_kernel_pages에서 memmap_pages 만큼 줄인다.
    • nr_kernel_pages와 nr_all_pages 값은 각 zone의 워터마크를 설정할 때 사용된다.
    • nr_kernel_pages=
      • lowmem pages – mem_map pages(highmem 제외) – dma_reserve
        • 조건: highmem memmap 비중이 lowmem의 절반 이하일 때 – mem_map pages(highmem)
    • nr_all_pages=
      • lowmem pages – mem_map pages(highmem 제외) – dma_reserve + highmem pages
    • 두 개의 변수는 alloc_large_system_hash()에서 해쉬의 크기를 결정할 때 엔트리 크기가 지정되지 않을 경우 메모리의 크기에 비례하여 만들기 위해 사용된다.
      • 예) uhash_entries=, ihash_entries=, dhash_entries=, mhash_entries=
  • nr_all_pages += freesize;
    • 전역 변수 nr_all_pages에 freesize를 추가한다.

 

                /*
                 * Set an approximate value for lowmem here, it will be adjusted
                 * when the bootmem allocator frees pages into the buddy system.
                 * And all highmem pages will be managed by the buddy system.
                 */
                zone->managed_pages = is_highmem_idx(j) ? realsize : freesize;
#ifdef CONFIG_NUMA
                zone->node = nid;
                zone->min_unmapped_pages = (freesize*sysctl_min_unmapped_ratio)
                                                / 100;
                zone->min_slab_pages = (freesize * sysctl_min_slab_ratio) / 100;
#endif
                zone->name = zone_names[j];
                spin_lock_init(&zone->lock);
                spin_lock_init(&zone->lru_lock);
                zone_seqlock_init(zone);
                zone->zone_pgdat = pgdat;
                zone_pcp_init(zone);

                /* For bootup, initialized properly in watermark setup */
                mod_zone_page_state(zone, NR_ALLOC_BATCH, zone->managed_pages);

                lruvec_init(&zone->lruvec);
                if (!size)
                        continue;

                set_pageblock_order();
                setup_usemap(pgdat, zone, zone_start_pfn, size);
                ret = init_currently_empty_zone(zone, zone_start_pfn,
                                                size, MEMMAP_EARLY);
                BUG_ON(ret);
                memmap_init(size, nid, j, zone_start_pfn);
                zone_start_pfn += size;
        }
}
  • zone->managed_pages = is_highmem_idx(j) ? realsize : freesize;
    • 현재 zone이 highmem인 경우 managed_pages에 realsize를 highmem이 아니면 freesize를 담는다.
    • zone->managed_pages:
      • – ZONE_HIGHMEM: realsize(실제 페이지 수)
      • – 기타 ZONE: freesize(실제 페이지 수 – 메타데이터(memmap, dma_reserve)
  • zone->min_unmapped_pages = (freesize*sysctl_min_unmapped_ratio) / 100;
    • freesize의 sysctl_min_unmapped_ratio(1%) 만큼 배정한다.
  • zone->min_slab_pages = (freesize * sysctl_min_slab_ratio) / 100;
    • freesize의 sysctl_min_slab_ratio(5%) 만큼 배정한다.
  • zone_pcp_init(zone);
    • 버디 시스템에서 0 order 할당 요청에 대응하여 동작하는 Per-CPU Page Frame Cache (zone->pageset)를 초기화한다.
      • zone->pageset에 boot_pageset per-cpu 데이터의 주소를 대입한다
    • pcp는 0 order 할당에 대한 속도를 향상시키기 위해 각 cpu에서 동작한다.
  • mod_zone_page_state(zone, NR_ALLOC_BATCH, zone->managed_pages);
    • 지정된 zone의 NR_ALLOC_BATCH 통계 항목에 managed_pages를 추가(SMP에서 atomic 하게)한다.
  • lruvec_init(&zone->lruvec);
    • zone->lruvec 구조체를 초기화 한다.
  • set_pageblock_order();
    • 다음 조건에서 전역 변수 pageblock_order를 설정한다.
      • LPAE를 지원하는 32bit ARM에서 CONFIG_HUGETLB_PAGE 및 CONFIG_HUGETLB_PAGE_SIZE_VARIABLE 커널 옵션을 사용하는 경우 아키텍처에 따라 HUGETLB_PAGE_ORDER(9) 또는 MAX_ORDER(11)-1 값으로 지정할 수 있다.
    • pageblock_order는 기본적으로 compile 타임에 설정된다..
      • CONFIG_HUGETLB_PAGE 가 설정된 경우 HUGETLB_PAGE_ORDER(9) 값으로 compile 타임에 설정된다.
      • 그렇지 않은 경우 MAX_ORDER(11)-1 값으로 설정된다.
  • setup_usemap(pgdat, zone, zone_start_pfn, size);
    • Sparse 메모리 모델을 사용하지 않을 때 zone별로 usemap을 memblock에 할당 받는다.
  • ret = init_currently_empty_zone(zone, zone_start_pfn, size, MEMMAP_EARLY);
    • wait table에 대한 해시 엔트리 수를 결정하고 관련 메모리를 할당 받은 후 초기화(waitqueue 및 spinlock) 하고 버디시스템에서 사용하는 free_area[].free_list[]를 초기화한다.
  • memmap_init(size, nid, j, zone_start_pfn);
    • mem_map의 각 멤버를 초기화한다.
  • zone_start_pfn += size;
    • zone 시작 pfn 값에 size를 더한다.

 

다음 그림은 각 zone의 managed_pages와 전역 nr_kernel_pages 및 nr_all_pages를 한눈에 알아볼 수 있도록 도식화하였다

free_area_init_nodes-2a

다음 그림은 위에서 산출한 값을 실제 연산과정으로 살펴보았다.

free_area_init_nodes-1b

 

다음은 ZONE_NORMAL만 있는 경우의 예(rpi2)로 산출한 사례이다.

 

calc_memmap_size()

mm/page_alloc.c

static unsigned long __paginginit calc_memmap_size(unsigned long spanned_pages,
                                                   unsigned long present_pages)
{
        unsigned long pages = spanned_pages;

        /*
         * Provide a more accurate estimation if there are holes within
         * the zone and SPARSEMEM is in use. If there are holes within the
         * zone, each populated memory region may cost us one or two extra
         * memmap pages due to alignment because memmap pages for each
         * populated regions may not naturally algined on page boundary.
         * So the (present_pages >> 4) heuristic is a tradeoff for that.
         */
        if (spanned_pages > present_pages + (present_pages >> 4) &&
            IS_ENABLED(CONFIG_SPARSEMEM))
                pages = present_pages;

        return PAGE_ALIGN(pages * sizeof(struct page)) >> PAGE_SHIFT;
}
  • spanned size 수 만큼 page 구조체 배열이 들어갈 페이지 수를 구한다.
  • 다만 CONFIG_SPARSEMEM 커널 옵션을 사용하는 경우에는 spanned size가 real size 수의 125% 만큼 보다 큰 경우 spanned size 대신 real size를 대신 사용한다.

 

zone_pcp_init()

mm/page_alloc.c

static __meminit void zone_pcp_init(struct zone *zone)
{
        /*
         * per cpu subsystem is not up at this point. The following code
         * relies on the ability of the linker to provide the
         * offset of a (static) per cpu variable into the per cpu area.
         */
        zone->pageset = &boot_pageset;

        if (populated_zone(zone))
                printk(KERN_DEBUG "  %s zone: %lu pages, LIFO batch:%u\n",
                        zone->name, zone->present_pages,
                                         zone_batchsize(zone));
}
  • zone->pageset = &boot_pageset;
    • zone->pageset에 boot_pageset per-cpu 데이터의 주소를 대입한다.

 

lruvec_init()

mm/mmzone.c

void lruvec_init(struct lruvec *lruvec)
{
        enum lru_list lru;

        memset(lruvec, 0, sizeof(struct lruvec));

        for_each_lru(lru)
                INIT_LIST_HEAD(&lruvec->lists[lru]);
}
  • memset(lruvec, 0, sizeof(struct lruvec));
  • for_each_lru(lru) INIT_LIST_HEAD(&lruvec->lists[lru]);
    • lists를 초기화한다.

 

setup_usemap()

이 함수는 SPARSEMEM에서는 아무것도 수행하지 않고 그렇지 않은 다음의 구현을 사용한다.

mm/page_alloc.c

static void __init setup_usemap(struct pglist_data *pgdat,
                                struct zone *zone,
                                unsigned long zone_start_pfn,
                                unsigned long zonesize)
{
        unsigned long usemapsize = usemap_size(zone_start_pfn, zonesize);
        zone->pageblock_flags = NULL;
        if (usemapsize)
                zone->pageblock_flags =
                        memblock_virt_alloc_node_nopanic(usemapsize,
                                                         pgdat->node_id);
}

usemap을 할당받고 지정한다.

  • unsigned long usemapsize = usemap_size(zone_start_pfn, zonesize);
    • usemap에 사용하는 사이즈(바이트)
  • zone->pageblock_flags = NULL;
  • if (usemapsize) zone->pageblock_flags = memblock_virt_alloc_node_nopanic(usemapsize, pgdat->node_id);
    • usemapsize 만큼 memblock을 할당 받는다.

 

usemap_size()

SPARSE 메모리 모델을 사용하는 경우와 아닌 경우 두 가지 구현이 있다.

mm/page_alloc.c

/*
 * Calculate the size of the zone->blockflags rounded to an unsigned long
 * Start by making sure zonesize is a multiple of pageblock_order by rounding
 * up. Then use 1 NR_PAGEBLOCK_BITS worth of bits per pageblock, finally
 * round what is now in bits to nearest long in bits, then return it in
 * bytes.
 */
static unsigned long __init usemap_size(unsigned long zone_start_pfn, unsigned long zonesize)
{
        unsigned long usemapsize;

        zonesize += zone_start_pfn & (pageblock_nr_pages-1);
        usemapsize = roundup(zonesize, pageblock_nr_pages);
        usemapsize = usemapsize >> pageblock_order;
        usemapsize *= NR_PAGEBLOCK_BITS;
        usemapsize = roundup(usemapsize, 8 * sizeof(unsigned long));

        return usemapsize / 8; 
}
  • SPARSEMEM이 아닌 경우이다.
  • zonesize += zone_start_pfn & (pageblock_nr_pages-1);
    • pageblock_nr_pages
      • 32bit ARM 기준으로 2^10=1K
    • zonesize에 zone_start_pfn의 lsb 10bit만 더한다.
  • usemapsize = roundup(zonesize, pageblock_nr_pages);
    • zonesize는 1K 단위로 round up 한다.
  • usemapsize = usemapsize >> pageblock_order;
    • usermapsize를 우측으로 10bit 쉬프트한다.
  • usemapsize *= NR_PAGEBLOCK_BITS;
    • usermapsize를 NR_PAGEBLOCK_BITS(4) 만큼 곱한다.
  • usemapsize = roundup(usemapsize, 8 * sizeof(unsigned long));
    • usermapsize를 32 단위로 round up 한다.
  • return usemapsize / 8;
    • usermapsize를 8로 나눈 수를 리턴한다.

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;
}
  • SPARSEMEM에서 SECTION_BLOCKFLAGS_BITS
    • ((1UL << (PFN_SECTION_SHIFT – pageblock_order)) * NR_PAGEBLOCK_BITS)
    • 아키텍처에 따라 달라서 SECTION_SIZE_BITS=28을 사용하는 Realview PBX 보드를 사용하는 것으로 한다.
      • 256MB @ 0x00000000 -> PAGE_OFFSET
      • 512MB @ 0x20000000 -> PAGE_OFFSET + 0x10000000
      • 256MB @ 0x80000000 -> PAGE_OFFSET + 0x30000000
      • PFN_SECTION_SHIFT=(SECTION_SIZE_BITS(28) – PAGE_SHIFT(12))=16
      • pageblock_order=(MAX_ORDER(11)-1)=10
      • NR_PAGEBLOCK_BITS(4)
    • =24
  • size_bytes = roundup(SECTION_BLOCKFLAGS_BITS, 8) / 8;
    • Realview PBX 보드의 경우 size_bytes=3
  • size_bytes = roundup(size_bytes, sizeof(unsigned long));
    • Realview PBX 보드의 경우 4 byte round up 하여 size_bytes=4

 

init_currently_empty_zone()

mm/page_alloc.c

int __meminit init_currently_empty_zone(struct zone *zone,
                                        unsigned long zone_start_pfn,
                                        unsigned long size,
                                        enum memmap_context context)
{
        struct pglist_data *pgdat = zone->zone_pgdat;
        int ret;
        ret = zone_wait_table_init(zone, size);
        if (ret)
                return ret;
        pgdat->nr_zones = zone_idx(zone) + 1;

        zone->zone_start_pfn = zone_start_pfn;

        mminit_dprintk(MMINIT_TRACE, "memmap_init",
                        "Initialising map node %d zone %lu pfns %lu -> %lu\n",
                        pgdat->node_id,
                        (unsigned long)zone_idx(zone),
                        zone_start_pfn, (zone_start_pfn + size));

        zone_init_free_lists(zone);

        return 0;
}
  • ret = zone_wait_table_init(zone, size);
    • 해쉬를 사용하는 wait queue table의 할당을 받아 초기화 한다.
  • pgdat->nr_zones = zone_idx(zone) + 1;
    • 현재 노드의 zone 수에 현재 zone 인덱스 + 1을 하여 저장한다.
  • zone->zone_start_pfn = zone_start_pfn;
    • 현재 zone에 zone_start_pfn 값을 저장한다.
  • zone_init_free_lists(zone);
    • zone을 관리하는 free_area[].free_list[]를 초기화 한다.

 

zone_wait_table_init()

mm/page_alloc.c

static noinline __init_refok
int zone_wait_table_init(struct zone *zone, unsigned long zone_size_pages)
{
        int i;
        size_t alloc_size;

        /*
         * The per-page waitqueue mechanism uses hashed waitqueues
         * per zone.
         */
        zone->wait_table_hash_nr_entries =
                 wait_table_hash_nr_entries(zone_size_pages);
        zone->wait_table_bits =
                wait_table_bits(zone->wait_table_hash_nr_entries);
        alloc_size = zone->wait_table_hash_nr_entries
                                        * sizeof(wait_queue_head_t);

        if (!slab_is_available()) {
                zone->wait_table = (wait_queue_head_t *)
                        memblock_virt_alloc_node_nopanic(
                                alloc_size, zone->zone_pgdat->node_id);
        } else {
                /*
                 * This case means that a zone whose size was 0 gets new memory
                 * via memory hot-add.
                 * But it may be the case that a new node was hot-added.  In
                 * this case vmalloc() will not be able to use this new node's
                 * memory - this wait_table must be initialized to use this new
                 * node itself as well.
                 * To use this new node's memory, further consideration will be
                 * necessary.
                 */
                zone->wait_table = vmalloc(alloc_size);
        }
        if (!zone->wait_table)
                return -ENOMEM;

        for (i = 0; i < zone->wait_table_hash_nr_entries; ++i)
                init_waitqueue_head(zone->wait_table + i);

        return 0;
}

해쉬를 사용하는 wait queue table의 할당을 받아 초기화 한다.

  • zone->wait_table_hash_nr_entries = wait_table_hash_nr_entries(zone_size_pages);
    • wait_table_hash_nr_entries를 산정한다.
    • 예) 400 -> 512
      • 예) 32bit ARM은 4K 페이지, zone_size_pages=102400 (400M bytes)
        • 해쉬 수=512
  • zone->wait_table_bits = wait_table_bits(zone->wait_table_hash_nr_entries);
    • wait_queue_table_bits를 산정한다.
      • 예) wait_table_hash_nr_entries=512
        • bits=9
  •   alloc_size = zone->wait_table_hash_nr_entries * sizeof(wait_queue_head_t);
    • 해쉬 엔트리와 그 구조체 크기만큼 곱하여 할당 사이즈를 구한다.
  • if (!slab_is_available()) { zone->wait_table = (wait_queue_head_t *)
    memblock_virt_alloc_node_nopanic(alloc_size, zone->zone_pgdat->node_id);

    • slab 메모리 할당자가 동작하지 않는 경우 memblock을 사용하여 할당을 한다.
  • zone->wait_table = vmalloc(alloc_size);
    • slab 메모리 할당자가 동작하는 경우 vmalloc() 함수를 사용하여 할당을 한다.
  • for (i = 0; i < zone->wait_table_hash_nr_entries; ++i) init_waitqueue_head(zone->wait_table + i);
    • 해쉬 수 만큼 waitqueue를 초기화한다.
      • wait_table(wait_queue_head_t)의 멤버 변수 lock에 대해 spin lock 초기화와 task_list(list_head)에 대한 초기화를 수행한다.

 

wait_table_hash_nr_entries()

mm/page_alloc.c

#ifndef CONFIG_MEMORY_HOTPLUG
static inline unsigned long wait_table_hash_nr_entries(unsigned long pages)
{
        unsigned long size = 1;

        pages /= PAGES_PER_WAITQUEUE;

        while (size < pages)
                size <<= 1;

        /*
         * Once we have dozens or even hundreds of threads sleeping
         * on IO we've got bigger problems than wait queue collision.
         * Limit the size of the wait table to a reasonable size.
         */
        size = min(size, 4096UL);

        return max(size, 4UL);
}
#else
/*
 * A zone's size might be changed by hot-add, so it is not possible to determine
 * a suitable size for its wait_table.  So we use the maximum size now.
 *
 * The max wait table size = 4096 x sizeof(wait_queue_head_t).   ie:
 *
 *    i386 (preemption config)    : 4096 x 16 = 64Kbyte.
 *    ia64, x86-64 (no preemption): 4096 x 20 = 80Kbyte.
 *    ia64, x86-64 (preemption)   : 4096 x 24 = 96Kbyte.
 *
 * The maximum entries are prepared when a zone's memory is (512K + 256) pages
 * or more by the traditional way. (See above).  It equals:
 *
 *    i386, x86-64, powerpc(4K page size) : =  ( 2G + 1M)byte.
 *    ia64(16K page size)                 : =  ( 8G + 4M)byte.
 *    powerpc (64K page size)             : =  (32G +16M)byte.
 */
static inline unsigned long wait_table_hash_nr_entries(unsigned long pages)
{
        return 4096UL;
}
#endif

이 함수는 waitqueue hash table의 수를 4~4096 범위(32bit ARM: 4M~4G 관리)내에서 구해오는데  CONFIG_MEMORY_HOTPLUG 선언이 안된 경우 수행되며 옵션이 사용되는 경우에는 항상 4096을 리턴한다.

  • pages /= PAGES_PER_WAITQUEUE;
    • pages를 PAGES_PER_WAITQUEUE(256) 만큼 나눈다.
  • while (size < pages) size <<= 1;
    • pages가 size보다 큰 동안 루프를 돌며 size를 2배씩 증가시킨다.
    • size를 2의 차수 단위에서 pages 보다 큰 수를 찾는다.
      • 예) pages=400 -> size=512
  • size = min(size, 4096UL);
    • size가 4096을 넘지 않게 한다.
  • return max(size, 4UL);
    • size가 최소 4 이상 되게 한다.
  • 예) pages=102400(400M)
    • size=512

 

wait_table_bits()

mm/page_alloc.c

/*
 * This is an integer logarithm so that shifts can be used later
 * to extract the more random high bits from the multiplicative
 * hash function before the remainder is taken.
 */
static inline unsigned long wait_table_bits(unsigned long size)
{
        return ffz(~size);
}
  • return ffz(~size);
    •  ffz(x)
      • lsb 부터 msb 방향으로 clear(0) 되어 있는 첫 비트의 위치(0~31)를 리턴한다.  x 값이 0xffff_ffff 즉,  zero bit가 없으면 -1을 리턴한다.
    • size 값에서 lsb 부터 msb 방향으로 set(1) 되어 있는 첫 비트의 위치(0~31)를 리턴한다.

 

zone_init_free_lists()

mm/page_alloc.c

static void __meminit zone_init_free_lists(struct zone *zone)
{
        unsigned int order, t;
        for_each_migratetype_order(order, t) {
                INIT_LIST_HEAD(&zone->free_area[order].free_list[t]);
                zone->free_area[order].nr_free = 0;
        }
}
  • zone 멤버 free_area[]의 초기화를 수행한다.

 

memmap_init()

mm/page_alloc.c

#ifndef __HAVE_ARCH_MEMMAP_INIT
#define memmap_init(size, nid, zone, start_pfn) \
        memmap_init_zone((size), (nid), (zone), (start_pfn), MEMMAP_EARLY)
#endif

 

memmap_init_zone()

mm/page_alloc.c

/*
 * Initially all pages are reserved - free ones are freed
 * up by free_all_bootmem() once the early boot process is
 * done. Non-atomic initialization, single-pass.
 */
void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
                unsigned long start_pfn, enum memmap_context context)
{
        struct page *page;
        unsigned long end_pfn = start_pfn + size;
        unsigned long pfn;
        struct zone *z;

        if (highest_memmap_pfn < end_pfn - 1)
                highest_memmap_pfn = end_pfn - 1;

        z = &NODE_DATA(nid)->node_zones[zone];
        for (pfn = start_pfn; pfn < end_pfn; pfn++) {
                /*
                 * There can be holes in boot-time mem_map[]s
                 * handed to this function.  They do not
                 * exist on hotplugged memory.
                 */
                if (context == MEMMAP_EARLY) {
                        if (!early_pfn_valid(pfn))
                                continue;
                        if (!early_pfn_in_nid(pfn, nid))
                                continue;
                }
                page = pfn_to_page(pfn);
                set_page_links(page, zone, nid, pfn);
                mminit_verify_page_links(page, zone, nid, pfn);
                init_page_count(page);
                page_mapcount_reset(page);
                page_cpupid_reset_last(page);
                SetPageReserved(page);

 

                /*
                 * Mark the block movable so that blocks are reserved for
                 * movable at startup. This will force kernel allocations
                 * to reserve their blocks rather than leaking throughout
                 * the address space during boot when many long-lived
                 * kernel allocations are made. Later some blocks near
                 * the start are marked MIGRATE_RESERVE by
                 * setup_zone_migrate_reserve()
                 *
                 * bitmap is created for zone's valid pfn range. but memmap
                 * can be created for invalid pages (for alignment)
                 * check here not to call set_pageblock_migratetype() against
                 * pfn out of zone.
                 */
                if ((z->zone_start_pfn <= pfn)
                    && (pfn < zone_end_pfn(z))
                    && !(pfn & (pageblock_nr_pages - 1)))
                        set_pageblock_migratetype(page, MIGRATE_MOVABLE);

                INIT_LIST_HEAD(&page->lru);
#ifdef WANT_PAGE_VIRTUAL
                /* The shift won't overflow because ZONE_NORMAL is below 4G. */
                if (!is_highmem_idx(zone))
                        set_page_address(page, __va(pfn << PAGE_SHIFT));
#endif
        }
}
  • if (highest_memmap_pfn < end_pfn – 1) highest_memmap_pfn = end_pfn – 1;
    • highest_memmap_pfn이 end_pfn-1을 초과하지 않도록 한다.
  • z = &NODE_DATA(nid)->node_zones[zone];
    • 인수로 지정된 노드와 zone으로 zone 정보를 얻어온다.
  • for (pfn = start_pfn; pfn < end_pfn; pfn++) {
    • 시작 pfn 부터 끝 pfn 까지 루프를 돈다.
  • if (context == MEMMAP_EARLY) {
    • 인수 context가 MEMMAP_EARLY(0) 인 경우
  • if (!early_pfn_valid(pfn)) continue;
    • pfn에 해당하는 페이지가 유효하지 않은 경우 continue.
      • SPARSEMEM의 경우 메모리 사이에 hole이 있을 수 있다.
  • if (!early_pfn_in_nid(pfn, nid))
    • pfn이 해당 노드에 없으면 continue
    • early_pfn_in_nid()
      • CONFIG_NODES_SPAN_OTHER_NODES 커널 옵션에 따라 early_pfn_in_nid() 함수 구현은
        • 사용하지 않는 경우  함수는 항상 true이다.
        • 사용하는 경우 해당 pfn이 지정된 노드에 있는지 여부를 알아온다.
  • page = pfn_to_page(pfn);
    • pfn 번호로 page 구조체를 알아온다.
  • set_page_links(page, zone, nid, pfn);
    • page->flags 정보에 zone, nid, 섹션 정보를 설정한다.
  • mminit_verify_page_links(page, zone, nid, pfn);
    • page->flasgs 정보에 zone, nid, 섹션 정보가 잘 기록되었는지 확인하는 디버그 코드이다.
  • init_page_count(page);
    • page->_count를 1로 초기화한다.
  • page_mapcount_reset(page);
    • page->_mapcount를 -1로 초기화한다.
  • page_cpupid_reset_last(page);
    • page->_last_cpuid = -1 & LAST_CPUID_MASK(메모리 모델마다 다름)
      • _last_cpuid에 msb 부터 LAST_CPUID  bit 자리 까지 0이고 나머지 lsb까지 1로 설정된다.
  • SetPageReserved(page);
    • page->flags의 PG_reserved 비트를 atomic하게 설정한다.
  • if ((z->zone_start_pfn <= pfn) && (pfn < zone_end_pfn(z)) && !(pfn & (pageblock_nr_pages – 1))) set_pageblock_migratetype(page, MIGRATE_MOVABLE);
    • pfn이 zone 범위안에 포함되고 pfn이 1K 단위 align이 되어 있는 경우 해당 페이지의 이주 속성에 MIGRATE_MOVABLE 속성을 설정한다.
  • INIT_LIST_HEAD(&page->lru);
    • page->lru 리스트를 초기화한다.
  • if (!is_highmem_idx(zone)) set_page_address(page, __va(pfn << PAGE_SHIFT));
    • zone이 highmem이 아니면 page->virtual에  pfn을 가상주소로 바꾼 주소로 설정한다.

 

참고

 

Memory Model – (1)

3가지 메모리 모델

리눅스는 FLATMEM, DISCONTIGMEM 및 SPARSEMEM의 3가지 모델을 제공하며 아키텍처 머신들은 이 중 하나 이상을 지원하며 커널 빌드 시 지원되는 메모리 모델 중 하나를 선택하여 빌드한다.

mm-1b

 

메모리 모델별 특징

FLATMEM

  • 설치된 메모리 뱅크들의 주소가 연속된다.
  • UMA에서만 사용

 

DISCONTIGMEM

  • 설치된 메모리 뱅크들의 간격이 발생된다. 홀(hole) 발생 가능.
  • UMA 및 NUMA에서 사용 가능
  • ARM은 2010년 이 옵션을 삭제하였다.

 

SPARSEMEM

  • 설치된 메모리 뱅크들의 간격이 발생된다. 홀(hole) 발생 가능.
  • DISCONTIGMEM 보다 성능이 떨어질 수 있지만 hotplug memory 기능이 지원된다.
    • 그러나 특정 아키텍처에 구현된 SPARSEMEM_VMEMMAP을 사용하는 경우 DISCONTIGMEM 보다 더 빠르고 FLATMEM에 필적하는 성능을 보여 64비트 시스템에서는 점점 Sparsemem으로 이전하고 있음.
    • 참고: x86_64: Make sparsemem/vmemmap the default memory model
  • 섹션 단위로 online/offline(hotplug memory) 관리가 되며 사이즈는 수십MB~수G로 아키텍처별로 다르다.
  • UMA에서 사용하는 경우
    • ARM의 경우 SA1100(xscale 아키텍처), ARCH-RPC 및 일부 ARM 머신(RealView-PBX)에서 두 개의 메모리 뱅크가 떨어져 있어서 default로 메모리 모델로 사용한다.
    • 또한 메인스트림에 적용된 코드는 아니지만 ARM에서 MULTI_PLATFORM 커널 옵션을 사용하는 경우에도 사용할 수 있도록 패치가 준비되어 있다.

 

ARM with NUMA

  • arm 및 arm64 모두 현재 커널에서는 지원되지 않는다.
  • 2012년 RFC 패치에 arm에서 NUMA를 지원하였었고, linaro 커널도 arm에서 NUMA를 지원하지만 절대적인 필요성이 없어서인지 메인스트림에는 포함되지 않고 있다.
  • 2014년 패치에 NUMA를 지원하는 arm64가 나왔다.
mm-2a

메모리 모델 주요 커널 옵션

mm-4

메모리 모델 부가 커널 옵션

mm-5
  • NEED_MULTIPLE_NODES
    • DISCONTIGMEM 메모리 모델을 사용하거나 NUMA 시스템인 경우 사용할 수 있다.
    • NODE_DATA() 매크로를 사용하여 각 노드의 mem_map을 가리킬 수 있게 한다.
    • 전역 node_data[] 배열을 사용하여 노드별로 mem_map에 접근한다.
  •  FLAT_NODE_MEM_MAP
    • FLATMEM 또는 DISCONTIGMEM 메모리 모델에서 사용할 수 있다.
    • 노드 정보를 기술해 놓은 pglist_data 구조체의 멤버 변수 node_mem_map을 통해 mem_map에 접근한다.
  • HAVE_MEMORY_PRESENT
    • SPARSEMEM 메모리 모델을 위해 요청한 범위에 hole이 발생할 수 있으므로 각 섹션 별로 메모리 존재 여부를 관리할 수 있도록 한다.
  • SPARSEMEM_EXTREME
    • mem_section[] 배열을 dynamic 하게 할당 받아 사용하려고 할 때 사용
    • 주로 생성해야 할 섹션 수가 많은 시스템에서 사용한다.
  • SPARSEMEM_STATIC
    • 컴파일 시 static하게 할당해둔 mem_section[]을 사용한다.
    • 주로 생성해야 할 섹션 수가 적은 시스템에서 사용한다.
  • SPARSEMEM_VMEMMAP
    • vmemmap을 사용하여 빠르게 pfn과 page descriptor 주소를 변환할 수 있다.
    • vmalloc 주소 범위가 큰 64비트 시스템에서 사용한다.
  • MEMORY_HOTREMOVE
    • 메모리를 시스템 동작 중에 제거할 수 있다.
    • MEMORY_HOTPLUG, ARCH_ENABLE_MEMORY_HOTREMOVE, MIGRATION 옵션이 있을 때 선택할 수 있다.
  • MOVABLE_NODE
    • ◦X86_64 누마 시스템에서만 사용가능하며 메모리 단편화 지원을 위해 사용한다.
    • 한 노드를 movable 메모리로만 사용할 수 있게 할당할 수 있다.
    • HAVE_MEMBLOCK, NO_BOOTMEM 커널 옵션과 같이 사용된다.
  • MEMORY_HOTPLUG_SPARSE
    • SPARSEMEM && MEMORY_HOTPLUG에서 선택할 수 있다.
  • NO_BOOTMEM
    • 기존 커널에서 사용하였던 BOOTMEM early 메모리 할당자를 사용하지 않는다.
    • ARM은 기본적으로 BOOTMEM을 사용하지 않고 memblock으로 대체되었다. 대부분의 아키텍처에서도 점점 사용하지 않는 추세이다.
  • HAVE_MEMBLOCK
    • memblock early 메모리 할당자를 사용한다.
    • ARM은 기본적으로 사용하고 대부분의 아키텍처도 BOOTMEM 대신 early 메모리 할당자로 사용한다.
  • HIGHMEM
    • 32bit에서 커널에 미리 1:1 매핑되지 않아 커널이 직접 access를 해야 할 때마다 매핑하여 사용해야 하는 메모리 범위이다.
    • 32bit 시스템에서 시스템 메모리가 커널 공간보다 큰 경우 HIGHMEM 옵션을 사용하여 더 많은 메모리를 활용할 수 있게 한다.
  • HIGHPTE
    • 32bit 시스템에서 2차 PTE를 highmem에 할당한다.
    • 32bit 시스템에 수 기가 이상의 메모리를 사용하는 경우 2차 PTE를 highmem에 로드하게 하여 lowmem 메모리를 소모하지 않게 유도한다.
  • NODE_NOT_IN_PAGE_FLAGS
    • page 구조체의 flags 멤버변수에 노드 번호를 기록하지 못하는 경우 노드 번호를 별도로 섹션에 대해 1:1로 매핑하여 저장한다
    • 32비트 시스템에서 비트 자리가 부족하여 노드 번호를 기록하지 못하는 경우 사용

메모리 모델과 페이지 관리맵(mem_map)

전체 물리 메모리의 페이지 프레임 만큼 page 구조체 배열이 mem_map이라하는데 다음과 같이 각 메모리 모델별로 mem_map을 관리한다.
  • FLATMEM
    • *mem_map 포인터가 page[] 구조체 배열을 가리킨다.
    • 노드가 하나 이므로 전역 구조체 contig_page_data를 사용하는데 이 구조체의 멤버 변수 node_mem_map 역시 page[] 구조체 배열을 가리킨다.
  • DISCONTIGMEM
    • 노드가 두 개 이상이므로 node_data[] 배열을 사용하는데 이 구조체의 멤버 변수 node_mem_map은 각 노드와 관련된 page[] 구조체 배열을 가리킨다.
  • SPARSEMEM
    • 두 가지 방법을 사용한다.
      • SPARSEMEM_STATIC
        • 섹션 수 만큼 mem_section[] 배열을 컴파일 타임에 정적 메모리에 두고 각 섹션 엔트리의 멤버 변수 section_mem_map이 해당 섹션 크기 만큼의 페이지를 관리하는 page[] 구조체 배열을 가리킨다.
      • SPARSEMEM_EXTREAM
        • 섹션 수 만큼 *mem_section[] 포인터 배열을 정적 메모리에 두고 실제 각 mem_section[] 배열은 커널 초기화를 진행할 때 mem_section[] 배열을 dynamic하게 할당받아 메모리를 사용하고 mem_section[]의 사용은 SPARSEMEM_STATIC과 같다.
mm-3b

참고

 

bootmem_init()

memory memblock으로 부터 lowmem 영역의 경계를 파악하여 각각의 zone으로 경계를 나누어 설정하고 0번 노드의 빈 페이지들을 초기화한다. 또한 Sparse 메모리 모델을 지원하는 경우에는 sparse memory의 초기화도 수행한다.

bootmem_init-2a

 

bootmem_init()

arch/arm/mm/init.c

void __init bootmem_init(void)
{
        unsigned long min, max_low, max_high;

        memblock_allow_resize();
        max_low = max_high = 0;

        find_limits(&min, &max_low, &max_high);

        /*
         * Sparsemem tries to allocate bootmem in memory_present(),
         * so must be done after the fixed reservations
         */
        arm_memory_present();

        /*
         * sparse_init() needs the bootmem allocator up and running.
         */
        sparse_init();

        /*
         * Now free the memory - free_area_init_node needs
         * the sparse mem_map arrays initialized by sparse_init()
         * for memmap_init_zone(), otherwise all PFNs are invalid.
         */
        zone_sizes_init(min, max_low, max_high);

        /*
         * This doesn't seem to be used by the Linux memory manager any
         * more, but is used by ll_rw_block.  If we can get rid of it, we
         * also get rid of some of the stuff above as well.
         */
        min_low_pfn = min;
        max_low_pfn = max_low;
        max_pfn = max_high;
}
  • memblock_allow_resize();
    • memblock_can_resize = 1로 만들어 향후 memblock 관리 영역이 모자랄 때 2배 단위로 커질 수 있도록 한다.
  • find_limits(&min, &max_low, &max_high);
    • memblock 정보로 max_pfn, max_low_pfn, min_low_pfn 값을 얻어온다.
  • arm_memory_present();
    • Sparsemem의 경우 내부에서 mem_section[] 매핑을 위한 allocation이 수행되기 때문에 반드시 fixed reservation이 끝난 후에 이 함수가 호출되어야 한다.
    • 몇 개 32bit ARM 머신을 제외하고 대부분의 32bit ARM에서는 CONFIG_SPARSEMEM 옵션을 사용하지 않는다.
    • 참고: Sparse Memory | 문c
  • sparse_init();
    • Sparse memory 모델을 사용하는 시스템을 위해 관리 영역을 할당받고 매핑 초기화한다.
    • 참고: Sparse Memory | 문c
  • zone_sizes_init(min, max_low, max_high);
    • zone  영역을 나누고 초기화한다.

아래와 같이 memblock 설정 값을 읽어 lowmem 영역의 경계를 나눈다. 이 값은 zone을 나누는 경계로도 사용된다.

bootmem_init-1b

 

memblock_allow_resize()

mm/memblock.c

void __init memblock_allow_resize(void)
{
        memblock_can_resize = 1;
}
  • memblock_can_resize 플래그를 enable 시켜 향후 memblock 관리 영역이 모자랄 때 2배 단위로 커질 수 있도록 한다.

 

find_limits()

arch/arm/mm/init.c

static void __init find_limits(unsigned long *min, unsigned long *max_low,
                               unsigned long *max_high)
{
        *max_low = PFN_DOWN(memblock_get_current_limit());
        *min = PFN_UP(memblock_start_of_DRAM());
        *max_high = PFN_DOWN(memblock_end_of_DRAM());
}
  • memblock 정보를 읽어 다음을 설정한다.
  • max_low
    • lowmem/highmem 영역의 경계 pfn
  • min
    • 메모리 영역의 최하 pfn
  • max_high
    • 메모리 영역의 최상 pfn

 

zone_sizes_init()

arch/arm/mm/init.c

static void __init zone_sizes_init(unsigned long min, unsigned long max_low,
        unsigned long max_high)
{
        unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];
        struct memblock_region *reg;

        /*  
         * initialise the zones.
         */
        memset(zone_size, 0, sizeof(zone_size));

        /*  
         * The memory size has already been determined.  If we need
         * to do anything fancy with the allocation of this memory
         * to the zones, now is the time to do it.
         */
        zone_size[0] = max_low - min;
#ifdef CONFIG_HIGHMEM
        zone_size[ZONE_HIGHMEM] = max_high - max_low;
#endif

        /*  
         * Calculate the size of the holes.
         *  holes = node_size - sum(bank_sizes)
         */
        memcpy(zhole_size, zone_size, sizeof(zhole_size));
        for_each_memblock(memory, reg) {
                unsigned long start = memblock_region_memory_base_pfn(reg);
                unsigned long end = memblock_region_memory_end_pfn(reg);

                if (start < max_low) {
                        unsigned long low_end = min(end, max_low);
                        zhole_size[0] -= low_end - start;
                }   
#ifdef CONFIG_HIGHMEM
                if (end > max_low) {
                        unsigned long high_start = max(start, max_low);
                        zhole_size[ZONE_HIGHMEM] -= end - high_start;
                }
#endif
        }

#ifdef CONFIG_ZONE_DMA
        /*  
         * Adjust the sizes according to any special requirements for
         * this machine type.
         */
        if (arm_dma_zone_size)
                arm_adjust_dma_zone(zone_size, zhole_size,
                        arm_dma_zone_size >> PAGE_SHIFT);
#endif

        free_area_init_node(0, zone_size, min, zhole_size);
}
  • zone_size[0] = max_low – min;
    • lowmem 영역 사이즈 설정
  • zone_size[ZONE_HIGHMEM] = max_high – max_low;
    • highmem이 동작하는 경우 highmem 영역 사이즈 설정
  • memcpy(zhole_size, zone_size, sizeof(zhole_size));
    • zone_size[]를 zhole_size[]에 복사
  • for_each_memblock(memory, reg) {
    • memory memblock을 루프를 돌며 하나씩 얻어온다.
  • if (start < max_low) {
    • memory memblock이 lowmem 영역에 들어있거나 일부 겹치는 경우
  • unsigned long low_end = min(end, max_low);
    • 끝 주소가 lowmem을 초과하지 않게 한다.
  • zhole_size[0] -= low_end – start;
    • hole 사이즈를 감소시킨다. 이 값이 0이되면 hole이 없는 것이다.
  • highmem 영역에 대해서도 위와 똑같은 방법으로 hole 사이즈를 감소시킨다.
  • if (arm_dma_zone_size)
    • DMA zone 사이즈가 존재하는 경우 dma zone 사이즈만큼 ZONE_DMA에 사이즈를 구성하고 ZONE_NORMAL 영역에서 그 사이즈만큼 뺀다.
  • free_area_init_node(0, zone_size, min, zhole_size);
    • 0번 노드에 zone_size[]와, zhole_size[] 정보를 사용하여 빈 페이지들을 초기화한다.
    • 참고: free_area_init_node() | 문c

아래 그림과 같이 highmem 영역을 사용하는 경우 ZONE_HIGHMEM에 대한 사이즈가 지정되고, 아키텍처가 dma 영역을 별도로 지정하여 사용하는 경우 ZONE_NORMAL에 대한 size를 dma 사이즈 만큼 감소시키고 ZONE_DMA에 대한 사이즈를 dma 사이즈로 지정한다.

  • rpi2: ZONE_NORMAL만 사용한다.

bootmem_init-3b

참고