内存管理

13. 内存管理

13.1. 引言

Linux对物理内存的描述机制有两种:UMANUMALinux把物理内存划分为三个层次来管理:存储节点(Node)、管理区(Zone)和页面 Page)。UMA对应一致存储结构,它只需要一个Node就可以描述当前系统中的物理内存,但是NUMA的出现打破了这种平静,此时需要多个 Node,它们被统一定义为一个名为discontig_node_data的数组。为了和UMA兼容,就将描述UMA存储结构的描述符 contig_page_data放到该数组的第一个元素中。内核配置选项CONFIG_NUMA决定了当前系统是否支持NUMA机制。此时无论UMA NUMA,它们都是对应到一个类型为pg_data_t的数组中,便于统一管理。

 71. Node ZonePage的关系

 

上图描述Linux管理物理内存的三个层次之间的拓扑关系。从图中可以看出一个存储节点由pg_data_t描述,一个UMA系统中只有一个Node,而 NUMA中则可以存在多个Node。它由CONFIG_NODES_SHIFT配置选项决定,它是CONFIG_NUMA的子选项,所以只有配置了 CONFIG_NUMA,该选项才起作用。UMA情况下,NODES_SHIFT被定义为0MAX_NUMNODES也即为1

include/linux/numa.h

 

#ifdef CONFIG_NODES_SHIFT

#define NODES_SHIFT CONFIG_NODES_SHIFT

#else

#define NODES_SHIFT 0

#endif

 

#define MAX_NUMNODES (1 << NODES_SHIFT)

这里主要介绍UMA机制。contig_page_data被定义如下:

mm/page_alloc.c

struct pglist_data __refdata contig_page_data = { .bdata = &bootmem_node_data[0] };

EXPORT_SYMBOL(contig_page_data);

struct pglist_data即是pg_data_t的原型。了解pg_data_t中的结构成员对于了解内存管理是必经之路:

enum zone_type {

ZONE_DMA,

ZONE_NORMAL,

ZONE_MOVABLE,

......

__MAX_NR_ZONES

};

 

typedef struct pglist_data {

struct zone node_zones[MAX_NR_ZONES];

struct zonelist node_zonelists[MAX_ZONELISTS];

int nr_zones;

#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */

struct page *node_mem_map;

#ifdef CONFIG_CGROUP_MEM_RES_CTLR

struct page_cgroup *node_page_cgroup;

#endif

#endif

struct bootmem_data *bdata;

 

...... /* for CONFIG_MEMORY_HOTPLUG */

 

unsigned long node_start_pfn;

unsigned long node_present_pages; /* total number of physical pages */

unsigned long node_spanned_pages; /* total size of physical page

range, including holes */

int node_id;

wait_queue_head_t kswapd_wait;

struct task_struct *kswapd;

int kswapd_max_order;

} pg_data_t;

注意到zonelist中的_zonerefs元素,它用来实现分配器分配内存时候的管理区后援功能。MAX_ZONES_PER_ZONELIST被定 义为所有节点中包含的最多管理区的和并加上1,加1的目的是在后援链表中,可以检测是否遍历到最后一个节点了,如果是说明申请失败。

/* Maximum number of zones on a zonelist */

#define MAX_ZONES_PER_ZONELIST (MAX_NUMNODES * MAX_NR_ZONES)

 

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

};

节点中的管理区都在free_area_init_core函数中初始化。调用关系如下所示:

start_kernel->setup_arch->paging_init->bootmem_init->bootmem_free_node->free_area_init_node->free_area_init_core

在理想的计算机体系结构中,一个物理页框就是一个内存存储单元,可用于任何事情:存放内核数据和用户数据,磁盘缓冲数据等。热河中磊的数据页都可以存放在 任何页框中,没有什么限制。但是,实际的计算机体系结构有硬件的制约,这制约页框可以使用的方式。尤其是Linux内核必须处理80x86体系结构的两种 硬件约束:

最后一种限制不仅存在于80x86,而存在于所有的体系结构中。为了应对这两种限制,Linux把每个内存节点的物理内存划分为多个(通常为3个)管理区(zone)。在80x86 UMA体系结构中的管理区为:

对于ARM来说,ZONE_HIGHMEM被名为ZONE_MOVABLE的宏取代,而ZONE_DMA也不会仅限于最低的16MB,而可能对应所有的内存区域,此时只有内存节点ZONE_DMA有效,所以ZONE_DMA并不一定名副其实的用来作为DMA访问之用。

ZONE_DMAZONE_NORMAL区包含内存的"常规"页框,通过把它们线性的映射到线性地址的第4 GB0xc0000000-0xcfffffff),内核就可以直接访问。相反ZONE_HIGHMEM或者ZONE_MOVABLE区包含的内存页不 能由内核直接访问,尽管它们也线性地映射到了线性地址空间的第4GB。每个内存管理区都有自己的描述符struct zone。它用来保存管理区的跟踪信息:内存使用统计,空闲区,锁定区等。

include/linux/mmzone.h

struct zone {

/* Fields commonly accessed by the page allocator */

unsigned long pages_min, pages_low, pages_high;

 

unsigned long lowmem_reserve[MAX_NR_ZONES];

struct per_cpu_pageset pageset[NR_CPUS];

 

struct free_area free_area[MAX_ORDER];

 

ZONE_PADDING(_pad1_)

 

/* Fields commonly accessed by the page reclaim scanner */

spinlock_t lru_lock;

struct {

struct list_head list;

unsigned long nr_scan;

} lru[NR_LRU_LISTS];

 

unsigned long recent_rotated[2];

unsigned long recent_scanned[2];

 

unsigned long pages_scanned; /* since last reclaim */

unsigned long flags; /* zone flags, see below */

 

/* Zone statistics */

atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];

};

在申请内存时,会遇到两种情况:如果有足够的空闲页可用,请求就会被立刻满足;否则,必须回收一些内存,并且将发出请求的内核控制路径阻塞,直到有内存被 释放。不过有些内存请求不能被阻塞。这种情况发生在处理中断或在执行临界区内的代码时。在这些情况下,一条内核控制路径应使用原子内存分配请求 (GFP_ATOMIC)。原子请求从不被阻塞;如果没有足够的空闲页,则仅仅是分配失败而已。

内核为了尽可能保证一个原子内存分配请求成功,它为原子内存分配请求保留了一个页框池,只有在内存不足时才使用。保留内存的数量存放在min_free_kbytes变量中,单位为KB

mm/page_alloc.c

 

int min_free_kbytes = 1024;

.....

 

/* min_free_kbytes = sqrt(lowmem_kbytes * 16); */

lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE >> 10);

min_free_kbytes = int_sqrt(lowmem_kbytes * 16);

min_free_kbytes由当前直接映射区的物理内存数量决定。也即ZONE_DMAZONE_NORMAL内存管理区的可用页框数决定,这可以 通过nr_free_buffer_pages获取。尽管可以通过/proc/sys/vm/min_free_kbytes来修改该它的大小,但是 min_free_kbytes的初始值范围必须是[128K, 64M]。管理区描述符中的pages_min成员存储了管理区内保留页框的数目。这个字段与pages_lowpages_high字段一起被用在内 存分配和回收算法中。pages_low字段总是被设为pages_min的值的5/4,而pages_high则总是被设为pages_min的值的3 /2。这些值在模块快初始化module_init调用的init_per_zone_pages_min中被设置。

 27. 页面分配控制

名称

大小

pages_min

min_free_kbytes >> (PAGE_SHIFT - 10)

pages_low

pages_min * 5 / 4

pages_high

pages_min * 3 / 2

 

free_area_init_core中对管理区初始化的代码部分如下,后续章节将对该函数进一步分析。

        zone->spanned_pages = size;

        zone->present_pages = realsize;

 

        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->prev_priority = DEF_PRIORITY;

 

        zone_pcp_init(zone);

        for_each_lru(l) {

            INIT_LIST_HEAD(&zone->lru[l].list);

            zone->lru[l].nr_scan = 0;

        }

        zone->recent_rotated[0] = 0;

        zone->recent_rotated[1] = 0;

        zone->recent_scanned[0] = 0;

        zone->recent_scanned[1] = 0;

        zap_zone_vm_stats(zone);

        zone->flags = 0;

13.2. page管理项

struct page {

    unsigned long flags;        /* Atomic flags, some possibly

                     * updated asynchronously */

    atomic_t _count;        /* Usage count, see below. */

    union {

        atomic_t _mapcount;    /* Count of ptes mapped in mms,

                     * to show when page is mapped

                     * & limit reverse map searches.

                     */

        struct {        /* SLUB */

            u16 inuse;

            u16 objects;

        };

    };

每一个物理页框都需要一个对应的page结构来进行管理:记录分配状态,分配和回收,互斥以及同步操作。对该结构成员的解释如下:

include/linux/page-flags.h

enum pageflags {

PG_locked, /* Page is locked. Don't touch. */

PG_error,

PG_referenced,

PG_uptodate,

PG_dirty,

PG_lru,

PG_active,

......

__NR_PAGEFLAGS,

......

}

以上是页标志位的可能取值,通常不应该直接使用这些标志位,而应该内核预定义好的宏,它们在相同的头文件中被定义,但是它们是被间接定义的,也即通过##连字符来统一对它们进行定义。

#define TESTPAGEFLAG(uname, lname)                    

static inline int Page##uname(struct page *page)             

            { return test_bit(PG_##lname, &page->flags); }

......

TESTPAGEFLAG(Locked, locked)

PAGEFLAG(Error, error)

PAGEFLAG(Referenced, referenced) TESTCLEARFLAG(Referenced, referenced)

 28. 页标志宏函数

扩展函数/

用途

TESTPAGEFLAG(uname, lname)

Page##uname

测试PG_##lname

SETPAGEFLAG(uname, lname)

SetPage##uname

设置PG_##lname

CLEARPAGEFLAG(uname, lname)[a]

ClearPage##uname

清除PG_##lname

TESTSETFLAG(uname, lname)

TestSetPage##uname

测试并设置PG_##lname

TESTCLEARFLAG(uname, lname)

TestClearPage##uname

测试并清除PG_##lname

PAGEFLAG(uname, lname)[b]

TESTPAGEFLAG
SETPAGEFLAG
CLEARPAGEFLAG

当于同时扩展了三个宏,也即三个函数

PAGEFLAG_FALSE(uname)

Page##uname

永远返回0

TESTSCFLAG(uname, lname)

TESTSETFLAG
TESTCLEARFLAG

当于同时扩展了两个宏,也即两个函数

SETPAGEFLAG_NOOP(uname)

SetPage##uname

空操作

CLEARPAGEFLAG_NOOP(uname)

ClearPage##unam

空操作

__CLEARPAGEFLAG_NOOP(uname)

__ClearPage##uname

空操作

TESTCLEARFLAG_FALSE(uname)

TestClearPage##uname

永远返回0

[a] 以上三个宏分别对应test_bitset_bitclear_bit,是原子操作,与它们对应的是有三个开头
为下划线的同名函数__SETPAGEFLAG等与它们相对应,但不是原子操作,这里不再列出。

[b] 与此对应也有__PAGEFLAG的宏存在。

 


flags
实际上为两部分:标志区(Flags Area)从最低处向上扩展到第__NR_PAGEFLAGS;字段区(Fields Area)则从最高位向低位扩展。字段区用来实现管理区,内存节点和稀疏内存的映射。

| FIELD | ... | FLAGS |

N-1 ^ 0

(__NR_PAGEFLAGS)

_count引用计数不应被直接引用,内核提供了一系列的内联函数来操作它,通常它们被定义在include/linux/mm.h中。

 29. 页引用计数函数

函数名

用途

page_count

读取引用计数

get_page

引用计数加1

init_page_count

初始化引用计数为1


_mapcount
_count引用计数类似,不应被直接引用,内核提供了一系列的内联函数来操作它,它们也被定义在include/linux/mm.h中。

 30. 页引用计数函数

函数名

用途

reset_page_mapcount

初始化引用计数为-1[a]

page_mapcount

读取引用计数并加1的值

page_mapped

该函数根据引用计数值是否大于等于0,判断该页框是否被映射。

[a] 没有初始化为0是因为atomic_inc_and_testatomic_add_negative的操作,对该引用计数的加减是由这两个函数完成的。

    union {

        struct {

    unsigned long private;        /* Mapping-private opaque data:

                     * usually used for buffer_heads

                    * if PagePrivate set; used for

                     * swp_entry_t if PageSwapCache;

                    * indicates order in the buddy

                    * system if PG_buddy is set.

                         */

    struct address_space *mapping;    /* If low bit clear, points to

                    * inode address_space, or NULL.

                    * If page mapped as anonymous

                    * memory, low bit is set, and

                    * it points to anon_vma object:

                    * see PAGE_MAPPING_ANON below.

                        */

        };

#if USE_SPLIT_PTLOCKS

        spinlock_t ptl;

#endif

        struct kmem_cache *slab;    /* SLUB: Pointer to slab */

        struct page *first_page;    /* Compound tail pages */

    };

    union {

        pgoff_t index;        /* Our offset within mapping. */

        void *freelist;        /* SLUB: freelist req. slab lock */

    };

    struct list_head lru;        /* Pageout list, eg. active_list

    

由于内核引入了很多的分配机制,以往间简单的page结构变得越来越复杂。为了无缝引入slub分配器来分配小于1个页面的内存,这里使用共用体将 slab指针和复合页的首页指针与privatemapping公用存储空间。private的用途与flags标志位息息相关。如果设置了 PG_private,那么它被用于buffer_heads;如果设置了PG_swapcache,那么用于swp_entry_t;如果设置了 PG_buddy,则用于伙伴系统中的阶(Order)

内核可以将多个相邻的页框合并为复合页(Compound Page)。分组中的第一个也成为首页(Head Page),而所有其余各页叫做尾页(Tail Page)。所有尾页对应的管理page数据结构都将first_page指向首页。

mapping指定了页框所在的地址空间。index是页框在mapping映射内部的偏移量。mapping指针通常是对齐到sizeof(long)的,这保证它的最低位为0,但是它并总是如此。可以有以下两种可能:

lru是一个表头,用于在各种量表上维护该页框,以便将它按不同类别分组,最重要的就是zone->lru_lock保护的活动页框(active_list)和不活动页框。

#if defined(WANT_PAGE_VIRTUAL)

    void *virtual;            /* Kernel virtual address (NULL if

                     not kmapped, ie. highmem) */

#endif /* WANT_PAGE_VIRTUAL */

};

WANT_PAGE_VIRTUAL是由是否需要高端内存决定的,virtual用于寻址高端内存区域中的页框,存储该页的虚拟地址。有些时候高端内存并不映射到任何实际的物理地址页框上,此时它的值为NULL

13.3. bootmem_free_node

bootmem_init_node函数中,根据struct meminfo参数来初始化内存节点对应的pg_data_t数据结构contig_page_data,并且申请Bootmem机制使用的 bitmap。从该函数使用的参数来看,一个内存节点对应一个struct meminfo的内存块信息。所以一个内存节点有可能对应多个membank,而这些membank的物理地址可能是不连续的,这就是内存孔洞的存在的原 因。contig_page_data成员中的bdata->node_min_pfnbdata->node_low_pfn参数分别记 录了所有内存块中的最低物理页框地址和最高物理页框地址。

arch/arm/include/asm/setup.h

struct membank {

unsigned long start;

unsigned long size;

int node;

};

 

struct meminfo {

int nr_banks;

struct membank bank[NR_BANKS];

};

 

static unsigned long __init bootmem_init_node(int node, struct meminfo *mi);

bootmem_free_nodebootmem_init_node参数类似,它用来初始化特定内存节点的管理区信息。

arch/arm/mm/init.c

static void __init bootmem_free_node(int node, struct meminfo *mi);

#define arch_adjust_zones(node,size,holes) do { } while (0)

 72. bootmem_free_node调用流程

 

13.4. 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)

free_area_init_node的函数参数解释如下:

free_area_init_node完成以下功能:

13.5. 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

*/

static void __paginginit free_area_init_core(struct pglist_data *pgdat,

        unsigned long *zones_size, unsigned long *zholes_size);

free_area_init_core的函数参数解释如下:

free_area_init_core针对单个内存节点内的所有管理区进行初始化,并计算管理内存页所用的struct page数组占用的memmap_pages

接下来遍历内存节点中的所有管理区,完成以下工作:

13.6. memmap_init_zone

memmap_init_zone函数初始化每个管理区中的页帧对应的page数组。

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

 

void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,

        unsigned long start_pfn, enum memmap_context context);

mem_init_zone通过memmap_init宏实现调用,由于一些特定的架构,系统公共的memmap初始化函数无法满足需求,比如IA64 此时在特定架构的代码中会定义memmap_initmemmap_init_zone顾名思义,它是针对单个管理区对应的page数组来初始化的。

memmap_init_zone依次完成了以下功能:

include/asm-generic/memory_model.h

#if defined(CONFIG_FLATMEM)

#ifndef ARCH_PFN_OFFSET

#define ARCH_PFN_OFFSET (0UL)

#endif

......

#define __pfn_to_page(pfn) (mem_map + ((pfn) - ARCH_PFN_OFFSET))

#define __page_to_pfn(page) ((unsigned long)((page) - mem_map) +

ARCH_PFN_OFFSET)

......

#ifdef CONFIG_OUT_OF_LINE_PFN_TO_PAGE

struct page;

/* this is useful when inlined pfn_to_page is too big */

extern struct page *pfn_to_page(unsigned long pfn);

extern unsigned long page_to_pfn(struct page *page);

#else

#define page_to_pfn __page_to_pfn

#define pfn_to_page __pfn_to_page

#endif /* CONFIG_OUT_OF_LINE_PFN_TO_PAGE */

struct page管理数组是线性分布的时候,pfn_to_page被统一定义为__pfn_to_page。平坦内存中的ARCH_PFN_OFFSET被定义 0,而mem_mapalloc_node_mem_map中被赋值为node_mem_map,也即管理数组的首地址。

pageflags成员的Field Area由三部分组成,它们从高地址位该是依次分布。段区只有在配置了CONFIG_SPARSEMEM时才有可能存在。

/* .....

*

* No sparsemem or sparsemem vmemmap: | NODE | ZONE | ... | FLAGS |

* classic sparse with space for node:| SECTION | NODE | ZONE | ... | FLAGS |

* classic sparse no space for node: | SECTION | ZONE | ... | FLAGS |

*/

13.7. build_all_zonelists

build_all_zonelistsinit/main.c中的start_kernel中被调用,它用来初始化内存分配器使用的存储节点中的管理区链表。

include/linux/kernel.h

extern enum system_states {

SYSTEM_BOOTING,

SYSTEM_RUNNING,

SYSTEM_HALT,

SYSTEM_POWER_OFF,

SYSTEM_RESTART,

SYSTEM_SUSPEND_DISK,

} system_state;

 

mm/page_alloc.c

 

void build_all_zonelists(void)

{

    set_zonelist_order();

 

    if (system_state == SYSTEM_BOOTING) {

        __build_all_zonelists(NULL);

        mminit_verify_zonelist();

        cpuset_init_current_mems_allowed();

    } else {

        /* we have to stop all cpus to guarantee there is no user

         of zonelist */

        stop_machine(__build_all_zonelists, NULL, NULL);

        /* cpuset refresh routine should be here */

    }

    vm_total_pages = nr_free_pagecache_pages();

 

    if (vm_total_pages < (pageblock_nr_pages * MIGRATE_TYPES))

        page_group_by_mobility_disabled = 1;

    else

        page_group_by_mobility_disabled = 0;

 

    printk("Built %i zonelists in %s order, mobility grouping %s. "

        "Total pages: %ld ",

            num_online_nodes(),

            zonelist_order_name[current_zonelist_order],

            page_group_by_mobility_disabled ? "off" : "on",

            vm_total_pages);

}

build_all_zonelists依次完成以下功能:

pageblock_orderfree_area_init_core中被初始化为伙伴系统中的最高阶,pageblock_nr_pages * MIGRATE_TYPES的用于在于可以确保当前内存可以满足最高阶链表中至少有一个可分配节点的存在。

include/linux/pageblock-flags.h

#define pageblock_nr_pages (1UL << pageblock_order)

一个拥有256MB的内存开发板上的管理区参考链表信息如下:

Built 1 zonelists in Zone order, mobility grouping on. Total pages: 65024

build_all_zonelists调用了一些列函数,调用流程如下所示:

 73. 内存管理区初始化流程图

 

13.8. __build_all_zonelists

/* return values int ....just for stop_machine() */

static int __build_all_zonelists(void *dummy)

{

    int nid;

 

    for_each_online_node(nid) {

        pg_data_t *pgdat = NODE_DATA(nid);

 

        build_zonelists(pgdat);

        build_zonelist_cache(pgdat);

    }

    return 0;

}

__build_all_zonelistsdummy参数没有任何意义,并且返回值永远为0,这是为了方便stop_machine对其结果作为参数引用。build_zonelist_cacheCONFIG_NUMA相关,它用来设置zlcache相关的成员。

static void build_zonelist_cache(pg_data_t *pgdat)

{

    pgdat->node_zonelists[0].zlcache_ptr = NULL;

}

13.9. build_zonelists

static void build_zonelists(pg_data_t *pgdat)

{

    int node, local_node;

    enum zone_type j;

    struct zonelist *zonelist;

 

    local_node = pgdat->node_id;

 

    zonelist = &pgdat->node_zonelists[0];    

    j = build_zonelists_node(pgdat, zonelist, 0, MAX_NR_ZONES - 1);

 

    for (node = local_node + 1; node < MAX_NUMNODES; node++) {

        if (!node_online(node))

            continue;

        j = build_zonelists_node(NODE_DATA(node), zonelist, j,

                            MAX_NR_ZONES - 1);

    }

    for (node = 0; node < local_node; node++) {

        if (!node_online(node))

            continue;

        j = build_zonelists_node(NODE_DATA(node), zonelist, j,

                            MAX_NR_ZONES - 1);

    }

 

    zonelist->_zonerefs[j].zone = NULL;

    zonelist->_zonerefs[j].zone_idx = 0;

}

注意到最后一个_zonerefs元素的zone被设置为NULLzone_idx0,这是用来遍历时判断结尾。

13.10. build_zonelists_node

static int build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist,

                int nr_zones, enum zone_type zone_type)

{

    struct zone *zone;

 

    BUG_ON(zone_type >= MAX_NR_ZONES);

    zone_type++;

 

    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;

}

build_zonelists_node针对单个存储节点初始化分配器的管理区列表。zone_type用来指明当前处理的管理区的个数,通常它就是 MAX_NR_ZONES - 1nr_zones则指明了将被填充的_zonerefs管理区备用链表的索引,通常该索引之前的成员已经按内存优先级进行了赋值。循环中 zone_type一直递减,而nr_zones索引一直递减,所以在UMA系统中,编号越大的管理区类型优先级越高,将被最先挂载到备用链表。通常 ZONE_HIGHMEM的优先级最高,而ZONE_DMA的优先级最低。所以分配器在分配内存的时候通常在ZONE_HIGHMEM中分配。 populated_zone函数用来确保该区的可用内存页(present_pages)有效,也即大于0。在内存少于896MB的系统 上,ZONE_HIGHMEM的有效内存页就为0,此时只有到ZONE_NORMAL或者ZONE_DMA区分配内存。

build_zonelists_node完成了以下功能,当未开启CONFIG_NUMA时,依次映射管理区到参考链表中。

static void zoneref_set_zone(struct zone *zone, struct zoneref *zoneref)

{

    zoneref->zone = zone;

    zoneref->zone_idx = zone_idx(zone);

}

注意:zoneref同时定义zone的地址和索引,看起来多此一举,这是因为在NUMA中,可能将另一个Node中的zone加入本Node中的参考链表中。

 

原文地址:https://www.cnblogs.com/liujinghuan/p/5841713.html