您的位置:首页 > 运维架构 > Linux

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_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本身
}
}
}


创建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();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: