Linux 内存管理框架
2017-10-22 17:01
260 查看
目录
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
内存管理框架概览
内存管理各个数据结构之间关系
主要数据结构说明
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] | 内存域中各种类型页统计 |
MEMEBLOCK
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 f582 = INIT_MEMBLOCK_REGIONS, .reserved.name = "reserved", …… .bottom_up = false, .current_limit = MEMBLOCK_ALLOC_ANYWHERE, };
内存信息获取
系统启动阶段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:解析出机器名
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); ...... }
内核页表
页表结构
页表就是一个多维数组,TTBR就是用于存放这个数组起始地址的寄存器。D_Table表示这一条描述符指向下一级页表;D_Block表示这一条描述符指向一个内存块;D_Page表示这一条描述符指向一个物理页。
表描述符/页描述符/块描述符其结构都是基地址加属性的模式,这些基地址都是物理地址,属性包含读写执行等等:
内核页表建立过程
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 }
内存节点的建立
start_kernel--->setup_arch--->bootmem_initdummy_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本身 } } }
创建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; …… }
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(); }
相关文章推荐
- ObjC学习10-Foundation框架之内存管理
- linux内存管理机制
- Linux运行时IO设备电源管理框架---PM
- 内核如何管理内存 | Linux 中国
- linux 内核源代码情景分析——i386 的页式内存管理机制
- 浅谈Linux内存管理机制
- X86 Linux分页内存管理机制
- Linux内核之内存管理
- Linux内存和地址空间管理
- 宋宝华:网上坑爹的Linux资料汇总之内存管理
- Linux基础篇之内存管理机制
- Linux内核之内存管理
- Linux 内核开发 - 内存管理
- Linux学习总结—启动、内存结构和管理
- Linux企业级开发技术(6)——libevent企业级开发之内存管理
- Linux运行时IO设备电源管理框架---PM
- linux 内存管理笔记
- 深入理解Linux内核个人小结8---内存区管理
- Linux系统对IO端口和IO内存的管理
- Linux-3.14.12内存管理笔记【内存泄漏检测kmemleak示例】【转】