<kernel v5.0>

page extension

memmap extension 또는 extended memap 이라고도 불린다. page 구조체를 변경하지 않고 page_ext라는 별도의 구조체를 만들어 page 확장을 하여 디버깅에 도움을 줄 수 있게 하였다. 이 기능은 2014년 12월 kernel v3.19-rc1에 LG 전자의 김준수님이 추가하였다.


다음과 같은 기능들이 준비되어 있다.

  • page alloc 디버깅
  • page poisoning 디버그
  • page owner 트래킹
  • idle page 트래킹


다음 그림은 sparse 메모리 모델이 아닌 경우 수행되는 page_ext_init_flatmem() 함수가 동작하여 확장 memmap을 생성하는 과정을 보여준다.


Flat & Discontiguous 메모리 모델에서의 page_ext 초기화

  • 참고로 sparse 메모리 모델에서의 초기화 루틴은 page_ext_init() 함수에서 한다.




void __init page_ext_init_flatmem(void)

        int nid, fail;

        if (!invoke_need_callbacks())

        for_each_online_node(nid)  {
                fail = alloc_node_page_ext(nid);
                if (fail)
                        goto fail;
        pr_info("allocated %ld bytes of page_ext\n", total_usage);

        pr_crit("allocation of page_ext failed.\n");
        panic("Out of memory");

page_ext가 필요한 경우 각 노드에 page_ext를 할당 받고 초기화한다.




static bool __init invoke_need_callbacks(void)
        int i;
        int entries = ARRAY_SIZE(page_ext_ops);
        bool need = false;

        for (i = 0; i < entries; i++) {
                if (page_ext_ops[i]->need && page_ext_ops[i]->need()) {
                        page_ext_ops[i]->offset = sizeof(struct page_ext) +
                        extra_mem += page_ext_ops[i]->size;
                        need = true;

        return need;

page_ext_ops 엔트리 수 만큼 루프를 돌며 엔트리에 등록된 need 함수를 수행시켜 엔트리 하나라도 초기화가 필요한 경우 true를 반환한다.

  • 코드 라인 7~8에서 컴파일 타임에 결정된 page_ext_ops[] 배열 엔트리 수 만큼 순회하며 해당 오퍼레이션에 등록된 (*need) 후크 함수에서 초기화가 필요한 경우 true를 반환하여 조건을 만족시킨다.
  • 코드 라인 9~10에서 해당 오퍼레이션의 offset에는 page_ext 구조체 + 해당 오퍼레이션이 사용할 extra 메모리에 대한 offset 바이트 수를 대입한다.
    • CONFIG_PAGE_OWNER 커널 옵션을 사용하는 경우 extra 메모리로 page_owner 구조체 사이즈를 사용한다.
  • 코드 라인 11에서 오퍼레이션에서 사용한 extra 메모리 사이즈 만큼 전역 변수 extra_mem에 더한다.




static int __init alloc_node_page_ext(int nid)
        struct page_ext *base;
        unsigned long table_size;
        unsigned long nr_pages;

        nr_pages = NODE_DATA(nid)->node_spanned_pages;
        if (!nr_pages)
                return 0;

         * Need extra space if node range is not aligned with
         * MAX_ORDER_NR_PAGES. When page allocator's buddy algorithm
         * checks buddy's status, range could be out of exact node range.
        if (!IS_ALIGNED(node_start_pfn(nid), MAX_ORDER_NR_PAGES) ||
                !IS_ALIGNED(node_end_pfn(nid), MAX_ORDER_NR_PAGES))
                nr_pages += MAX_ORDER_NR_PAGES;

        table_size = get_entry_size() * nr_pages;

        base = memblock_virt_alloc_try_nid_nopanic(
                        table_size, PAGE_SIZE, __pa(MAX_DMA_ADDRESS),
                        BOOTMEM_ALLOC_ACCESSIBLE, nid);
        if (!base)
                return -ENOMEM;
        NODE_DATA(nid)->node_page_ext = base;
        total_usage += table_size;
        return 0;

지정된 노드에 MAX_ORDER_NR_PAGES 단위로 align된 존재하는 페이지 수 만큼의 page_ext 구조체 + extra 메모리 만큼을 memblock에 할당한다.

  • 코드 라인 7~9에서 해당 노드가 관리하는 홀을 포함한 전체 페이지 수를 가져온다.
  • 코드 라인 16~18에서 해당 노드의 시작과 끝 페이지가 최대 버디 오더 페이지 단위로 정렬되지 않은 경우 extra 공간을 더 정렬 단위만큼 추가한다.
    • MAX_ORDER_NR_PAGES는 버디 시스템에서 최대 할당 가능한 오더 페이지 수 이다.
  • 코드 라인 20~26에서 page_ext 용도로 메모리를 할당한다.
    • memblock의 DMA 주소 ~ lowmem 범위에 가능하면 노드 공간에 page_ext 공간을 할당한다.
    • 노드 주소 범위가 DMA 주소 ~ lowmem 범위에 포함되지 않는 경우에는 지정된 노드가 아니더라도 할당을 시도한다.
  • 코드 라인 27에서 할당된 메모리는 노드 멤버 node_page_ext에 지정한다.
  • 코드 라인 28에서 전역 사용량(바이트)에 할당 사이즈 만큼 추가한다.




static void __init invoke_init_callbacks(void)
        int i;
        int entries = ARRAY_SIZE(page_ext_ops);

        for (i = 0; i < entries; i++) {
                if (page_ext_ops[i]->init)

page_ext_ops 엔트리 수 만큼 루프를 돌며 초기화 핸들러 함수를 호출한다.




struct page_ext *lookup_page_ext(struct page *page)
        unsigned long pfn = page_to_pfn(page);
        unsigned long offset;
        struct page_ext *base;

        base = NODE_DATA(page_to_nid(page))->node_page_ext;
         * The sanity checks the page allocator does upon freeing a
         * page can reach here before the page_ext arrays are
         * allocated when feeding a range of pages to the allocator
         * for the first time during bootup or memory hotplug.
        if (unlikely(!base))
                return NULL;
        offset = pfn - round_down(node_start_pfn(page_to_nid(page)),
        return base + offset;

page로 page_ext 정보츨 찾아 반환한다.



page_ext_operations 구조체


struct page_ext_operations {
        size_t offset;
        size_t size;
        bool (*need)(void);       
        void (*init)(void);
  • offset
    • page_ext 뒤에 추가된 extra 메모리를 가리키는 offset 이다.
  • size
    • 해당 오퍼레이션에서 사용하는 extra 메모리 바이트 수
    • CONFIG_PAGE_OWNER 커널 옵션을 사용하는 경우 page_owner 구조체 크기가 사용된다.
  •  need
    • 필요 여부 조회 핸들러 함수 등록
  • init
    • 초기화 핸들러 함수 등록


page_ext_ops[] 전역 객체


 * struct page extension
 * This is the feature to manage memory for extended data per page.
 * Until now, we must modify struct page itself to store extra data per page.
 * This requires rebuilding the kernel and it is really time consuming process.
 * And, sometimes, rebuild is impossible due to third party module dependency.
 * At last, enlarging struct page could cause un-wanted system behaviour change.
 * This feature is intended to overcome above mentioned problems. This feature
 * allocates memory for extended data per page in certain place rather than
 * the struct page itself. This memory can be accessed by the accessor
 * functions provided by this code. During the boot process, it checks whether
 * allocation of huge chunk of memory is needed or not. If not, it avoids
 * allocating memory at all. With this advantage, we can include this feature
 * into the kernel in default and can avoid rebuild and solve related problems.
 * To help these things to work well, there are two callbacks for clients. One
 * is the need callback which is mandatory if user wants to avoid useless
 * memory allocation at boot-time. The other is optional, init callback, which
 * is used to do proper initialization after memory is allocated.
 * The need callback is used to decide whether extended memory allocation is
 * needed or not. Sometimes users want to deactivate some features in this
 * boot and extra memory would be unneccessary. In this case, to avoid
 * allocating huge chunk of memory, each clients represent their need of
 * extra memory through the need callback. If one of the need callbacks
 * returns true, it means that someone needs extra memory so that
 * page extension core should allocates memory for page extension. If
 * none of need callbacks return true, memory isn't needed at all in this boot
 * and page extension core can skip to allocate memory. As result,
 * none of memory is wasted.
 * When need callback returns true, page_ext checks if there is a request for
 * extra memory through size in struct page_ext_operations. If it is non-zero,
 * extra space is allocated for each page_ext entry and offset is returned to
 * user through offset in struct page_ext_operations.
 * The init callback is used to do proper initialization after page extension
 * is completely initialized. In sparse memory system, extra memory is
 * allocated some time later than memmap is allocated. In other words, lifetime
 * of memory for page extension isn't same with memmap for struct page.
 * Therefore, clients can't store extra data until page extension is
 * initialized, even if pages are allocated and used freely. This could
 * cause inadequate state of extra data per page, so, to prevent it, client
 * can utilize this callback to initialize the state of it correctly.
static struct page_ext_operations *page_ext_ops[] = {
#if defined(CONFIG_IDLE_PAGE_TRACKING) && !defined(CONFIG_64BIT)

커널 옵션에 따라 몇 가지 기능의 핸들러 객체들이 등록되어 있다.


debug_guardpage_ops 전역 객체


struct page_ext_operations debug_guardpage_ops = {
        .need = need_debug_guardpage,
        .init = init_debug_guardpage,

가드 페이지 디버깅에 관련된 핸들러 함수들이 등록되어 있다.


page_owner_ops 전역 객체


struct page_ext_operations page_owner_ops = {
        .size = sizeof(struct page_owner),
        .need = need_page_owner,
        .init = init_page_owner,

페이지 owner 트래킹에 관련된 핸들러 함수들이 등록되어 있다.

  • 디버그용으로 CONFIG_PAGE_OWNER 커널 옵션이 동작하는 경우에만 사용된다.


page_idle_ops 전역 객체


#ifndef CONFIG_64BIT
struct page_ext_operations page_idle_ops = {
        .need = need_page_idle,

idle 페이지 트래킹에 관련된 핸들러 함수들이 등록되어 있다.

  • 이 오퍼레이션은 32비트 시스템에서만 사용되며, 64비트 시스템에서는 page_ext를 사용하는 이러한 오퍼레이션 없이 page 구조체만을 사용하여 동작한다.




댓글 남기기