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을 가상주소로 바꾼 주소로 설정한다.

 

참고

 

답글 남기기

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