概述
- 内存通过节点(pg_data_t)来管理,每个节点关联到系统中的一个处理器
- 节点又进一步划分为内存域,是对内存的进一步细分,一个节点最多有3个内存域。
- 各个内存域关联了一个数组,用来组织属于该内存域的物理内存页(页帧)。对各个页帧,都分配了一个struct page实例以及所需的管理数据。
- 除了节点自己的内存域,每个节点还提供了一个备用列表(struct zonelist),该列表包含了其他节点,可用于代替当前节点分配内存。
数据结构
1. 内存域类型
```
enum zone_type {
#ifdef CONFIG_ZONE_DMA
ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
ZONE_DMA32,
#endif
ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
ZONE_HIGHMEM,
#endif
ZONE_MOVABLE,
MAX_NR_ZONES
};
```
- ZONE_DMA:适合DMA的内存区域,长度依赖于处理器类型
- ZONE_DMA32:使用了32位地址字可寻址,适合DMA的区域,只有在64位系统上有意义
- ZONE_NORMAL:可以直接映射到内核段的普通内存域,所以系统结构上都会存在的
唯一内存域,但无法保证该地址范围对应了实际的物理内存
(AMD64位系统有2G内存,那么所有内存都属于ZONE_DMA32范围,ZONE_NORMAL为空)
- ZONE_HIGHMEM:标记了超出内核段的物理内存
- ZONE_MOVABLE:伪内存域,防止物理内存碎片机制需要该内存域
- MAX_NR_ZONES:结束标记
2. 节点管理
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[MAX_ZONELISTS];
int nr_zones;
struct page *node_mem_map;
struct bootmem_data *bdata;
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;
- node_zones:包含了节点内各内存域的数据结构.
- node_zonelists:指定了备用节点及其内存域的列表,如果当前节点没有可用空间时,可在备用节点内分配内存
- nr_zones:节点内内存域的个数
- node_mem_map:包含节点内的所有物理内存页
- bdata:指向自举内存分配器数据结构的实例
- node_start_pfn:该节点内第一个页帧的逻辑编号,系统内所有页帧是依次编号的,每个页帧的号码是全局唯一的
- node_present_pages:节点中页帧的个数
- node_spanned_pages:节点中包含空洞的页帧个数
- node_id:全局节点ID
- pgdat_next:连接到下一个内存节点,系统中的所有内存节点都通过单链表连接起来.
- kswapd_wait:交换守护进程的等待队列.
enum node_states {
N_POSSIBLE, /* The node could become online at some point */
N_ONLINE, /* The node is online */
N_NORMAL_MEMORY, /* The node has regular memory */
#ifdef CONFIG_HIGHMEM
N_HIGH_MEMORY, /* The node has regular or high memory */
#else
N_HIGH_MEMORY = N_NORMAL_MEMORY,
#endif
N_CPU, /* The node has one or more cpus */
NR_NODE_STATES
};
- 节点状态管理数据结构
- static inline void node_set_state(int node, enum node_states state)
- static inline void node_clear_state(int node, enum node_states state)
3. 内存域
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];
spinlock_t lock;
struct free_area free_area[MAX_ORDER];
ZONE_PADDING(_pad1_)
/* Fields commonly accessed by the page reclaim scanner */
spinlock_t lru_lock;
struct list_head active_list;
struct list_head inactive_list;
unsigned long nr_scan_active;
unsigned long nr_scan_inactive;
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];
int prev_priority;
ZONE_PADDING(_pad2_)
/* Rarely used or read-mostly fields */
wait_queue_head_t * wait_table;
unsigned long wait_table_hash_nr_entries;
unsigned long wait_table_bits;
/*
* Discontig memory support fields.
*/
struct pglist_data *zone_pgdat;
/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
unsigned long zone_start_pfn;
unsigned long spanned_pages; /* total size, including holes */
unsigned long present_pages; /* amount of memory (excluding holes) */
/*
* rarely used fields:
*/
const char *name;
} ____cacheline_internodealigned_in_smp;
- ZONE_PADDING:生成填充字段,使常用数据结构处于自身的缓冲行中
- pages_min, pages_high,pages_low:页换出时使用的内存水印,如果内存不足,内核可以将页写到硬盘
- 空闲页多于pages_high:内存域是理想状态
- 空闲页低于pages_low:内核开始将页换出到硬盘
- 空闲页低于page_min:页回收工作压力大
- lowmem_reserve:用于一些无论如何都不能失败的关键性内存分配,分别为各个内存域指定了若干页
- pageset:用于实现每个CPU的热/冷页帧列表,用于保存可用于满足分配的新鲜页,仍然在高速缓存中的称为热页
- free_area:用于实现伙伴系统,每个数组元素表示固定长度的一些连续内存区
- active_list:活动页的集合
- inactive_list:不活动页的集合
- nr_scan_active和nr_scan_inactive:内存回收时需要扫描的活动和不活动页的数据
- pages_scanned:从上次换出一页以来,有多少页未能成功扫描
- flags:内存域的当前状态
typedef enum { ZONE_ALL_UNRECLAIMABLE, ZONE_RECLAIM_LOCKED, ZONE_OOM_LOCKED, }
- ZONE_ALL_UNRECLAIMABLE:页无法回收
- ZONE_RECLAIM_LOCKED:CPU回收内存域时设置,阻止其他CPU进行同样操作
- ZONE_OOM_LOCKED:内核试图杀死消耗进程最多的进程时设置该标志位
- vm_stat:维护该内存域的统计信息
- prev_priority:上一次扫描该内存域的优先级
- wait_table, wait_table_bits和wait_table_hash_nr_entries实现了一个等待队列,可用于等待某一页表变为可用的进程
- zone_pgdat:指向父节点
4. 内存域水印
- 在计算内存水印前,内核首先要确定为关键性分配保留的内存空间的最小值,该值随可用内存大小而非线性增长,并保存在全局变量min_free_kbytes中,一个不变的约束是不能少于128k也不能多于64MB。
- 数据结构中水印值的填充由init_per_zone_pages_min处理,该函数在内核启动期间调用,主要调用如下两个函数:
- setup_per_zone_pages_min
- setup_per_zone_lowmem_reserve
- setup_per_zone_pages_min负责设置struct zone中的pages_min,pages_low和pages_high成员,在计算出高端内存区域之外的页面总数后(保存在lowmem_pages),内核迭代系统中的所有内存域并执行相关计算
- lowmem_reserve的计算由setup_per_zone_lowmem_reserve完成,内核迭代系统中所有节点,对每个节点的各个内存域分别计算预留内存最小值
5. 冷热页
- struct zone的pageset成员用于实现冷热分配器,热页是指那些加载到CPU高速缓存的页,其数据访问速度比不在高速缓存中的冷页快
struct per_cpu_pageset { struct per_cpu_pages pcp[2]; //0:hot, 1:code } ____cacheline_aligned_in_smp; struct per_cpu_pages { int count; /* number of pages in the list */ int high; /* high watermark, emptying needed */ int batch; /* chunk size for buddy add/remove */ struct list_head list; /* the list of pages */ }
- cout:该列表相关的页的数目
- high:如果count > high则表明列表中的页太多了
- list:双链表,保存了当前CPU的冷页或者热页
6. 页帧
-
页帧是系统内存的最小单位,对内存中的每一个页都会创建struct page的一个实例
-
页的广泛使用,增加了struct 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, */ unsigned int inuse; /* SLUB: Nr of objects */ }; 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. */ }; 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 * protected by zone->lru_lock ! */ /* * On machines where all RAM is mapped into kernel address space, * we can simply calculate the virtual address. On machines with * highmem some memory is mapped into kernel virtual memory * dynamically, so we need a place to store that address. * Note that this field could be 16 bits on x86 ... ;) * * Architectures with slow multiplication can define * WANT_PAGE_VIRTUAL in asm/page.h */ void *virtual; /* Kernel virtual address (NULL if not kmapped, ie. highmem) */ };
- slab,freelist和inuse用于slub分配器
- flags存储了和体系结构无关的标志,用于描述页的属性
- _count是一个使用计数,表示内核中引用该页的次数
- _mapcount表示在页表中有多少项指向该页
- lru是一个表头,用于在各种链表上维护该页,以便将页按照不同类别分组,最重要的类别是活动和不活动页
- 内核可以将多个毗连的页合并为较大的复合页,第一个页称作首页,而所有其余各页叫做尾页,所有尾页对应的page实例,都将first_page设置为指向首页
- mapping指定了页帧所在的地址空间,index是页帧在映射内部的偏移量,地址空间是一个非常一般的概念,例如,可以用在向内存读取文件时,用于将文件的内容与装载数据的内存区关联起来。通过一个小技巧,mapping不仅能保存一个指针,还能包含一些额外的信息,用于判断页是否属于某个未关联到地址空间某个匿名内存区:如果将mapping最低位置1,该指针指向另外一个数据结构(anon_vma),这个结构对实现匿名映射的逆向映射很重要
- private指向私有数据的指针,根据页的用途,可以用不同方式使用该指针
- virtual用于高端内存区域中的页
-
体系结构无关的标志
- PG_locked:锁定页面不允许内核的其他部分访问.
- PG_error:涉及该页的I/O操作期间发生错误.
- PG_referenced, PG_active:控制系统中使用该页的活跃程度.
- PG_uptodate:页的数据已经从块设备读取,其间没有出错
- PG_dirty:与硬盘上的数据相比,页的内容发生改变.
- PG_lru:有助于实现页面回收和切换,内核使用两个最近最少使用链表来区分活动页和不活动页,如果页在其中一个链表中,该位置位,如果在活动链表中,PG_active会置位
- PG_highmem:页在高端内存中
- PG_private:表明page的private成员非空
- PG_writeback:页的内容处于向块设备回写过程中
- PG_slab:页是slab分配器的一部分
- PG_swapcache:页处于交换缓存中
- PG_reclaim:内核决定回收特定页时设置
- PG_compound:该页属于一个更大复合页
- PG_buddy:表明页空闲且包含在伙伴系统的列表中