Linux 内存管理框架

 

目录

1.    内存管理框架概览    2

1.1.    内存管理各个数据结构之间关系    2

1.2.    主要数据结构说明    3

2.    MEMEBLOCK    4

2.1.    memblock数据结构    4

2.2.    内存信息获取    4

2.3.    MEMBLOCK函数接口    5

3.    内核页表    6

3.1.    页表结构    6

3.2.    内核页表建立过程    7

4.    内存节点的建立    10

5.    创建zonelists与冷热页链表    14

5.1主要代码流程    14

5.2创建zonelists    15

5.3创建冷热页链表    16

6.    watermark设置    17

6.1水印设置    17

6.2设置预留内存页    18

  1. 内存管理框架概览

    1. 内存管理各个数据结构之间关系

 

  1. 主要数据结构说明

struct pglist_data

struct zone node_zones[MAX_NR_ZONES]

节点中包含的所有内存域

sstruct zonelist node_zonelists[MAX_ZONELISTS]

包含ZONELIST_FALLBACK和ZONELIST_NOFALLBACK两个链表,设置如果在一个内存域中分配失败的备用zone列表

int nr_zones

节点中包含的zone个数

struct page *node_mem_map

Page map,节点上所有页的struct page数组

struct bootmem_data *bdata

如果使用了自举分配器指向struct bootmem_data

unsigned long node_start_pfn

节点的起始页帧号

unsigned long node_present_pages

节点上包含的页数,不包含空洞

unsigned long node_spanned_pages

节点上所有页数包含空洞

int node_id

节点编号

struct task_struct *kswapd

内存交换进程

struct task_struct *kcompactd

内存压缩进程

unsigned long totalreserve_pages

每个节点预留的页,这些页不能用于应用程序内存分配

unsigned long min_unmapped_pages

内存回收的阀值,如果unmapped 页达到这个值

unsigned long min_slab_pages

如果用于slab的页达到这个值就缓存收缩

struct lruvec lruvec 

活动/不活动链表用于内存回收页扫描

atomic_long_t vm_stat[NR_VM_NODE_STAT_ITEMS]

节点上各种类型页的统计

 

struct zone

unsigned long watermark[NR_WMARK]

内存域的三个水印值:WMARK_MIN,WMARK_LOW,WMARK_HIGH

long lowmem_reserve[MAX_NR_ZONES]

用于指定预留内存页用于无论如何都不能分配失败的操作

int node

指定内存域所属的节点

struct pglist_data *zone_pgdat

指向内存域所属的节点

struct per_cpu_pageset __percpu *pageset

冷热页缓存

unsigned long *pageblock_flags

位表,每4个bits用于标识一个pageblock_order 的迁移类型

unsigned long zone_start_pfn

内存域的起始页帧号

unsigned long managed_pages

伙伴系统中管理的页数

unsigned long spanned_pages

内存域中所有页包含空洞

unsigned long present_pages

内存域中页数不包含空洞

const char *name

内存域名"DMA"'HITGHMEM'等

struct free_area free_area[MAX_ORDER]

伙伴系统链表

atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS]

内存域中各种类型页统计

  1. MEMEBLOCK

    1. memblock数据结构

memblock用于开机阶段的内存管理。系统定义了连个静态数组memblock_memory和memblock_reserved用于管理内存。这个阶段内存以内存区块来管理,内存区块由结构体struct memblock_region来描述。其中memblock_memory中包含系统中所内存区块,数组memblock_reserve中包含系统中保留的或者被分配出去的内存区块。

memblock.h
#define INIT_MEMBLOCK_REGIONS   128 
//数组最大容纳128个区块,如果超过这个限制将重新分配和一个区块管理数组并且是原来
//的两倍大小
struct memblock_region {//描述一个内存区块    
    phys_addr_t base; //区块起始地址 
    phys_addr_t size;//区块大小
    unsigned long flags;
};
memblock.c
static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] 
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS] 
    
struct memblock memblock __initdata_memblock = {
    .memory.regions     = memblock_memory_init_regions,
    .memory.cnt     = 1,  //表示内存块的数量,还没有插入内存块设置为1
    .memory.max     = INIT_MEMBLOCK_REGIONS, //数组最大容纳区块数
    .memory.name        = "memory",//内存数组名

    .reserved.regions   = memblock_reserved_init_regions,
    .reserved.cnt       = 1,  
    .reserved.max       = INIT_MEMBLOCK_REGIONS,
    .reserved.name      = "reserved",
	……
    .bottom_up      = false,
    .current_limit      = MEMBLOCK_ALLOC_ANYWHERE,
};  

  1. 内存信息获取

系统启动阶段BootLoader将fdt存放起始地址传递到内核。进入内核之后fdt起始地址将被保存到全局变量__fdt_pointer中

__fdt_pointer中
__primary_switched:
    adrp    x4, init_thread_union
    add sp, x4, #THREAD_SIZE
    adr_l   x5, init_task
    msr sp_el0, x5          // Save thread_info

    adr_l   x8, vectors         // load VBAR_EL1 with virtual
    msr vbar_el1, x8            // vector table address
    isb
    stp xzr, x30, [sp, #-16]!
    mov x29, sp

    str_l   x21, __fdt_pointer, x5      // Save FDT pointer 保存fdt起始地址到__fdt_pointer中

启动阶段内核会解析fdt,其中包含解析内存信息,其流程如下:




setup_machine_fdt:传入参数__fdt_pointer,从fdt中解析机器相关的一些信息
fixmap_remap_fdt:将__fdt_pointer到fdt末尾的物理地址区间映射到内核虚拟地址空间 FIX_FDT到
        __end_of_permanent_fixed_addresses中
early_init_dt_scan:解析一些启动阶段必要的信息,比如cmdline,memory等信息
of_flat_dt_get_machine_name:解析出机器名

  1. MEMBLOCK函数接口

memblock_add(phys_addr_t base, phys_addr_t size) : 将内存区块base到base+size添加到内存区间管理数
    组memblock.memory.regions中
memblock_remove(phys_addr_t base, phys_addr_t size):将内存区块base到base+size从内存区间管理数组
    memblock.memory.regions中删除
memblock_free(phys_addr_t base, phys_addr_t size):将内存区块base到base+size从内存区间管理数组
    memblock.reserved.regions中移除
memblock_reserve(phys_addr_t base, phys_addr_t size):将内存区块base到base+size添加到内存区间管理数
    组memblock.reserved.regions中
memblock_is_reserved(phys_addr_t addr):查询addr是否包含在memblock.reserved.regions中
memblock_is_memory(phys_addr_t addr):查询addr是否包含在memblock.memory.regions中
memblock_isolate_range(type, base, size, &start_rgn, &end_rgn):返回base到base+size涵盖的起始区块
    start_rgn和结束区块end_rgn,如果base到base+size与原有区块由重叠就截断。

void __init arm64_memblock_init(void)
{
    const s64 linear_region_size = -(s64)PAGE_OFFSET;

    memstart_addr = round_down(memblock_start_of_DRAM(),
                   ARM64_MEMSTART_ALIGN); 

    memblock_remove(max_t(u64, memstart_addr + linear_region_size,
            __pa_symbol(_end)), ULLONG_MAX); //移除不会使用到去内存区域
    ......
    if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && initrd_start) {
        u64 base = initrd_start & PAGE_MASK;
        u64 size = PAGE_ALIGN(initrd_end) - base;

        if (WARN(base < memblock_start_of_DRAM() ||
             base + size > memblock_start_of_DRAM() +
                       linear_region_size,
            "initrd not fully accessible via the linear mapping -- please check your bootloader ...\n")) {
            initrd_start = 0;
        } else {
            memblock_remove(base, size); /* clear MEMBLOCK_ flags */
            memblock_add(base, size);
            memblock_reserve(base, size); //将用于initrd的区间插入memblock.reserved.regions
        }
    }

    if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
        extern u16 memstart_offset_seed;
        u64 range = linear_region_size -
                (memblock_end_of_DRAM() - memblock_start_of_DRAM());

        if (memstart_offset_seed > 0 && range >= ARM64_MEMSTART_ALIGN) {
            range = range / ARM64_MEMSTART_ALIGN + 1;
            memstart_addr -= ARM64_MEMSTART_ALIGN *
                     ((range * memstart_offset_seed) >> 16);//给内存起始位置随机
        }
    }
//将内核代码区设置为保留
    memblock_reserve(__pa_symbol(_text), _end - _text);
    ......
}

  1. 内核页表

    1. 页表结构

页表就是一个多维数组,TTBR就是用于存放这个数组起始地址的寄存器。D_Table表示这一条描述符指向下一级页表;D_Block表示这一条描述符指向一个内存块;D_Page表示这一条描述符指向一个物理页。

表描述符/页描述符/块描述符其结构都是基地址加属性的模式,这些基地址都是物理地址,属性包含读写执行等等:

 

  1. 内核页表建立过程

arch/arm64/kernel/head.S
__primary_switch:
	……    
// 使能mmu,需要关注的是加载ttbr1: adrp x2, swapper_pg_dir; msr ttbr1_el1, x2
    bl  __enable_mmu 
	…….
    adrp    x0, __PHYS_OFFSET
    blr x8

    msr sctlr_el1, x20          // disable the MMU
    isb
    bl  __create_page_tables  //创建kernel占用区域的映射

    tlbi    vmalle1             // Remove any stale TLB entries
    dsb nsh
	……
    ldr x8, =__primary_switched
    adrp    x0, __PHYS_OFFSET
    br  x8
ENDPROC(__primary_switch)




__create_page_tables:
    mov x28, lr

    adrp    x0, idmap_pg_dir
    adrp    x1, swapper_pg_dir + SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE
    bl  __inval_cache_range
  //清除swapper_pg_dir和idmap_pg_dir
    adrp    x0, idmap_pg_dir
    adrp    x6, swapper_pg_dir + SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE
1:  stp xzr, xzr, [x0], #16
    stp xzr, xzr, [x0], #16
    stp xzr, xzr, [x0], #16
    stp xzr, xzr, [x0], #16
    cmp x0, x6
    b.lo    1b

    mov x7, SWAPPER_MM_MMUFLAGS //页表属性
    ......
    adrp    x0, swapper_pg_dir
    mov_q   x5, KIMAGE_VADDR + TEXT_OFFSET  // 获取内核起始位置对应的虚拟地址
add x5, x5, x23         // add KASLR displacement
/*
这里是创建早期的内核页表,在开机的后续流程中这个页表将被清除重新建立
这里只建立内核代码空间的页表映射,这里的映射是按照block来映射的,一个block 2M大小
宏create_pgd_entry是创建内核起始虚拟地址在pgd中的rentry并指向下一级table PMD
符号swapper_pg_dir在vmlinux.lds.S中定义,以swapper_pg_dir为起点预留了两页内存第一页为
PGD第二页为PMD,宏create_pgd_entry的工作就是根据内核起始虚拟地址找到其在PGD中的entry
用PMD的基地址以及属性设置到这个entry中
*/
    create_pgd_entry x0, x5, x3, x6
    adrp    x6, _end            // runtime __pa(_end)
    adrp    x3, _text           // runtime __pa(_text)
    sub x6, x6, x3          // _end - _text
add x6, x6, x5          // runtime __va(_end)
//创建内核代码空间的映射
    create_block_map x0, x7, x3, x5, x6
    adrp    x0, idmap_pg_dir
    adrp    x1, swapper_pg_dir + SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE
    dmb sy
    bl  __inval_cache_range
    ret x28
ENDPROC(__create_page_tables)

下面是第二阶段内核建立流程,下面流程中将清除之前的页表重新建立新的页表:


函数early_fixmap_init做了一项很重要的工作就是建立固定映射,固定映射区包含内核参数区(将内核参数内存区映射到这个位置),和三个page用于创建页表的时候映射PGD page/PMD page/PTE page以便通过虚拟地址访问page table。建立固定映射的各级页表是静态定义的如下
static pte_t bm_pte[PTRS_PER_PTE] __page_aligned_bss;

static pmd_t bm_pmd[PTRS_PER_PMD] __page_aligned_bss __maybe_unused;

static pud_t bm_pud[PTRS_PER_PUD] __page_aligned_bss __maybe_unused; //linux使用的3级页表没有pud


 

下面函数paging_init实现了重建内核页表:

void __init paging_init(void)
{
    phys_addr_t pgd_phys = early_pgtable_alloc(); //分配一个page作为临时pgd
    pgd_t *pgd = pgd_set_fixmap(pgd_phys);//将前面分配的临时pgd page映射到FIX_PGD

    map_kernel(pgd); //从新映射内核代码区和以FIXADDR_START为起始地址的固定映射区
    map_mem(pgd);//映射一直映射内存,64位系统则是映射所有全部物理内存
    cpu_replace_ttbr1(__va(pgd_phys)); //将临时pgd_phys写入ttbr1
    memcpy(swapper_pg_dir, pgd, PGD_SIZE);//覆盖之前的pgd
    cpu_replace_ttbr1(lm_alias(swapper_pg_dir));//将swapper_pg_dir的物理地址写入ttbr1

    pgd_clear_fixmap();//清除FIX_PGD
    memblock_free(pgd_phys, PAGE_SIZE); //释放临时pgd page
    memblock_free(__pa_symbol(swapper_pg_dir) + PAGE_SIZE,
              SWAPPER_DIR_SIZE - PAGE_SIZE);//释放掉预留多余的page
}

  1. 内存节点的建立

start_kernel--->setup_arch--->bootmem_init


dummy_numa_init:为memblock.memory.regions中每一个内存区块设置内存节点编号

get_pfn_range_for_nid:计算节点的起始页帧号和结束页帧号

setup_node_data:分配节点描述结构pg_data_t并做基本初始化,后续代码详解
find_zone_movable_pfns_for_nodes:查找zone_movable的起始页帧
calculate_node_totalpages:计算每一个zone的总页数和实际页数(不包含空洞),以及内存节点的总页数
    和实际页数(不包含空洞)
alloc_node_mem_map:分配struct page数组(节点中每个页对应有个struct page)并由
    pgdat->node_mem_map指向该数组
free_area_init_core:初始化每个zone的起始成员,并初始化每一个zone包含页的struct page结构

内存节点分配

start_kernel--->setup_arch--->bootmem_init---> arm64_numa_init---> numa_init---> numa_register_nodes---> setup_node_data

static void __init setup_node_data(int nid, u64 start_pfn, u64 end_pfn)
{
    const size_t nd_size = roundup(sizeof(pg_data_t), SMP_CACHE_BYTES);
    u64 nd_pa;
    void *nd;
    int tnid;
	……
    nd_pa = memblock_alloc_try_nid(nd_size, SMP_CACHE_BYTES, nid); //分配内存节点管理结构pg_data_t
    nd = __va(nd_pa);
    
    tnid = early_pfn_to_nid(nd_pa >> PAGE_SHIFT);
    if (tnid != nid)
        pr_info("NODE_DATA(%d) on node %d\n", nid, tnid);

    node_data[nid] = nd; //全局数组node_data[]包含系统所有内存节点指针,通常情况下只有一个节点
    memset(NODE_DATA(nid), 0, sizeof(pg_data_t));
    NODE_DATA(nid)->node_id = nid; //设置节点编号
    NODE_DATA(nid)->node_start_pfn = start_pfn;//本节点起始页帧
    NODE_DATA(nid)->node_spanned_pages = end_pfn - start_pfn;//本节点包含的页帧数,包含空洞
}

内存zone&节点页帧数的计算

start_kernel--->setup_arch--->bootmem_init---> zone_sizes_init---> free_area_init_nodes---> free_area_init_node---> calculate_node_totalpages

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 = 0, totalpages = 0;
    enum zone_type i;

    for (i = 0; i < MAX_NR_ZONES; i++) { //遍历节点中多有zone
        struct zone *zone = pgdat->node_zones + i;
        unsigned long zone_start_pfn, zone_end_pfn;
        unsigned long size, real_size;

        size = zone_spanned_pages_in_node(pgdat->node_id, i,
                          node_start_pfn,
                          node_end_pfn,
                          &zone_start_pfn,
                          &zone_end_pfn,
                          zones_size);//计算zone的起始页帧和结束页帧以及zone包含的总页帧数
//计算去掉空洞之后的页帧数,通过查询page是否在memblock.memory.regions可知页是否为空洞
        real_size = size - zone_absent_pages_in_node(pgdat->node_id, i,
                          node_start_pfn, node_end_pfn,
                          zholes_size);
        if (size)
            zone->zone_start_pfn = zone_start_pfn;//设置zone的起始页帧
        else
            zone->zone_start_pfn = 0;
        zone->spanned_pages = size;//zone包含的页帧总数,包含空洞
        zone->present_pages = real_size;//zone的实际页帧数,不包含空洞

        totalpages += size; //系统的总页帧数
        realtotalpages += real_size;//系统实际页帧数,不包含空洞
    }

    pgdat->node_spanned_pages = totalpages;//重新设置节点管理的页帧数
    pgdat->node_present_pages = realtotalpages;//节点实际页帧数
    printk(KERN_DEBUG "On node %d totalpages: %lu\n", pgdat->node_id,
                            realtotalpages);
}

struct page数组分配
start_kernel--->setup_arch--->bootmem_init---> zone_sizes_init---> free_area_init_nodes---> free_area_init_node---> alloc_node_mem_map

static void __ref alloc_node_mem_map(struct pglist_data *pgdat)
{
    unsigned long __maybe_unused start = 0;
    unsigned long __maybe_unused offset = 0;
    
    start = pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES - 1);
    offset = pgdat->node_start_pfn - start;
    if (!pgdat->node_mem_map) {
        unsigned long size, end;
        struct page *map;
        end = pgdat_end_pfn(pgdat);
        end = ALIGN(end, MAX_ORDER_NR_PAGES);
        size =  (end - start) * sizeof(struct page);//计算page map数组的大小
        map = alloc_remap(pgdat->node_id, size); //分配page map数组
        if (!map)
            map = memblock_virt_alloc_node_nopanic(size,
                                   pgdat->node_id);
        pgdat->node_mem_map = map + offset; //节点page map的起始叶框位置
    }
    if (pgdat == NODE_DATA(0)) {
        mem_map = NODE_DATA(0)->node_mem_map;
        if (page_to_pfn(mem_map) != pgdat->node_start_pfn)
            mem_map -= offset;
    }
}

ZONE初始化

start_kernel--->setup_arch--->bootmem_init---> zone_sizes_init---> free_area_init_nodes---> free_area_init_node---> free_area_init_core

static void __paginginit free_area_init_core(struct pglist_data *pgdat)
{
    enum zone_type j;
    int nid = pgdat->node_id;
    int ret;
    ......
    lruvec_init(node_lruvec(pgdat));

    for (j = 0; j < MAX_NR_ZONES; j++) {//遍历每一个zone初始化zone结构
        struct zone *zone = pgdat->node_zones + j;
        unsigned long size, realsize, freesize, memmap_pages;
        unsigned long zone_start_pfn = zone->zone_start_pfn;

        size = zone->spanned_pages;
        realsize = freesize = zone->present_pages;

        memmap_pages = calc_memmap_size(size, realsize);
        if (!is_highmem_idx(j)) {
            if (freesize >= memmap_pages) {
                freesize -= memmap_pages; //空闲内存数需要减去page map占用的内存
                if (memmap_pages)
                    printk(KERN_DEBUG
                           "  %s zone: %lu pages used for memmap\n",
                           zone_names[j], memmap_pages);
            } else
                pr_warn("  %s zone: %lu pages exceeds freesize %lu\n",
                    zone_names[j], memmap_pages, freesize);
        }

        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;//内核空闲页数
        else if (nr_kernel_pages > memmap_pages * 2)
            nr_kernel_pages -= memmap_pages;//page map分配在low memory
        nr_all_pages += freesize; //系统总空闲页数
        
        zone->managed_pages = is_highmem_idx(j) ? realsize : freesize;//highmem还现在处于空闲状态
#ifdef CONFIG_NUMA 
        zone->node = nid; //设置zone所属节点
#endif  
        zone->name = zone_names[j]; //” Normal”, “HighMem”,”DMA”等等
        zone->zone_pgdat = pgdat; //指向所属的节点
        spin_lock_init(&zone->lock);
        zone_seqlock_init(zone);
        zone_pcp_init(zone); //设置zone->pageset = &boot_pageset
        
        set_pageblock_order();
//分配zone->pageblock_flags 每个pageblock_order 4个bits,用于设置每个块的迁移类型
        setup_usemap(pgdat, zone, zone_start_pfn, size);
//设置zone的起始页帧,初始化伙伴系统链表等等
        ret = init_currently_empty_zone(zone, zone_start_pfn, size);
        BUG_ON(ret);
//初始化zone包含的每个页帧的page map
        memmap_init(size, nid, j, zone_start_pfn);
    }
}

内存域中每一个page的Struct page结构初始化

start_kernel--->setup_arch--->bootmem_init---> zone_sizes_init---> free_area_init_nodes---> free_area_init_node---> free_area_init_core---> memmap_init---> memmap_init_zone

start_kernel--->setup_arch--->bootmem_init---> zone_sizes_init---> free_area_init_nodes---> free_area_init_node---> free_area_init_core---> memmap_init---> memmap_init_zone

void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
        unsigned long start_pfn, enum memmap_context context)
{
    struct vmem_altmap *altmap = to_vmem_altmap(__pfn_to_phys(start_pfn));
    unsigned long end_pfn = start_pfn + size;
    pg_data_t *pgdat = NODE_DATA(nid);
    unsigned long pfn;
    unsigned long nr_initialised = 0;

    for (pfn = start_pfn; pfn < end_pfn; pfn++) {
        ......
        if (!(pfn & (pageblock_nr_pages - 1))) {//如果是块的起始页帧
            struct page *page = pfn_to_page(pfn);//根据页帧号在page map中查找page

            __init_single_page(page, pfn, zone, nid);//设置page flags和初始化一些链表
//找到也块在zone->pageblock_flags中的位置并将整块设置为MIGRATE_MOVABLE类型
            set_pageblock_migratetype(page, MIGRATE_MOVABLE);//
        } else {
            __init_single_pfn(pfn, zone, nid); //如果不是页块的起始页就只初始化struct page本身
        }
    }
}

  1. 创建zonelists与冷热页链表

5.1主要代码流程

 

build_zonelists :创建ZONELIST_FALLBACK和ZONELIST_NOFALLBACK,后面细讲
pageset_init:初始化每个zone的per_cpu_pageset

mem_init: 将memblock.reserved.regions中的页标记为已分配SetPageReserved(page),将存在于
    memblock.memory.regions不存在memblock.reserved.regions中的页释放到伙伴系统。

kmem_cache_init:初始化高速缓存,后面单独章节讲解

percpu_init_late:percpu内存初始化,后面单独章节讲解

vmalloc_init:初始化vmalloc相关结构,后面单独章节讲解

kmem_cache_init_late:创建通用缓存,后面单独章节讲解

setup_per_cpu_pageset:初始化per_cpu_pageset,设置pcp->batch和pcp->high

5.2创建zonelists

static void build_zonelists(pg_data_t *pgdat)
{
    int i, node, load;
    nodemask_t used_mask;
    int local_node, prev_node;
    struct zonelist *zonelist;
    unsigned int order = current_zonelist_order;
	……
    local_node = pgdat->node_id; 
    load = nr_online_nodes;
    prev_node = local_node; 
    nodes_clear(used_mask);

    memset(node_order, 0, sizeof(node_order));
    i = 0;
//计算node与local_node的距离(分配代价),根据距离由近到远依次放到node_order[]
    while ((node = find_next_best_node(local_node, &used_mask)) >= 0) {
        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[i++] = node; 
    }

if (order == ZONELIST_ORDER_ZONE) {
//创建pgdat->node_zonelists[ZONELIST_FALLBACK];
        build_zonelists_in_zone_order(pgdat, i);
    }
//创建pgdat->node_zonelists[ZONELIST_NOFALLBACK];
    build_thisnode_zonelists(pgdat);
}

创建之后如下图所示:

 

5.3创建冷热页链表

static void __meminit setup_zone_pageset(struct zone *zone)
{
    int cpu;
    zone->pageset = alloc_percpu(struct per_cpu_pageset); //分配per cpu pageset指针
    for_each_possible_cpu(cpu)
        zone_pageset_init(zone, cpu);//初始化每个CPU对应的struct per_cpu_pageset
}

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);//初始化链表pcp->lists
    pageset_set_high_and_batch(zone, pcp);//设置batch和high,high为6 * batch
}
batch计算
static int zone_batchsize(struct zone *zone)
{   
    int batch;
    batch = zone->managed_pages / 1024;  //内存域总内存的千分之一
    if (batch * PAGE_SIZE > 512 * 1024)
        batch = (512 * 1024) / PAGE_SIZE; //不能超过512M
    batch /= 4;     //冷热页占总内存的0.25‰
    if (batch < 1)
        batch = 1;

    batch = rounddown_pow_of_two(batch + batch/2) - 1; //batch变为2的次方
        
    return batch;
	……
}



  1. watermark设置

6.1水印设置

static void __setup_per_zone_wmarks(void)
{
	unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10);
    unsigned long lowmem_pages = 0;
    struct zone *zone;
    unsigned long flags;
    //计算lowmem_pages,不包含空洞
    for_each_zone(zone) {
        if (!is_highmem(zone))
            lowmem_pages += zone->managed_pages;
    }
    for_each_zone(zone) {
        u64 tmp;
        spin_lock_irqsave(&zone->lock, flags);
// pages_min * zone->managed_pages / lowmem_pages,根据每个zone占总内存大小均摊min_free_kbytes
        tmp = (u64)pages_min * zone->managed_pages;
        do_div(tmp, lowmem_pages);
        if (is_highmem(zone)) {
            unsigned long min_pages;
            //计算highmem的min_pages不超过128 pages
            min_pages = zone->managed_pages / 1024;
            min_pages = clamp(min_pages, SWAP_CLUSTER_MAX, 128UL);
            zone->watermark[WMARK_MIN] = min_pages;
        } else {
            zone->watermark[WMARK_MIN] = tmp;
        }
		……
        zone->watermark[WMARK_LOW]  = min_wmark_pages(zone) + tmp;
        zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) + tmp * 2;
        spin_unlock_irqrestore(&zone->lock, flags);
    }
calculate_totalreserve_pages();
……
}

6.2设置预留内存页

static void setup_per_zone_lowmem_reserve(void)
{
    struct pglist_data *pgdat;
    enum zone_type j, idx;
    for_each_online_pgdat(pgdat) {
        for (j = 0; j < MAX_NR_ZONES; j++) {
            struct zone *zone = pgdat->node_zones + j;
            unsigned long managed_pages = zone->managed_pages;
            zone->lowmem_reserve[j] = 0;
            idx = j;
            while (idx) {
                struct zone *lower_zone;
                idx--;
                if (sysctl_lowmem_reserve_ratio[idx] < 1)
                    sysctl_lowmem_reserve_ratio[idx] = 1;
                lower_zone = pgdat->node_zones + idx;
//为每个zone设置预留内存页用于无论如何也不能失败的内存分配
                lower_zone->lowmem_reserve[j] = managed_pages /
                    sysctl_lowmem_reserve_ratio[idx];
                managed_pages += lower_zone->managed_pages;
            }
        }
    }
    calculate_totalreserve_pages();
}


  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值