build_all_zonelists()

이 함수는 부팅 시에 호출이 되는 경우 전체 노드에 대해 zonelist를 구성하고 현재 cpu에 대해 모든 노드의 메모리를 access 할 수 있도록 설정한다. 또한 운영 중에 핸들러에 의해 호출(hotplug memory)되는 경우 전체 cpu를 멈추고 zone에 대한 boot pageset 테이블 구성 및 전체 노드에 대해 zonelist를 다시 구성한다. 구성 후 free 페이지가 적은 경우 전역 변수 page_group_by_mobility_disabled를 1로 하여 mobility 기능을 잠시 disable하고 나중에 enable한다.

 

zone 구성

  • zone은 하나 이상 사용할 수 있으며 조합하여 사용할 때 다음의 zone type 끼리 최대 4개까지 조합이 가능하다.
    • ZONE_DMA
    • ZONE_DMA32
    • ZONE_NORMAL
    • ZONE_HIGHMEM
    • ZONE_MOVABLE
  • 다음의 조합은 사용할 수 없는 조합이다.
    • ZONE_DMA + ZONE_HIGHMEM
    • ZONE_DMA + ZONE_DMA32
    • ZONE_DMA32 + ZONE_HIGHMEM
    • ZONE_DMA + ZONE_DMA32 + ZONE_HIGHMEM
    • ZONE_MOVABLE + ZONE_HIGHMEM + ZONE_DMA
    • ZONE_MOVABLE + ZONE_DMA32 + ZONE_DMA
    • ZONE_MOVABLE + ZONE_DMA32 + ZONE_HIGHMEM
    • ZONE_MOVABLE + ZONE_DMA32 | ZONE_DMA | ZONE_HIGHMEM

 

zonelists

  • fallback
    • NUMA 시스템에서 각 노드에 zone들이 구성되어 있는데 특정 노드에서 메모리를 할당해야 할 때 할당할 영역(node+zone)을 선택하는데 그 영역에서 out of memory로 인해 할당이 실패하는 경우 대체(fallback) 영역에서 할당을 재시도 해야하는데 그 때 그 때 마다 영역을 찾지 않고 일정한 규칙에 의해 할당 우선 순위 리스트를 만들어 두어 사용하는데 이것을 zonelists라고 한다.
  • zonelist order
    • 우선 순위를 두어 단계적인 fallback용 zonelist를 구성한다.
    • 시스템 설계에 따라 node order 또는 zone order를 결정하여 순서를 정할 수 있다. default로 32bit 시스템은 zone order로 되어 있고 64bit 시스템은 node order로 구성한다.
      • node order
        • best node -> zone 타입 역순으로 zonelist를 구성한다.
        • 아키텍처에 따라 node order로 동작하더라도 ZONE_DMA 또는 ZONE_DMA32에 대해서는 할당을 제한하기도 한다.
        • 64 비트 시스템에서는 보통 메모리가 충분해서 부족하지 않으므로 node order를 보통 사용
      • zone order
        • zone 타입 역순 -> best node 순으로 zonelist를 구성한다.
        • ZONE_NORMAL에 대한 영역에 대해 충분히 free 영역을 확보할 필요가 있기 때문에 보통 메모리가 적은 32bit 시스템에서는 zone order 방식을 default로 사용하여 ZONE_HIGHMEM(ZONE_MOVABLE) 부터 사용할 수 있게한다.
        • 32 비트 시스템에서는 보통 메모리가 충분하지 않으므로 ZONE_DMA 및 ZONE_NORMAL 메모리를 보호하기 위해 zone order를 보통 사용
  • node와 zone의 검색 방향
    • node
      • 노드 간 access 속도가 다르므로 현재 노드로 부터 가장 빠른 노드(best)를 우선순위로 둔다.
    • zone 타입 역순
      • 각 zone에 대해 아래 순서로 진행을 한다. DMA 영역은 가장 낮은 순위로 메모리 할당을 하여 보호 받는다.
        • ZONE_MOVABLE (최 우선 순위)
        • ZONE_HIGHMEM (64비트 시스템에서는 없다)
        • ZONE_NORMAL
        • ZONE_DMA32 (64비트 시스템) | ZONE_DMA (32비트 시스템)
  • 2 개의 zonelists를 사용하는 NUMA 시스템
    • zonelists[0]은 전체 노드를 대상으로 만들어진다.
    • zonelists[1]은 현재 노드만을 대상으로 만들어진다. (NUMA only)

 

ZONE_MOVABLE

이 영역은 NUMA 시스템에서 특별히 버디 시스템으로 구현된 페이지 할당자가 메모리 파편화를 막기 위해 전용으로 사용하는 영역이며 동시에 최근에 구현된 메모리 Hotplug를 지원하기 위해 구성되야 하는 영역이다. 버디 시스템에서 메모리 파편화를 막기 위해 사용하는 방법으로 가능하면 migrate 타입별로 관리를 하는데 보다 더 확실한 영역을 지정하여 더 효율적으로 사용할 수 있다.

  • 두 가지 설정 방법.
    • 1) 사이즈로 설정
      • 커널 파라메터로 “kernelcore=” 또는 “movablecore”를 사용하여 사이즈를 지정하여 사용한다.
        • kernelcore=를 사용하는 경우 전체 메모리에서 지정된 사이즈를 제외한 나머지 사이즈가 ZONE_MOVABLE로 설정된다.
        • movablecore=를 사용하여 지정된 사이즈가 ZONE_MOVABLE로 설정된다.
    • 2) 특정 노드 메모리를 지정
      • “CONFIG_MOVABLE_NODE” 커널 옵션을 사용하면서
      • 커널 파라메터로 “movable_node”를 사용하고
      • memory memblock regions의 flags에 MEMBLOCK_HOTPLUG 플래그 비트를 설정한 메모리 블럭을 통째로 ZONE_MOVABLE로 지정할 수 있다.
      • 메모리 hotplug가 가능한 메모리 영역을 지정하여 사용한다.
        • 리눅스는 구현이 완료되었고, 이 기능을 응용하여 flexible하게 메모리의 동적 구성이 가능하다.
        • 실제 하드웨어로 메모리 hotplug가 시험되었는지 여부는 미지수이다. (구현 당시에는 하드웨어가 준비되지 않은 상태임)
    • 위의 두 가지 설정이 없는 커널에서는 ZONE_MOVABLE의 영역을 별도로 운영하지 않는다.
  • 특징
    • GFP_MOVABLE 속성 플래그를 가진 페이지들은 가능하면 ZONE_MOVABLE에 들어갈 수 있다.
      • ZONE_MOVABLE에는 GFP_MOVABLE 속성 플래그를 가지지 않은 페이지들은 존재하지 않는다.
      • GFP_RECLAIMABLE 속성 플래그도 상관 없을 듯 한데 아직 구현이 안되어 있다고 한다.
    • GFP_MOVABLE 속성을 사용하는 경우 버디 시스템으로 구현된 페이지 할당자에서 연속된 페이지 요청을 수행하기 힘든 경우 메모리 compaction이 일어나는데 이 때 물리 메모리 페이지를 다른 위치로 이동시킨다.
      • 메모리 compacton
        • 버디 시스템에서 연속된 free 메모리가 부족하여 요청한 order 페이지에 대한 할당이 불가능할 때 해당 order의 앞쪽에 있는 사용된 페이지를 뒤 쪽으로 옮겨서 앞 부분에 필요한 공간을 만든다. (간단히 설명한 방법이다)
    • ZONE_MOVABLE은 가장 마지막 ZONE의 메모리를 분할하여 사용한다.
      • 아키텍처와 메모리 크기에 따라 해당 시스템의 마지막 zone이 다르다.
        • arm: CONFIG_HIGHMEM을 선택하고 만일 없으면 CONFIG_NORMAL을 선택한다.
        • arm64: CONFIG_NORMAL을 선택하고 만일 없으면 ZONE_DMA32을 선택한다.
        • x86(32bit): ZONE_HIGHMEM을 선택하고 만일 없으면 ZONE_NORMAL을 선택한다.
        • x86(64bit): ZONE_NORMAL을 선택하고 만일 없으면 ZONE_DMA32을 선택한다.

 

다음 그림은 zonelist를 구성하는 순서이다.

build_all_zonelists-1

 

build_all_zonelists()

mm/page_alloc.c

/*
 * Called with zonelists_mutex held always
 * unless system_state == SYSTEM_BOOTING.
 *
 * __ref due to (1) call of __meminit annotated setup_zone_pageset
 * [we're only called with non-NULL zone through __meminit paths] and
 * (2) call of __init annotated helper build_all_zonelists_init
 * [protected by SYSTEM_BOOTING].
 */
void __ref build_all_zonelists(pg_data_t *pgdat, struct zone *zone)
{
        set_zonelist_order(); 

        if (system_state == SYSTEM_BOOTING) {
                build_all_zonelists_init();
        } else {
#ifdef CONFIG_MEMORY_HOTPLUG
                if (zone)
                        setup_zone_pageset(zone);
#endif 
                /* we have to stop all cpus to guarantee there is no user
                   of zonelist */
                stop_machine(__build_all_zonelists, pgdat, NULL);
                /* cpuset refresh routine should be here */
        }
        vm_total_pages = nr_free_pagecache_pages();
        /*
         * Disable grouping by mobility if the number of pages in the
         * system is too low to allow the mechanism to work. It would be
         * more accurate, but expensive to check per-zone. This check is
         * made on memory-hotadd so a system can start with mobility
         * disabled and enable it later
         */
        if (vm_total_pages < (pageblock_nr_pages * MIGRATE_TYPES))
                page_group_by_mobility_disabled = 1;
        else
                page_group_by_mobility_disabled = 0;

        pr_info("Built %i zonelists in %s order, mobility grouping %s.  "
                "Total pages: %ld\n",
                        nr_online_nodes,
                        zonelist_order_name[current_zonelist_order],
                        page_group_by_mobility_disabled ? "off" : "on",
                        vm_total_pages);
#ifdef CONFIG_NUMA
        pr_info("Policy zone: %s\n", zone_names[policy_zone]);
#endif
}
  • set_zonelist_order()
    • 전역 current_zonelist_order를 node order 또는 zone order로 결정한다.
      • 별도 커널 파라메터로 선택하지 않은 경우 default로 32bit 시스템에서는 zone order, 64bit 시스템에서는 node order로 선택한다.
  • if (system_state == SYSTEM_BOOTING) { build_all_zonelists_init();
    • 최초 부팅 시에 호출된 경우 최초 zonelist 구성을 한다.
  • } else {
    • 최초 부팅 시가 아니면
  • if (zone) setup_zone_pageset(zone);
    • hotplug memory가 지원되는 경우 zone에 대한 boot pageset 테이블을 구성한다.
  • stop_machine(__build_all_zonelists, pgdat, NULL);
    • 모든 cpu를 멈춘 후 zonelist를 재 구성한다.
  • vm_total_pages = nr_free_pagecache_pages();
    • zonelist에서 high 워터마크를 제외한 free 페이지를 재 계산한다.

 

include/linux/kernel.h

/* Values used for system_state */
extern enum system_states {
        SYSTEM_BOOTING,
        SYSTEM_RUNNING,
        SYSTEM_HALT,
        SYSTEM_POWER_OFF,
        SYSTEM_RESTART,
} system_state;

 

set_zonelist_order()

mm/page_alloc.c

static void set_zonelist_order(void)
{
        if (user_zonelist_order == ZONELIST_ORDER_DEFAULT)
                current_zonelist_order = default_zonelist_order();
        else
                current_zonelist_order = user_zonelist_order;
}

전역 user_zonelist_order 값에 따라서 current_zonelist_order를 결정한다.

  • if (user_zonelist_order == ZONELIST_ORDER_DEFAULT) current_zonelist_order = default_zonelist_order();
    • 전역 user_zonelist_order를 변경하지 않아 default로 되어 있는 경우 default로 32bit 시스템에서는 zone으로 64bit 시스템에서는 node로 선택한다.
  • else current_zonelist_order = user_zonelist_order;
    • 그렇지 않은 경우 current_zonelist_order는 user_zonelist_order를 대입한다.

 

mm/page_alloc.c

/*
 *  zonelist_order:
 *  0 = automatic detection of better ordering.
 *  1 = order by ([node] distance, -zonetype)
 *  2 = order by (-zonetype, [node] distance)
 *
 *  If not NUMA, ZONELIST_ORDER_ZONE and ZONELIST_ORDER_NODE will create
 *  the same zonelist. So only NUMA can configure this param.
 */
#define ZONELIST_ORDER_DEFAULT  0
#define ZONELIST_ORDER_NODE     1
#define ZONELIST_ORDER_ZONE     2

 

mm/page_alloc.c

/* zonelist order in the kernel.
 * set_zonelist_order() will set this to NODE or ZONE.
 */
static int current_zonelist_order = ZONELIST_ORDER_DEFAULT;

/* The value user specified ....changed by config */
static int user_zonelist_order = ZONELIST_ORDER_DEFAULT;

 

default_zonelist_order()

mm/page_alloc.c

#if defined(CONFIG_64BIT)
/*
 * Devices that require DMA32/DMA are relatively rare and do not justify a
 * penalty to every machine in case the specialised case applies. Default
 * to Node-ordering on 64-bit NUMA machines
 */
static int default_zonelist_order(void)
{
        return ZONELIST_ORDER_NODE;
}
#else
/*
 * On 32-bit, the Normal zone needs to be preserved for allocations accessible
 * by the kernel. If processes running on node 0 deplete the low memory zone
 * then reclaim will occur more frequency increasing stalls and potentially
 * be easier to OOM if a large percentage of the zone is under writeback or
 * dirty. The problem is significantly worse if CONFIG_HIGHPTE is not set.
 * Hence, default to zone ordering on 32-bit.
 */
static int default_zonelist_order(void)
{
        return ZONELIST_ORDER_ZONE;
}
#endif /* CONFIG_64BIT */
  • zonelist order는 64비트 시스템에서는 node order로 32비트 시스템에서는 zone order로 리턴한다.

 

“numa_zonelist_order” 커널 파라메터

static __init int setup_numa_zonelist_order(char *s)
{
        int ret;

        if (!s)
                return 0;

        ret = __parse_numa_zonelist_order(s);
        if (ret == 0)
                strlcpy(numa_zonelist_order, s, NUMA_ZONELIST_ORDER_LEN);

        return ret;
}
early_param("numa_zonelist_order", setup_numa_zonelist_order);
  • 누마 시스템에서 zonelist order를 다음 중 하나로 선택하게 한다.
    • “numa_zonelist_order=d”
      • automatic configuration
    • “numa_zonelist_order=n”
      • node order (노드 거리에 따른 가장 가까운 best 노드 순)
    • “numa_zonelist_order=z”
      • zone order (zone type 역순)

 

/*
 * interface for configure zonelist ordering.
 * command line option "numa_zonelist_order"
 *      = "[dD]efault   - default, automatic configuration.
 *      = "[nN]ode      - order by node locality, then by zone within node
 *      = "[zZ]one      - order by zone, then by locality within zone
 */

static int __parse_numa_zonelist_order(char *s)
{
        if (*s == 'd' || *s == 'D') {
                user_zonelist_order = ZONELIST_ORDER_DEFAULT;
        } else if (*s == 'n' || *s == 'N') {
                user_zonelist_order = ZONELIST_ORDER_NODE;
        } else if (*s == 'z' || *s == 'Z') {
                user_zonelist_order = ZONELIST_ORDER_ZONE;
        } else {
                printk(KERN_WARNING
                        "Ignoring invalid numa_zonelist_order value:  "
                        "%s\n", s);
                return -EINVAL;
        }
        return 0;
}
  • 누마 시스템에서 문자 s에 따라 user_zonelist order를 다음 중 하나로 선택하게 한다.
    • ‘d’ 또는 ‘D”
      • default
    • ‘n’ 또는 ‘N’
      • node order
    • ‘z’ 또는 ‘Z’
      • zone order

 

build_all_zonelists_init()

mm/page_alloc.c

static noinline void __init
build_all_zonelists_init(void)
{
        __build_all_zonelists(NULL);
        mminit_verify_zonelist();
        cpuset_init_current_mems_allowed();
}

zonelist를 구성하고 디버그 출력하며 현재 cpu에 대해 모든 노드의 메모리를 access 할 수 있도록 설정한다.

 

__build_all_zonelists()

mm/page_alloc.c

/* return values int ....just for stop_machine() */
static int __build_all_zonelists(void *data)
{
        int nid;
        int cpu;
        pg_data_t *self = data;

#ifdef CONFIG_NUMA
        memset(node_load, 0, sizeof(node_load));
#endif

        if (self && !node_online(self->node_id)) {
                build_zonelists(self);
                build_zonelist_cache(self);
        }

        for_each_online_node(nid) {
                pg_data_t *pgdat = NODE_DATA(nid);

                build_zonelists(pgdat);
                build_zonelist_cache(pgdat);
        }

        /*
         * Initialize the boot_pagesets that are going to be used
         * for bootstrapping processors. The real pagesets for
         * each zone will be allocated later when the per cpu
         * allocator is available.
         *
         * boot_pagesets are used also for bootstrapping offline
         * cpus if the system is already booted because the pagesets
         * are needed to initialize allocators on a specific cpu too.
         * F.e. the percpu allocator needs the page allocator which
         * needs the percpu allocator in order to allocate its pagesets
         * (a chicken-egg dilemma).
         */
        for_each_possible_cpu(cpu) {
                setup_pageset(&per_cpu(boot_pageset, cpu), 0);

#ifdef CONFIG_HAVE_MEMORYLESS_NODES
                /*
                 * We now know the "local memory node" for each node--
                 * i.e., the node of the first zone in the generic zonelist.
                 * Set up numa_mem percpu variable for on-line cpus.  During
                 * boot, only the boot cpu should be on-line;  we'll init the
                 * secondary cpus' numa_mem as they come on-line.  During
                 * node/memory hotplug, we'll fixup all on-line cpus.
                 */
                if (cpu_online(cpu))
                        set_cpu_numa_mem(cpu, local_memory_node(cpu_to_node(cpu)));
#endif
        }

        return 0;
}

모든 노드에 대해 zonelists를 구성한다.

  • pg_data_t *self = data;
    • 최초 build_zonelists_all()) 함수 호출 시 null이 data 인수로 주어졌다.
  • memset(node_load, 0, sizeof(node_load));
    • 전역 node_load[]  배열을 0으로 초기화한다.
  •  if (self && !node_online(self->node_id)) {
    • self가 주어진 경우이면서 노드가 online 상태가 아니면
  • build_zonelists(self);
    • 주어진 노드에 대해서 zonelists를 구성한다.
  • build_zonelist_cache(self);
    • 주어진 노드에 대해 zonelists의 성능을 높이기 위해 캐시를 구성한다.
  • for_each_online_node(nid) {
  • pg_data_t *pgdat = NODE_DATA(nid);
    • pgdat에 현재 노드의 페이지 리스트 데이타 구조체를 가리키게 한다.
  • build_zonelists(pgdat);
    • 노드에 대해서 zonelists를 구성한다.
  • build_zonelist_cache(pgdat);
    • zonelists의 성능을 높이기 위해 캐시를 구성한다.
    • 최근 커널에서는 이 캐시는
  • for_each_possible_cpu(cpu) {
  • setup_pageset(&per_cpu(boot_pageset, cpu), 0);
    • cpu에 대해 boot pageset 테이블 구성을 한다.
    • 실제 페이지셋 테이블은 per-cpu 할당자가 실제 운영을 한 후에 메모리에 할당되고 구성된다.
  • if (cpu_online(cpu)) set_cpu_numa_mem(cpu, local_memory_node(cpu_to_node(cpu)));
    • cpu가 online 상태이면 _numa_mem_ 및 _node_numa_mem_[] per-cpu 데이터에 노드를 설정한다.

 

build_zonelists()

mm/page_alloc.c

static void build_zonelists(pg_data_t *pgdat)
{
        int j, node, load;
        enum zone_type i;
        nodemask_t used_mask;
        int local_node, prev_node;
        struct zonelist *zonelist;
        int order = current_zonelist_order;

        /* initialize zonelists */
        for (i = 0; i < MAX_ZONELISTS; i++) {
                zonelist = pgdat->node_zonelists + i;
                zonelist->_zonerefs[0].zone = NULL;
                zonelist->_zonerefs[0].zone_idx = 0;
        }

        /* NUMA-aware ordering of nodes */
        local_node = pgdat->node_id;
        load = nr_online_nodes;
        prev_node = local_node;
        nodes_clear(used_mask);

        memset(node_order, 0, sizeof(node_order));
        j = 0;

        while ((node = find_next_best_node(local_node, &used_mask)) >= 0) {
                /*
                 * We don't want to pressure a particular node.
                 * So adding penalty to the first node in same
                 * distance group to make it round-robin.
                 */
                if (node_distance(local_node, node) !=
                    node_distance(local_node, prev_node))
                        node_load[node] = load;

                prev_node = node;
                load--;
                if (order == ZONELIST_ORDER_NODE)
                        build_zonelists_in_node_order(pgdat, node);
                else
                        node_order[j++] = node; /* remember order */
        }

        if (order == ZONELIST_ORDER_ZONE) {
                /* calculate node order -- i.e., DMA last! */
                build_zonelists_in_zone_order(pgdat, j);
        }

        build_thisnode_zonelists(pgdat);
}
  • for (i = 0; i < MAX_ZONELISTS; i++) {
    • MAX_ZONELISTS(2 or 1) 수 만큼 루프를 돈다.
      • NUMA 시스템에서는 2, UMA 시스템에서는 1이다.
  • pgdat->node_zonelists를 초기화 한다.
  • local_node = pgdat->node_id;
    • 요청 노드 id
  • load = nr_online_nodes;
    • 운용중인 노드 수
  • prev_node = local_node;
    • 이전 노드에 일단 현재 요청 노드를 초기값으로 대입한다.
  • nodes_clear(used_mask);
    • used_mask가 가리키는 노드 비크맵을 clear 한다.
  •  memset(node_order, 0, sizeof(node_order));
    • 전역 node_order[] 배열을 0으로 clear 한다.
  • while ((node = find_next_best_node(local_node, &used_mask)) >= 0) {
    • used_mask 비트맵에 포함되지 않은 노드 중 가장 인접한 노드 id를 알아온다.
  • if (node_distance(local_node, node) != node_distance(local_node, prev_node)) node_load[node] = load;
    • 만일 현재 노드와 알아온 인접 노드의 거리가 현재 노드와 기존 노드와 거리가 다르다면
    • 즉, 이전 노드와 거리가 다르다면 node_load[node]에 load 값을 대입한다.
  • prev_node = node;
    • 이전 노드에 현재 노드 id를 대입한다.
  • load–;
    • load 값을 감소시킨다.
  • if (order == ZONELIST_ORDER_NODE) build_zonelists_in_node_order(pgdat, node);
    • order가 node 순이면 zonelists를 order 순으로 구성한다.
  • else  node_order[j++] = node;
    • order가 zone 순이면 일단 node_order[]에 노드 id를 대입하고 j를 증가시킨다.
  • if (order == ZONELIST_ORDER_ZONE) { build_zonelists_in_zone_order(pgdat, j); }
    • order가 zone 순이면 zonelists를 zone 순으로 구성한다.
  • build_thisnode_zonelists(pgdat);
    • 현재 노드에 대해서만 활성화된 zone을 zonelists[1]에 추가한다.

 

다음 그림은 NUMA 시스템에서 node_load[] 및 node_order[]를 알아본 사례이다.

  • node_order[]
    • 현재 부팅한 cpu가 있는 로컬 노드를 대상으로 가장 빠른(가까운) 순서의 노드 번호를 저장한다.
  • node_load[]
    • 다음 best 노드를 찾을 때 사용할 노드별 가중치 값

build_zonelists-1c

 

build_zonelists_in_node_order()

mm/page_alloc.c

/*
 * Build zonelists ordered by node and zones within node.
 * This results in maximum locality--normal zone overflows into local
 * DMA zone, if any--but risks exhausting DMA zone.
 */
static void build_zonelists_in_node_order(pg_data_t *pgdat, int node)
{
        int j;
        struct zonelist *zonelist;

        zonelist = &pgdat->node_zonelists[0];
        for (j = 0; zonelist->_zonerefs[j].zone != NULL; j++)
                ;
        j = build_zonelists_node(NODE_DATA(node), zonelist, j);
        zonelist->_zonerefs[j].zone = NULL;
        zonelist->_zonerefs[j].zone_idx = 0;
}
  • zonelist = &pgdat->node_zonelists[0];
    • 노드의 fallback을 포함한 zonelist
  • for (j = 0; zonelist->_zonerefs[j].zone != NULL; j++) ;
    • zonelist에 등록된 zonerefs에 빈 곳을 찾는다.
  • j = build_zonelists_node(NODE_DATA(node), zonelist, j);
    • 해당 노드에 등록된 활성화된 zone을 zonelist에 추가한다.
  • zonelist->_zonerefs[j].zone = NULL; zonelist->_zonerefs[j].zone_idx = 0;
    • zonelist에 추가 후 null로 마친다.

 

아래 그림은 4개 노드에 대해 node order로 구축한 zonelists를 보여준다.

  • 경우에 따라서는 DMA(DMA32)만 특별히 가장 밑으로 옮긴 경우도 있다.

build_zonelists_in_node_order-1b

 

build_zonelists_node()

mm/page_alloc.c

/*
 * Builds allocation fallback zone lists.
 *
 * Add all populated zones of a node to the zonelist.
 */
static int build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist,
                                int nr_zones)
{
        struct zone *zone;
        enum zone_type zone_type = MAX_NR_ZONES;

        do {
                zone_type--;
                zone = pgdat->node_zones + zone_type;
                if (populated_zone(zone)) {
                        zoneref_set_zone(zone,
                                &zonelist->_zonerefs[nr_zones++]);
                        check_highest_zone(zone_type);
                }
        } while (zone_type);

        return nr_zones;
}

노드 -> zone 순으로 활성화된 zone을 해당 노드의 zonelist에 추가한다.

  • enum zone_type zone_type = MAX_NR_ZONES;
    • 최하 2개의 zone (ZONE_NORMAL, ZONE_MOVABLE)
  • zone_type–;
    • fallback용 zonelist를 만들때 상위부터
  • zone = pgdat->node_zones + zone_type;
    • 노드에 대한 zone
  • if (populated_zone(zone)) {
    • 해당 zone이 사용 가능하면?
  • zoneref_set_zone(zone, &zonelist->_zonerefs[nr_zones++]);
    • fallback 용도를 포함한 할당에 사용할 zonelist에 해당 zone을 추가
  • check_highest_zone(zone_type);
    • ZONE_MOVABLE 을 제외한 최고 zone을 전역 policy_zone에 기억한다.
  • } while (zone_type);
    • 가장 낮은 zone 까지 루프를 돈다.

 

populated_zone()

include/linux/mmzone.h

static inline int populated_zone(struct zone *zone)
{
        return (!!zone->present_pages);
}
  • present_pages가 존재하는 경우 zone이 사용됨을 의미한다.

 

check_highest_zone()

include/linux/mempolicy.h

static inline void check_highest_zone(enum zone_type k)
{
        if (k > policy_zone && k != ZONE_MOVABLE)
                policy_zone = k;
}
  • 전역 policy_zone 보다 k가 크면서 k가 ZONE_MOVABLE이 아닌경우 policy_zone에 k를 대입한다.

 

zoneref_set_zone()

mm/page_alloc.c

static void zoneref_set_zone(struct zone *zone, struct zoneref *zoneref)
{
        zoneref->zone = zone;
        zoneref->zone_idx = zone_idx(zone);
}

zoneref에 인수로 지정한 zone과 zone type을 대입한다.

 

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이 0
    • rpi2: 0=ZONE_NORMAL, 1=ZONE_MOVABLE

 

build_zonelists_in_zone_order()

mm/page_alloc.c

/*
 * Build zonelists ordered by zone and nodes within zones.
 * This results in conserving DMA zone[s] until all Normal memory is
 * exhausted, but results in overflowing to remote node while memory
 * may still exist in local DMA zone.
 */
static int node_order[MAX_NUMNODES];

static void build_zonelists_in_zone_order(pg_data_t *pgdat, int nr_nodes)
{
        int pos, j, node;
        int zone_type;          /* needs to be signed */
        struct zone *z;
        struct zonelist *zonelist;

        zonelist = &pgdat->node_zonelists[0];
        pos = 0;
        for (zone_type = MAX_NR_ZONES - 1; zone_type >= 0; zone_type--) {
                for (j = 0; j < nr_nodes; j++) {
                        node = node_order[j];
                        z = &NODE_DATA(node)->node_zones[zone_type];
                        if (populated_zone(z)) {
                                zoneref_set_zone(z,
                                        &zonelist->_zonerefs[pos++]);
                                check_highest_zone(zone_type);
                        }
                }
        }
        zonelist->_zonerefs[pos].zone = NULL;
        zonelist->_zonerefs[pos].zone_idx = 0;
}

zone -> 노드 순으로 활성화된 zone을 해당 노드의 zonelist에 추가한다.

  • zonelist = &pgdat->node_zonelists[0];
    • 노드에 대응한 zonelists (fallback용으로 전체 노드에 대한 zone들이 구성됨)
  • for (zone_type = MAX_NR_ZONES – 1; zone_type >= 0; zone_type–) {
    • 전체 zone 수 만큼 역순으로 루프를 돈다.
  • for (j = 0; j < nr_nodes; j++) {
    • 노드 수 만큼 루프를 돈다.
  • node = node_order[j];
    • node 번호에 대응하는 노드를 node_order[] 배열에서 알아온다.
  • z = &NODE_DATA(node)->node_zones[zone_type];
    • 노드의 zone 인덱스에 대응하는 zone을 알아온다.
  • if (populated_zone(z)) {
    • 노드에서 활성화된 zone인 경우
  • zoneref_set_zone(z, &zonelist->_zonerefs[pos++]);
    • zonelist에 zone을 추가한다.
  • check_highest_zone(zone_type);
    • ZONE_MOVABLE 을 제외한 최고 zone을 전역 policy_zone에 기억한다.
  • zonelist->_zonerefs[pos].zone = NULL; zonelist->_zonerefs[pos].zone_idx = 0;
    • zonelist의 끝을 null로 마친다.

 

아래 그림은 4개 노드에 대해 zone order로 구축한 zonelists를 보여준다.

build_zonelists_in_zone_order-1b

 

build_thisnode_zonelists()

mm/page_alloc.c

/*
 * Build gfp_thisnode zonelists
 */
static void build_thisnode_zonelists(pg_data_t *pgdat)
{
        int j;
        struct zonelist *zonelist;

        zonelist = &pgdat->node_zonelists[1];
        j = build_zonelists_node(pgdat, zonelist, 0);
        zonelist->_zonerefs[j].zone = NULL;
        zonelist->_zonerefs[j].zone_idx = 0;
}

현재 노드에 대해서만 zonelists[1]을 구성한다.

  • zonelist = &pgdat->node_zonelists[1];
    • 현재 노드의 zonelists[1]
  • j = build_zonelists_node(pgdat, zonelist, 0);
    • 지정된 노드 하나에 대해서만 각 zone에 대해 활성화된 zone을 추가한다.
  • zonelist->_zonerefs[j].zone = NULL; zonelist->_zonerefs[j].zone_idx = 0;
    • zonelist의 끝을 null로 마친다.

 

build_zonelist_cache()

mm/page_alloc.c

/* Construct the zonelist performance cache - see further mmzone.h */
static void build_zonelist_cache(pg_data_t *pgdat)
{
        struct zonelist *zonelist;
        struct zonelist_cache *zlc;
        struct zoneref *z;

        zonelist = &pgdat->node_zonelists[0];
        zonelist->zlcache_ptr = zlc = &zonelist->zlcache;
        bitmap_zero(zlc->fullzones, MAX_ZONES_PER_ZONELIST);
        for (z = zonelist->_zonerefs; z->zone; z++)
                zlc->z_to_n[z - zonelist->_zonerefs] = zonelist_node_idx(z);
}

zonelist의 성능 향상을 위해 캐시를 구성한다.

 

  • zonelist = &pgdat->node_zonelists[0];
    • 해당 노드의 zonelists
  • zonelist->zlcache_ptr = zlc = &zonelist->zlcache;
    • zonelist의 zlcache 주소를  zacache_ptr에 대입
  • bitmap_zero(zlc->fullzones, MAX_ZONES_PER_ZONELIST);
    • zlc->fullzones 비트맵을 MAX_ZONES_PER_ZONELIST 수 만큼 clear한다.
    • MAX_ZONES_PER_ZONELIST
      • (MAX_NUMNODES * MAX_NR_ZONES)
    • fullzones 비트맵은 추후 각 zone에 대해 full이 되는 경우 설정되어 빠른 할당 검색에 도움을 준다.
  •  for (z = zonelist->_zonerefs; z->zone; z++) zlc->z_to_n[z – zonelist->_zonerefs] = zonelist_node_idx(z);
    • zonelist에 있는 모든 zone에 대해 zlc->z_to_n[]에 zone에 해당하는 노드 번호를 대입한다.

 

아래 그림은 zonelist_cache를 구성한 모습이다. fullzones 비트맵은 0으로 clear한다.

build_zonelist_cache-1b

 

zonelist_node_idx()

include/linux/mmzone.h

tatic inline int zonelist_node_idx(struct zoneref *zoneref)
{
#ifdef CONFIG_NUMA
        /* zone_to_nid not available in this context */
        return zoneref->zone->node;
#else
        return 0;
#endif /* CONFIG_NUMA */
}

 

setup_pageset()

mm/page_alloc.c

/*
 * Boot pageset table. One per cpu which is going to be used for all
 * zones and all nodes. The parameters will be set in such a way
 * that an item put on a list will immediately be handed over to
 * the buddy list. This is safe since pageset manipulation is done
 * with interrupts disabled.
 *
 * The boot_pagesets must be kept even after bootup is complete for
 * unused processors and/or zones. They do play a role for bootstrapping
 * hotplugged processors.
 *
 * zoneinfo_show() and maybe other functions do
 * not check if the processor is online before following the pageset pointer.
 * Other parts of the kernel may not check if the zone is available.
 */
static void setup_pageset(struct per_cpu_pageset *p, unsigned long batch)
{
        pageset_init(p);
        pageset_set_batch(p, batch);
}

부트 페이지셋 테이블을 할당 받고 초기화한다.

 

pageset_init()

mm/page_alloc.c

static void pageset_init(struct per_cpu_pageset *p)
{
        struct per_cpu_pages *pcp;
        int migratetype;

        memset(p, 0, sizeof(*p));

        pcp = &p->pcp;
        pcp->count = 0;
        for (migratetype = 0; migratetype < MIGRATE_PCPTYPES; migratetype++)
                INIT_LIST_HEAD(&pcp->lists[migratetype]);
}
  • memset(p, 0, sizeof(*p));
    • 인수로 지정된 per_cpu_pageset  구조체 p를 0으로 clear 한다.
  • pcp->count = 0;
    • 페이지 셋의 페이지들 카운트를 0으로 초기화한다.
  • for (migratetype = 0; migratetype < MIGRATE_PCPTYPES; migratetype++) INIT_LIST_HEAD(&pcp->lists[migratetype]);
    • p->pcp->lists[] 배열 중 MIGRATE_PCPTYPES 까지 만큼만 리스트를 초기화한다.

 

pageset_set_batch()

mm/page_alloc.c

/* a companion to pageset_set_high() */
static void pageset_set_batch(struct per_cpu_pageset *p, unsigned long batch)
{
        pageset_update(&p->pcp, 6 * batch, max(1UL, 1 * batch));
}

 

pageset_update()

mm/page_alloc.c

/*
 * pcp->high and pcp->batch values are related and dependent on one another:
 * ->batch must never be higher then ->high.
 * The following function updates them in a safe manner without read side
 * locking.
 *
 * Any new users of pcp->batch and pcp->high should ensure they can cope with
 * those fields changing asynchronously (acording the the above rule).
 *
 * mutex_is_locked(&pcp_batch_high_lock) required when calling this function
 * outside of boot time (or some other assurance that no concurrent updaters
 * exist).
 */
static void pageset_update(struct per_cpu_pages *pcp, unsigned long high,
                unsigned long batch)
{
       /* start with a fail safe value for batch */
        pcp->batch = 1;
        smp_wmb();

       /* Update high, then batch, in order */
        pcp->high = high;
        smp_wmb();

        pcp->batch = batch;
}
  • 지정된 순서 대로 batch를 1로 한 후 high와 batch를 대입한다.

 

mminit_verify_zonelist()

mm/mm_init.c

/* The zonelists are simply reported, validation is manual. */
void __init mminit_verify_zonelist(void)
{
        int nid;

        if (mminit_loglevel < MMINIT_VERIFY)
                return;

        for_each_online_node(nid) {
                pg_data_t *pgdat = NODE_DATA(nid);
                struct zone *zone;
                struct zoneref *z;
                struct zonelist *zonelist;
                int i, listid, zoneid;

                BUG_ON(MAX_ZONELISTS > 2);
                for (i = 0; i < MAX_ZONELISTS * MAX_NR_ZONES; i++) {
 
                        /* Identify the zone and nodelist */
                        zoneid = i % MAX_NR_ZONES;
                        listid = i / MAX_NR_ZONES;
                        zonelist = &pgdat->node_zonelists[listid];
                        zone = &pgdat->node_zones[zoneid];
                        if (!populated_zone(zone))
                                continue;

                        /* Print information about the zonelist */
                        printk(KERN_DEBUG "mminit::zonelist %s %d:%s = ",
                                listid > 0 ? "thisnode" : "general", nid,
                                zone->name);

                        /* Iterate the zonelist */
                        for_each_zone_zonelist(zone, z, zonelist, zoneid) {
#ifdef CONFIG_NUMA
                                printk(KERN_CONT "%d:%s ",
                                        zone->node, zone->name);
#else
                                printk(KERN_CONT "0:%s ", zone->name);
#endif /* CONFIG_NUMA */        
                        }
                        printk(KERN_CONT "\n");
                }               
        }
}

모든 노드의 존에 대해서 zonelists를 출력한다.

 

cpuset_init_current_mems_allowed()

kernel/cpuset.c

void __init cpuset_init_current_mems_allowed(void)
{
        nodes_setall(current->mems_allowed);
}

현재 태스크가 모든 노드의 메모리를 사용할 수 있도록 설정한다.

  • 현재 태스크의 mems_allowed 노드마스크 비트맵에 대해 모든 비트를 1로 설정한다.

 

nodes_setall()

include/linux/nodemask.h

#define nodes_setall(dst) __nodes_setall(&(dst), MAX_NUMNODES)
static inline void __nodes_setall(nodemask_t *dstp, unsigned int nbits)
{
        bitmap_fill(dstp->bits, nbits);
}

지정된 dst 노드 비트맵 마스크에 대해 모든 노드의 메모리를 사용할 수 있도록 설정한다.

  • dstp 노드마스크 비트맵에 대해 MAX_NUMNODES 수 만큼의 비트를 1로 설정한다.

 

setup_zone_pageset()

mm/page_alloc.c

static void __meminit setup_zone_pageset(struct zone *zone)
{
        int cpu;
        zone->pageset = alloc_percpu(struct per_cpu_pageset);
        for_each_possible_cpu(cpu)
                zone_pageset_init(zone, cpu);
}
  • zone->pageset = alloc_percpu(struct per_cpu_pageset);
    • 지정된 zone의 pageset에 percpu 형태로 메모리를 할당한다.
  • for_each_possible_cpu(cpu) zone_pageset_init(zone, cpu);
    • 모든 possible cpu에 대해 루프를 돌며 zone에 대한 페이지셋을 초기화한다.

 

zone_pageset_init()

mm/page_alloc.c

static void __meminit zone_pageset_init(struct zone *zone, int cpu)
{
        struct per_cpu_pageset *pcp = per_cpu_ptr(zone->pageset, cpu);

        pageset_init(pcp);
        pageset_set_high_and_batch(zone, pcp);
}
  • struct per_cpu_pageset *pcp = per_cpu_ptr(zone->pageset, cpu);
    • zone에 대한 pageset per-cpu 데이터를 가져온다.
  • pageset_init(pcp);
    • pageset을 초기화한다.

 

pageset_set_high_and_batch()

mm/page_alloc.c

static void pageset_set_high_and_batch(struct zone *zone,
                                       struct per_cpu_pageset *pcp)
{
        if (percpu_pagelist_fraction)
                pageset_set_high(pcp,
                        (zone->managed_pages /
                                percpu_pagelist_fraction));
        else
                pageset_set_batch(pcp, zone_batchsize(zone));
}

 

pageset_set_high()

mm/page_alloc.c

/*
 * pageset_set_high() sets the high water mark for hot per_cpu_pagelist
 * to the value high for the pageset p.
 */
static void pageset_set_high(struct per_cpu_pageset *p,
                                unsigned long high)
{
        unsigned long batch = max(1UL, high / 4);
        if ((high / 4) > (PAGE_SHIFT * 8))
                batch = PAGE_SHIFT * 8;

        pageset_update(&p->pcp, high, batch);
}

페이지셋에 high값과 batch 값을 갱신하는데 batch 값은 high /4 값으로 하되 1~96 범위로 한정한다.

  • unsigned long batch = max(1UL, high / 4);
    • batch에 high / 4 값을 대입하되 최소 1보다 커야 한다.
  • if ((high / 4) > (PAGE_SHIFT * 8)) batch = PAGE_SHIFT * 8;
    • high / 4 값이 PAGE_SHIFT(12) * 8 보다 큰 경우 batch 값을 PAGE_SHIFT(12) * 8로 제한한다.
  • pageset_update(&p->pcp, high, batch);
    • 페이지셋의 high, batch 값을 갱신한다.

 

nr_free_pagecache_pages()

mm/page_alloc.c

/**
 * nr_free_pagecache_pages - count number of pages beyond high watermark
 *
 * nr_free_pagecache_pages() counts the number of pages which are beyond the
 * high watermark within all zones.
 */
unsigned long nr_free_pagecache_pages(void)
{
        return nr_free_zone_pages(gfp_zone(GFP_HIGHUSER_MOVABLE));
}
  • GFP_HIGHUSER에는 zone 정보와 관련된 비트가 다음 두 개가 속해있다.
    • __GFP_HIGHMEM 및 __GFP_MOVABLE

 

nr_free_zone_pages()

mm/page_alloc.c

/**
 * nr_free_zone_pages - count number of pages beyond high watermark
 * @offset: The zone index of the highest zone
 *
 * nr_free_zone_pages() counts the number of counts pages which are beyond the
 * high watermark within all zones at or below a given zone index.  For each
 * zone, the number of pages is calculated as:
 *     managed_pages - high_pages
 */
static unsigned long nr_free_zone_pages(int offset)
{
        struct zoneref *z;
        struct zone *zone;

        /* Just pick one node, since fallback list is circular */
        unsigned long sum = 0;

        struct zonelist *zonelist = node_zonelist(numa_node_id(), GFP_KERNEL);

        for_each_zone_zonelist(zone, z, zonelist, offset) {
                unsigned long size = zone->managed_pages;
                unsigned long high = high_wmark_pages(zone);
                if (size > high)
                        sum += size - high;
        }

        return sum;
}

zonelist의 모든 zone에 대해 free 페이지 수를 알아오는데 high 워터마크 페이지 수를 제외한다.

  • struct zonelist *zonelist = node_zonelist(numa_node_id(), GFP_KERNEL);
    • 현재 cpu의 노드용 zonelist
  • for_each_zone_zonelist(zone, z, zonelist, offset) {
    • zonelist에 포함된 모든 zone에 대한 루프를 돈다.
  • unsigned long size = zone->managed_pages;
    • zone의 free 페이지 수
  • unsigned long high = high_wmark_pages(zone);
    • zone의 high 워터마크 페이지 수
  • if (size > high) sum += size – high;
    • size가 high보다 큰 경우 sum에 워커마크 페이지를 제외한 free 페이지 수를 더한다.

 

numa_node_id()

include/linux/topology.h

#ifndef numa_node_id
/* Returns the number of the current Node. */
static inline int numa_node_id(void)
{
        return raw_cpu_read(numa_node);
}
#endif

per-cpu 데이터인 numa_node 값을 알아온다. 즉 해당 cpu에 대한 numa_node id 값을 리턴한다.

  • 이 값은 set_numa_node() 또는 set_cpu_numa_node() 함수에 의해 설정된다.

 

include/linux/topology.h

#ifdef CONFIG_USE_PERCPU_NUMA_NODE_ID
DECLARE_PER_CPU(int, numa_node);
  • per-cpu 데이터 형으로 numa_node id 값이 담긴다. (based 0)

 

node_zonelist()

include/linux/gfp.h

/*
 * We get the zone list from the current node and the gfp_mask.
 * This zone list contains a maximum of MAXNODES*MAX_NR_ZONES zones.
 * There are two zonelists per node, one for all zones with memory and
 * one containing just zones from the node the zonelist belongs to.
 *
 * For the normal case of non-DISCONTIGMEM systems the NODE_DATA() gets
 * optimized to &contig_page_data at compile-time.
 */
static inline struct zonelist *node_zonelist(int nid, gfp_t flags)
{
        return NODE_DATA(nid)->node_zonelists + gfp_zonelist(flags);
}
  • 노드에 대응하는 2 개의 zonelists 중 하나를 다음 조건으로 리턴한다.
    • NUMA 시스템에서 __GFP_THISNODE 플래그 비트가 있는 경우 오직 자신의 노드에 대한 zone 만 포함된 zonelists[1]
    • otherwise, 모든 노드에 대한 zone이 포함된 zonelists[0]

 

gfp_zonelist()

include/linux/gfp.h

/*
 * There is only one page-allocator function, and two main namespaces to
 * it. The alloc_page*() variants return 'struct page *' and as such
 * can allocate highmem pages, the *get*page*() variants return
 * virtual kernel addresses to the allocated page(s).
 */

static inline int gfp_zonelist(gfp_t flags)
{
        if (IS_ENABLED(CONFIG_NUMA) && unlikely(flags & __GFP_THISNODE))
                return 1;

        return 0;
}

NUMA 시스템 이면서 낮은 확률로 __GFP_THISNODE 플래그가 설정된 경우 1을 리턴하고 그렇지 않은 경우 0을 리턴한다.

 

구조체

zonelist 구조체

include/linux/mmzone.h

/*
 * One allocation request operates on a zonelist. A zonelist
 * is a list of zones, the first one is the 'goal' of the
 * allocation, the other zones are fallback zones, in decreasing
 * priority.
 *
 * If zlcache_ptr is not NULL, then it is just the address of zlcache,
 * as explained above.  If zlcache_ptr is NULL, there is no zlcache.
 * *
 * To speed the reading of the zonelist, the zonerefs contain the zone index
 * of the entry being read. Helper functions to access information given
 * a struct zoneref are
 *
 * zonelist_zone()      - Return the struct zone * for an entry in _zonerefs
 * zonelist_zone_idx()  - Return the index of the zone for an entry
 * zonelist_node_idx()  - Return the index of the node for an entry
 */
struct zonelist {
        struct zonelist_cache *zlcache_ptr;                  // NULL or &zlcache
        struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];
#ifdef CONFIG_NUMA
        struct zonelist_cache zlcache;                       // optional ...
#endif
};
  • zlcache_ptr
    • NUMA 시스템인 경우 zlcache 주소를 가리키며 아닌 경우 null이다.
  • _zonerefs[]
    • 메모리 할당을 위해 fallback을 위한 zone들로 구성된다.
  • zlcache
    • zonelist 성능향상을 위해 캐시 데이터로 구성된다.

 

zonelist_cache 구조체

include/linux/mmzone.h

/*
 * We cache key information from each zonelist for smaller cache
 * footprint when scanning for free pages in get_page_from_freelist().
 *
 * 1) The BITMAP fullzones tracks which zones in a zonelist have come
 *    up short of free memory since the last time (last_fullzone_zap)
 *    we zero'd fullzones.
 * 2) The array z_to_n[] maps each zone in the zonelist to its node
 *    id, so that we can efficiently evaluate whether that node is
 *    set in the current tasks mems_allowed.
 *
 * Both fullzones and z_to_n[] are one-to-one with the zonelist,
 * indexed by a zones offset in the zonelist zones[] array.
 *
 * The get_page_from_freelist() routine does two scans.  During the
 * first scan, we skip zones whose corresponding bit in 'fullzones'
 * is set or whose corresponding node in current->mems_allowed (which
 * comes from cpusets) is not set.  During the second scan, we bypass
 * this zonelist_cache, to ensure we look methodically at each zone.
 *
 * Once per second, we zero out (zap) fullzones, forcing us to
 * reconsider nodes that might have regained more free memory.
 * The field last_full_zap is the time we last zapped fullzones.
 *
 * This mechanism reduces the amount of time we waste repeatedly
 * reexaming zones for free memory when they just came up low on
 * memory momentarilly ago.
 *
 * The zonelist_cache struct members logically belong in struct
 * zonelist.  However, the mempolicy zonelists constructed for
 * MPOL_BIND are intentionally variable length (and usually much
 * shorter).  A general purpose mechanism for handling structs with
 * multiple variable length members is more mechanism than we want
 * here.  We resort to some special case hackery instead.
 *
 * The MPOL_BIND zonelists don't need this zonelist_cache (in good
 * part because they are shorter), so we put the fixed length stuff
 * at the front of the zonelist struct, ending in a variable length
 * zones[], as is needed by MPOL_BIND.
 *
 * Then we put the optional zonelist cache on the end of the zonelist
 * struct.  This optional stuff is found by a 'zlcache_ptr' pointer in
 * the fixed length portion at the front of the struct.  This pointer
 * both enables us to find the zonelist cache, and in the case of
 * MPOL_BIND zonelists, (which will just set the zlcache_ptr to NULL)
 * to know that the zonelist cache is not there.
 *
 * The end result is that struct zonelists come in two flavors:
 *  1) The full, fixed length version, shown below, and
 *  2) The custom zonelists for MPOL_BIND.
 * The custom MPOL_BIND zonelists have a NULL zlcache_ptr and no zlcache.
 *
 * Even though there may be multiple CPU cores on a node modifying
 * fullzones or last_full_zap in the same zonelist_cache at the same
 * time, we don't lock it.  This is just hint data - if it is wrong now
 * and then, the allocator will still function, perhaps a bit slower.
 */
struct zonelist_cache {
        unsigned short z_to_n[MAX_ZONES_PER_ZONELIST];          /* zone->nid */
        DECLARE_BITMAP(fullzones, MAX_ZONES_PER_ZONELIST);      /* zone full? */
        unsigned long last_full_zap;            /* when last zap'd (jiffies) */
};
  • z_to_n[]
    • zonelist의 zoneref에 1:1로 대응하는 노드 id
  • fullzones
    • zonelist의 zoneref에 1:1로 대응하는 비트맵으로 zone이 full이 되었을 때 1로 설정되어 추후 메모리의 할당 검색에 빠른 도움을 준다.
  • last_full_zap
    • fullzones를 clear한 마지막 시간 jiffies가 들어간다.
    • fullzones의 정보는 1초 이상인 경우 유효되지 않도록하기 위해 zlc_setup() 함수에서 clear 할 때 시간 비교를 위해  사용한다.

 

per_cpu_pageset 구조체

include/linux/mmzone.h

struct per_cpu_pageset {
        struct per_cpu_pages pcp; 
#ifdef CONFIG_NUMA
        s8 expire;
#endif
#ifdef CONFIG_SMP
        s8 stat_threshold;
        s8 vm_stat_diff[NR_VM_ZONE_STAT_ITEMS];
#endif
};

 

boot_pageset 객체

mm/page_alloc.c

static DEFINE_PER_CPU(struct per_cpu_pageset, boot_pageset);

 

pageset

  • rpi2: ZONE_NORMAL만 사용한다.
$ cat /proc/zoneinfo
   (...생략...)
  pagesets
    cpu: 0
              count: 139
              high:  186
              batch: 31
  vm stats threshold: 24
    cpu: 1
              count: 84
              high:  186
              batch: 31
  vm stats threshold: 24
    cpu: 2
              count: 154
              high:  186
              batch: 31
  vm stats threshold: 24
    cpu: 3
              count: 137
              high:  186
              batch: 31

 

참고

 

답글 남기기

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