文章

linux内核笔记(六)启动时的内存初始化

启动时的内存初始化

涉及硬件架构相关的功能均以i386结构为例 (经典的x86 且有高端内存的处理逻辑)

系统引导时,内核在物理内存中的结构

ia32 boot

引导将内核载入到内存中,并跳转到相应的内存地址开始执行内核代码,在ia32架构下,内核在内存中的架构如上。

_text_etext_edata_end的具体地址在编译后的System.map下,以线性地址表示,如:

0xC0100000_text
0xC0381ecd_etext
0xC04704e0_edata
0xC04c3f44_end

内核启动时的内存相关处理

start_kernel memory init

以上逻辑在init/main.c文件的start_kernel

  • setup_arch 体系结构相关,其主要负责收集内存区域,建立分页机制。
  • setup_per_cpu_areas 体系结构相关,负责内存相关的每个CPU变量(使用__percpu修饰,例如上一文中的zone.pageset)的内存空间申请与数据初始化
  • build_all_zonelists 建立结点和内存域的数据结构
  • mm_init 建立内核的内存分配器并替代memblock分配器
    • mem_init
    • kmem_cache_init
    • percpu_init_late
    • pgtable_cache_init
    • vmalloc_init
  • setup_per_cpu_pageset 初始化CPU高速缓存行,为第一个cpu分配内存

启动阶段的内存分配

在内核启动完成之前,内核就需要使用内存,而内存管理模块还没有初始化好,在这期间,内核使用了一个额外的简化形式的内存管理模块memblock分配器,而在内存管理模块初始化完成后,内核将弃用memblock并转交给内核内存分配器开始管理内存。

该工作在2.6的早期版本中由bootmem分配器负责,在2.6.39.4默认启用较为灵活和更有适用性的memlock分配器。两者的区别如下:

特性bootmem分配器memblock分配器
数据结构位图存储内存使用信息数组存储可用内存信息
初始化需收集完可用内存区域后统一初始化分配器可灵活增加删除可用内存区
内存分配1为已分配0为未使用,分配器逐位扫描位图,直至找到一个能提供足够连续页的位置将内存记录从memory移到reserve
用途系统启动后被Buddy系统取代系统启动后与Buddy系统协同工作

memblock的数据结构

memblock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define INIT_MEMBLOCK_REGIONS   128 
#define MEMBLOCK_ERROR      0

struct memblock_region {
    phys_addr_t base;   //region的起始地址
    phys_addr_t size;   //region的长度
};

struct memblock_type {
    unsigned long cnt;                  //region的数量
    unsigned long max;                  //最多包含多少个region,默认为128个
    struct memblock_region *regions;    //regions数组首地址
};

struct memblock {
    phys_addr_t current_limit;      //当前可分配的最高物理地址
    phys_addr_t memory_size;        //memblock.memory.regions.size的总和
    struct memblock_type memory;    //可以被分配的内存
    struct memblock_type reserved;  //已经被分配的内存
};

extern struct memblock memblock;

memblock的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#define INIT_MEMBLOCK_REGIONS   128
int memblock_debug __initdata_memblock;
int memblock_can_resize __initdata_memblock;
static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS + 1] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS + 1] __initdata_memblock;


/*
* 在内核启动时,head_32.S中的汇编程序会跳往head32.c中的`i386_start_kernel`函数,此函数第一行即是调用memblock.h中的memblock_init函数,初始化memblock
*/
void __init memblock_init(void)
{
    memblock.memory.regions = memblock_memory_init_regions;
    memblock.memory.max     = INIT_MEMBLOCK_REGIONS;
    memblock.reserved.regions   = memblock_reserved_init_regions;
    memblock.reserved.max   = INIT_MEMBLOCK_REGIONS;
    memblock.memory.regions[INIT_MEMBLOCK_REGIONS].base = (phys_addr_t)RED_INACTIVE;
    memblock.reserved.regions[INIT_MEMBLOCK_REGIONS].base = (phys_addr_t)RED_INACTIVE;
    memblock.memory.regions[0].base = 0;
    memblock.memory.regions[0].size = 0;
    memblock.memory.cnt = 1;
    memblock.reserved.regions[0].base = 0;
    memblock.reserved.regions[0].size = 0;
    memblock.reserved.cnt = 1;
    memblock.current_limit = MEMBLOCK_ALLOC_ANYWHERE;
}

memblock的增加内存

随着内核的启动,x86将会查找出可用的物理内存,并将这些内存添加到memblock中;无论是添加可使用的memory类型memblock_add,还是添加保留的reserve类型memblock_reserve,其最终都将工作交由memblock_add_range函数完成。此函数简单来说就是将内存区添加到已有region中,如果不邻则新建个region保存。 回顾起始图,x86架构在setup_arch函数中,能过e820找到内存即使用此函数添加到memblock中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/*
* memblock_type memblock_add传递memblock.memory  memblock_reserve传递memblock.reserved
* base 起始地址
* size 内存长度
*/
static long __init_memblock memblock_add_region(struct memblock_type *type,
                    phys_addr_t base, phys_addr_t size)
{
    phys_addr_t end = base + size;
    int i, slot = -1;
    /* 从数组头部遍历region,尝试将此区域合并到已在的region中 */
    for (i = 0; i < type->cnt; i++) {
        struct memblock_region *rgn = &type->regions[i];
        phys_addr_t rend = rgn->base + rgn->size;

        /* 当前region不可能合并 */
        if (rgn->base > end || rgn->size == 0)
            break;

        /* 此内存已经在region中了 */
        if (rgn->base <= base && rend >= end)
            return 0;

        /* 检查内存是否与region的base重叠或相邻 */
        if (base < rgn->base && end >= rgn->base) {
           
           /* 内存是否能合并  默认返回1 一些特定架构可以重新实现 */
            if (!memblock_memory_can_coalesce(base, size,
                                rgn->base,
                                rgn->size)) { 
                /* 一般来说 重叠是肯定能合并的 如果架构实现的是不能合并 将会告警*/
                WARN_ON(end != rgn->base);
                /* 如果不能合并 就新增一块region */
                goto new_block;
            }
            /* 将region底部延伸到内存的起始处 */
            rgn->base = base;
            rgn->size = rend - base;

            /* 顶部无须处理 */
            if (rend >= end)
                return 0;

            /* 地址指向region顶部 */
            base = rend;
            size = end - base;
        }

        /* 处理顶部 */
        if (base <= rend && end >= rend) {
            if (!memblock_memory_can_coalesce(rgn->base,
                                rgn->size,
                                base, size)) {
                WARN_ON(rend != base);
                goto new_block;
            }
            size += (base - rgn->base);
            base = rgn->base;
            /* 将当前region从type中移除,尝试将被移除的region 合并到数组的下一轮region里 */
            memblock_remove_region(type, i--);
        }
    }

    /* 如果是type的第一个region 直接替换初始化region的底部与顶部 */
    if ((type->cnt == 1) && (type->regions[0].size == 0)) {
        type->regions[0].base = base;
        type->regions[0].size = size;
        return 0;
    }

new_block:
    /* 超出扩容限制 */
    if (WARN_ON(type->cnt >= type->max))
        return -1;

    /* 不能合并region,把内存区动态添加到type的regions排序数组里  从数组尾部依次比较底部位置*/
    for (i = type->cnt - 1; i >= 0; i--) {
        if (base < type->regions[i].base) {
            type->regions[i+1].base = type->regions[i].base;
            type->regions[i+1].size = type->regions[i].size;
        } else {
            type->regions[i+1].base = base;
            type->regions[i+1].size = size;
            slot = i + 1;
            break;
        }
    }
    if (base < type->regions[0].base) {
        type->regions[0].base = base;
        type->regions[0].size = size;
        slot = 0;
    }
    type->cnt++;

    /* 如果数组满了就尝试通过memblock_double_array扩容,扩容失败移除此次内存增加 */
    if (type->cnt == type->max && memblock_double_array(type)) {
        BUG_ON(slot < 0);
        memblock_remove_region(type, slot);
        return -1;
    }
    return 0;
}

memblock的内存分配与释放

mm/nobootmem.c文件中,实现了历史中的bootmem相关的内存分配(最终交由__alloc_memory_core_early函数)与释放函数free_bootmem

如果手动修改了编译配置中的CONFIG_NO_BOOTMEM项,将用mm/bootmem.c继续沿用老的bootmem分配器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static void * __init __alloc_memory_core_early(int nid, u64 size, u64 align,
                    u64 goal, u64 limit)
{
    void *ptr;
    u64 addr;
    if (limit > memblock.current_limit)
        limit = memblock.current_limit;
    
    /* 从上至下搜索memblock.memory中的regions 找到合适的region  此方法内会过滤掉在reserve区中的内存段*/
    addr = find_memory_core_early(nid, size, align, goal, limit);
    if (addr == MEMBLOCK_ERROR)
        return NULL;

    ptr = phys_to_virt(addr);
    /* 将此内存清空 */
    memset(ptr, 0, size);
    /* 将此内存添加至memblock.reserve区 */
    memblock_x86_reserve_range(addr, addr + size, "BOOTMEM");
    /* 如果启用了内存泄露检测 则测试*/
    kmemleak_alloc(ptr, size, 0, 0);
    return ptr;
}

void __init free_bootmem(unsigned long addr, unsigned long size)
{
    kmemleak_free_part(__va(addr), size);
    /* 工作交由给了arch/x86/mm/memblock.c  其转交给了mmblock.c中的__memblock_remove函数 */
    memblock_x86_free_range(addr, addr + size);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* 释放memblock内存的实现 这里的type一般是&memblock.memory */
static long __init_memblock __memblock_remove(struct memblock_type *type,
                        phys_addr_t base, phys_addr_t size)
{
    phys_addr_t end = base + size;
    int i;

    for (i = 0; i < type->cnt; i++) {
        struct memblock_region *rgn = &type->regions[i];
        phys_addr_t rend = rgn->base + rgn->size;

        /* 由于排过序  这里没有找到此段内存 退出 */
        if (rgn->base > end || rgn->size == 0)
            break;

        /* 要释放的内存完全覆盖了此region  删除region */
        if (base <= rgn->base && end >= rend) {
            memblock_remove_region(type, i--);
            continue;
        }

        /* region完全覆盖了此内存 分割region */
        if (base > rgn->base && end < rend) {
            rgn->size = base - rgn->base;
            if (!memblock_add_region(type, end, rend - end))
                return 0;
            /* 分割失败  还原 */
            rgn->size = rend - rgn->base;
            WARN_ON(1);
            return -1;
        }

        /* 分割后的顶部与底部的处理 */
        if (rgn->base < end && rend > end) {
            rgn->size -= end - rgn->base;
            rgn->base = end;
            break;
        }
        if (base < rend)
            rgn->size -= rend - base;

    }
    return 0;
}

地址空间的划分

在IA-32系统上内核通常将总的4GiB可用虚拟地址空间按默认3 : 1的比例划分给用户状态应用程序内存空间和内核专用内存空间。如此划分有以下几个动机

  • 安全保护: 每个用户在自己的虚拟地址空间运行,彼此隔离,防止彼此造成的内存影响。
  • 权限控制: 内核地址空间的数据只允许内核代码完全访问控制,而用户态程序则相反,需要经过内核特权许可。
  • 内存管理: 内核可以针对用户空间与内核空间其特性,实现不同的内存管理维护策略。
  • 性能: 内核空间的数据由于有特殊的策略(快速申请内存处理、内核缓存、不需权限检查等)具备更高的性能。

内核虚拟地址空间的分段

由于整个内核空间只有1GiB,却要管理整个系统的内存,因此内核空间又划分了几个段来维护不同类型的内存。 kernel memory segment

对于前896MiB的线性映射区,所有架构必须实现两个宏(__pa(vaddr)__va(paddr))提供线性虚拟地址与物理地址的转换。

而对于无法直接映射的内存区,内核使用后面的128MiB提供间接的映射,此区域被分成了3个段:

  • vmalloc: 物理内存不连续但需要在虚拟空间中连续的的内存分配在此区域
  • 持久映射: Permanent Mapping 内核将物理地址持久映射到此区域,在没有明确解除映射之前,此映射将一直保持。由PKMAP_BASE定义起始地址,长度为LAST_PKMAP个页。由函数kmap申请,kunmap释放,如果申请时地址被用完,将会阻塞。
  • 固定映射: 非阻塞的映射,每个CPU具有一定数量(KM_TYPE_NR)的窗口,但生命周期是临时性的。由函数kmap_atomic申请,kunmap_atomic释放。

以上各段的划分工作在setup_arch函数调用的paging_init完成,

paging_init

内存结点的初始化

系统启动函数中,在特定的体系结构中检测内存并确定系统中内存的分配情况,接下来会执行内存管理的初始化,此时,已经对各种系统内存模式生成了一个pgdat_t实例,用于保存诸如结点中内存数量以及内存在各个内存域之间分配情况的信息。所有平台上都实现了特定于体系结构的NODE_DATA宏,用于通过结点编号,来查询与一个NUMA结点相关的pgdat_t实例。

early_node_map用于记录系统内存节点的信息,内核在setup_arch->initmem_init->memblock_x86_register_active_regions函数中将可用的内存从memblock注册到early_node_map中,以便在后续让内核了解内存的布局与空洞并分配内存结点、zone的内存大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 声明在mm/page_alloc.c中 */
static struct node_active_region __meminitdata early_node_map[MAX_ACTIVE_REGIONS];

struct node_active_region {
    unsigned long start_pfn;    /* 起始页帧号 */
    unsigned long end_pfn;      /* 结束页帧号 */
    int nid;                    /* 内存结点号 */
};

/* uma系统只有一个内存结点,使用一个pgdat_t实例管理所有系统内存 x86默认声明在nobootmem.c */
#define NODE_DATA(nid)      (&contig_page_data) 
/* 返回结点页  这里直接返回全局page数组 mem_map的索引称为页帧号 pfn  */
#define NODE_MEM_MAP(nid)   mem_map

free_area_init_nodes函数中,内核为每个node的每个zone计算了可使用的最低和最高的页帧编号,计算其可用内存和内存空洞,并将内存页初始化为struct page,初始化结点下的node_mem_map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
static unsigned long __meminitdata arch_zone_lowest_possible_pfn[MAX_NR_ZONES];
static unsigned long __meminitdata arch_zone_highest_possible_pfn[MAX_NR_ZONES];
void __init free_area_init_nodes(unsigned long *max_zone_pfn)
{
    unsigned long nid;
    int i;
    /* 给early_node_map按页帧编号排序 */
    sort_node_map();
    /* 初始化全局页帧边界变量 */
    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;

    /* 如果配置了可移动内存域,计算其可用页帧 */
    memset(zone_movable_pfn, 0, sizeof(zone_movable_pfn));
    find_zone_movable_pfns_for_nodes(zone_movable_pfn);

    /* 打印各内存域的页帧范围 early_node_map的页帧信息 */
    ......

    /* 打印与校验一些内存相关的偏移与掩码 */
    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); //从ealy_node_map中提取第min_pfn
        /* 如果结点内有可用内存,打上node_status枚举的N_HIGH_MEMORY,表示此结点有普通或高端内存 */
        if (pgdat->node_present_pages)
            node_set_state(nid, N_HIGH_MEMORY);
        /* 如果设置了高端内存 此函数将会进一步区分结点是否包含普通内存 */
        check_for_regular_memory(pgdat);
    }
}
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);
    pgdat->node_id = nid;
    pgdat->node_start_pfn = node_start_pfn;
    /* 通过early_node_map与全局页帧边界校验,计算节点内可用的页帧(计算并过滤内存空洞) */
    calculate_node_totalpages(pgdat, zones_size, zholes_size);
    /* 
     * 初始化结点内存下的struct page实例,并将该结点下的node_mem_map指向第一个page
     * 如果结点id是0,还会将全局变量mem_map指向该结点的node_mem_map
     */
    alloc_node_mem_map(pgdat);
#ifdef CONFIG_FLAT_NODE_MEM_MAP
    /* 可通过dmesg 回溯查看 */
    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, zones_size, zholes_size);
}

内存域的初始化

free_area_init_node函数将初始化内存域zone的繁重工作交由给free_area_init_core完成,其完成了所有类型的zone的数据结构、page、pageset、扫描统计数据结构初始化,计算了zone的起始页帧结束页帧、实际可用内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
static void __paginginit free_area_init_core(struct pglist_data *pgdat,
        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);
    pgdat->nr_zones = 0;
    init_waitqueue_head(&pgdat->kswapd_wait);
    pgdat->kswapd_max_order = 0;
    pgdat_page_cgroup_init(pgdat);

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

        /*计算此内存域的realsize(实际可用内存)对齐了内存长度、减去了空洞与系统保留内存,*/
        size = zone_spanned_pages_in_node(nid, j, zones_size);
        realsize = size - zone_absent_pages_in_node(nid, j,
                                zholes_size);
        memmap_pages =
            PAGE_ALIGN(size * sizeof(struct page)) >> PAGE_SHIFT;
        if (realsize >= memmap_pages) {
            realsize -= 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 realsize %lu\n",
                zone_names[j], memmap_pages, realsize);
        if (j == 0 && realsize > dma_reserve) {
            realsize -= dma_reserve;
            printk(KERN_DEBUG "  %s zone: %lu pages reserved\n",
                    zone_names[0], dma_reserve);
        }

        if (!is_highmem_idx(j))
            nr_kernel_pages += realsize; /* 只统计非高端内存 */
        nr_all_pages += realsize; /* 统计所有内存 */

        zone->spanned_pages = size;
        zone->present_pages = realsize;
    #ifdef CONFIG_NUMA
        zone->node = nid;
        zone->min_unmapped_pages = (realsize*sysctl_min_unmapped_ratio)
                        / 100;
        zone->min_slab_pages = (realsize * sysctl_min_slab_ratio) / 100;
    #endif
        zone->name = zone_names[j];
        /* 检查并初始化zone的各段锁 */
        spin_lock_init(&zone->lock);
        spin_lock_init(&zone->lru_lock);
        zone_seqlock_init(zone);
        zone->zone_pgdat = pgdat;
        /* 将zone->pageset 置为 &boot_pageset 每个cpu的pageset在build_all_zonelists函数中初始化 */
        zone_pcp_init(zone);
        /* 初始化扫描统计数据 */
        for_each_lru(l) {
            INIT_LIST_HEAD(&zone->lru[l].list);
            zone->reclaim_stat.nr_saved_scan[l] = 0;
        }
        zone->reclaim_stat.recent_rotated[0] = 0;
        zone->reclaim_stat.recent_rotated[1] = 0;
        zone->reclaim_stat.recent_scanned[0] = 0;
        zone->reclaim_stat.recent_scanned[1] = 0;
        /* 重置zone->vm_stat统计数据*/
        zap_zone_vm_stats(zone);
        /* 清空标志 */
        zone->flags = 0;
        if (!size)
            continue;
        /* 检测配置是否可以开启迁移类型  并设置zone下page页的可迁移类型状态位图 */
        set_pageblock_order(pageblock_default_order());
        setup_usemap(pgdat, zone, size);
        /* 初始化free_area、wait_table相关队列的zone结构 (主要申请内存) */
        ret = init_currently_empty_zone(zone, zone_start_pfn,
                        size, MEMMAP_EARLY);
        BUG_ON(ret);
        /* 遍历所有页帧,将页帧的flag打上zone与结点的标记,重置页帧的映射计数,设置页帧的迁移类型标记 */
        memmap_init(size, nid, j, zone_start_pfn);
        zone_start_pfn += size;
    }
}

内存域备用列表、pageset的初始化

build_all_zonelists将内存结点的初始化工作交由给了中的__build_all_zonelists()函数,并传递了从early_node_map中遍历出来的最小页帧min_pfn当参数,后者将按zone_type的优先级,依次计算各内存域的备用域列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
enum zone_type {
#ifdef CONFIG_ZONE_DMA
    /* (Direct Memory Access)直接内存访问,给硬件子系统独立地直接读写系统内存。 */
    ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
    ZONE_DMA32,
#endif
    /* 可直接映射地址的内存,在32位系统中指虚拟地址空间中低位的那896MiB */
    ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
    /* 高端内存区,不可直接访问的 */
    ZONE_HIGHMEM,
#endif
    /* 虚拟内存域,需要管理员手动激活。在需要时可以被迁移的内存区,用于避免内存碎片与动态内存管理 */
    ZONE_MOVABLE,   // 
    __MAX_NR_ZONES
};

#define MAX_ZONES_PER_ZONELIST (MAX_NUMNODES * MAX_NR_ZONES)
struct zonelist {
    //内存结点中的备用内存zonelist结构
    struct zonelist_cache *zlcache_ptr;         // NULL or &zlcache
    struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];
#ifdef CONFIG_NUMA
    struct zonelist_cache zlcache;
#endif
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
static __init_refok int __build_all_zonelists(void *data)
{
    int nid;
    int cpu;
    for_each_online_node(nid) {
        pg_data_t *pgdat = NODE_DATA(nid);
        /* */
        build_zonelists(pgdat);
        build_zonelist_cache(pgdat);
    }

    /* 利用per_cpu宏 初始化每个cpu的pageset的count、high、batch、lists */
    for_each_possible_cpu(cpu) {
        setup_pageset(&per_cpu(boot_pageset, cpu), 0);
    }

    return 0;
}


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);
    }
    //将最后一个备用结点设置为NULL 标志备用结点结束
    zonelist->_zonerefs[j].zone = NULL;
    zonelist->_zonerefs[j].zone_idx = 0;
}
static int build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist,
                int nr_zones, enum zone_type zone_type)
{
    struct zone *zone;
    zone_type++;
    do {
        //按照zone_type枚举的内存区按照昂贵程序倒序分配备用结点与内存区
        zone_type--;
        zone = pgdat->node_zones + zone_type;
        if (populated_zone(zone)) { //确认结点中的当前类型的内存zone有空间
            //将结点中的当前类型的内存zone设置到备用结点zonelist中
            zoneref_set_zone(zone,
                &zonelist->_zonerefs[nr_zones++]);
            check_highest_zone(zone_type);
        }
    } while (zone_type);
    return nr_zones;
}

build_zone_list function

本文由作者按照 CC BY 4.0 进行授权