1.物理页面
- 物理内存管理的最小单位是页
- 内核中使用struct page数据结构描述一个物理页面,简化后如下
struct page{
unsigned long flags; //标志位集合
atomic_t _count; //内核中引用该页面的次数
atomic_t _mapcount; //内核中引用该页面的次数
unsigned long private;
struct address_space *mapping
pgoff_t index
struct list_head lru;
void *virtual;
}
1.1 标志位
- flags是页面的标志位集合,具体定义在
/include/linux/page-flags.h
文件中 - 除了存放标志位还可以存放SETION编号、NODE编号和ZONE编号等
1.2 _count和_mapcount引用计数
- _count 表示内核中引用该页面的次数,为0表示该page页面为空闲和即将要被释放的页面
- _mapcount表示这个页面被进程映射的个数,主要用于RMAP反向映射机制
1._mapcount等于-1,表示没有pte映射到页面中
2._mapcount等于0,表示只有父进程映射了页面 - 内核通过以下两个宏来统计这两个引用计数
static inline int page_mapcount(struct page *page)
static inline int page_count(struct page *page)
1.3 mapping
- 当被用于文件缓存时(page cache),mapping指向和这个文件缓存相关联的address_space对象
- 当被用于匿名页面时(Anonymous Page),mapping指向一个anon_vma数据结构,主要用于反向映射
1.4 lru
- 主要用在页面回收的LRU链表算法中
1.5 virtual
- 指向页面所对应的虚拟地址,在高端内存情况下,因为高端内存不会线性映射到内核空间,这个值为NULL
1.6物理页面的描述
- 内核为每个物理页面都分配了一个struct page数据结构
- 采用mem_map[]数组的形式存放这些struct page数据结构,并且和物理页面一对一映射
2.内存管理区
- ZONE_DMA:用于执行DMA操作,只适用于intel x86
- ZONE_NORMAL:用于线性映射物理内存
- ZONE_HGHMEN:用于管理高端内存,在64位处理器中不需要这个管理区
2.1 内存管理区描述符
- 内存管理区描述符用数据结构struct zone,位于include/linux/mmzone.h中
struct zone {
/* Read-mostly fields */
/*只读域*/
/* zone watermarks, access with *_wmark_pages(zone) macros */
unsigned long watermark[NR_WMARK];
long lowmem_reserve[MAX_NR_ZONES];
struct pglist_data *zone_pgdat;
struct per_cpu_pageset __percpu *pageset;
/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
unsigned long zone_start_pfn;
unsigned long managed_pages;
unsigned long spanned_pages;
unsigned long present_pages;
const char *name;
/* Write-intensive fields used from the page allocator */
/*写敏感域*/
ZONE_PADDING(_pad1_)
/* free areas of different sizes */
struct free_area free_area[MAX_ORDER];
/* zone flags, see below */
unsigned long flags;
/* Primarily protects free_area */
spinlock_t lock;
/* Write-intensive fields used by compaction and vmstats. */
ZONE_PADDING(_pad2_)
spinlock_t lru_lock;
struct lruvec;
/*内存管理区的统计信息*/
ZONE_PADDING(_pad3_)
/* Zone statistics */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
} ____cacheline_internodealigned_in_smp;
- struct zone可以分为读敏感区,写敏感区和统计计数
- ZONE_PADDING()时让zone->lock和zone->lru_lock分布在不同的缓存行,避免发生缓存伪共享
- watermark:每个内存管理区在系统启动时会计算出三个水位值,WMARK_MIN,WMARK_LOW和WMARK_HIGH
- lowmem_reserve:内存管理区中预留的内存
- zone_pgdat:指向内存节点
- pageset:用于维护Per-CPU上的一系列页面,减少自旋锁的竞争
- zone_start_pfn:内存管理区中开始页面的页帧号
- managed_pages:内存管理区中被伙伴系统管理的页面数量
- spanned_pages:内存管理区包含的页面数量
- present_pages:内存管理区里实际管理的页面数量
- free_area:管理空闲区域的数组
- lock:并行访问时用于对内存管理区保护的自旋锁。保护struct zone本身,而不是内存管理区所描述的内存地址空间
- lur_lock:用于对内存管理区中LRU并发访问时进行保护的自旋锁
- lruvec:lru链表集合
- vm_stat:内存管理计数器
2.2 辅助操作函数
- 位于include/linux/mmzone.h中
#define for_each_zone(zone) \
for (zone = (first_online_pgdat())->node_zones; \
zone; \
zone = next_zone(zone))
static inline int is_highmem(struct zone *zone);
#define zone_idx(zone) ((zone) - (zone)->zone_pgdat->node_zones)
- for_each_zone()用来遍历所有的内存管理区
- is_highmem用来检测是否属于ZONE_HGHMEN
- zone_idx返回当前zone所在的内存节点的编号
3.分配和释放页面
3.1 页面分配函数
static inline struct page *alloc_pages(gfp_t gfp_mask,unsigned int order)
- 用来分配2的order次幂个连续的物理页面,返回一个物理页面的struct page数据结构
- gfp_mask表示分配掩码,order不能大于MAX_ORDER
unsigned long __get_free_pages(gfp_t gfp_mask,unsigned int order)
- 返回分配内存的内核空间的虚拟地址
#define alloc_page(gfp_mask) alloc_pages(gfp_mask,0)
#define __get_free_page(gfp_mask) \
__get_free_pages((gfp_mask),0)
- 只分配一个物理页面
unsigned long get_zeroed_page(gfp_t gfp_mask)
- 返回一个全填充为0的页面
3.2 页面释放函数
void __free_pages(struct page *page,unsigned int order);
#define __free_page(page) __free_pages((page),0)
#define free_page(addr) free_pages((addr),0)
3.3 分配掩码gfp_mask
- gfp是get free pages的缩写
- gfp_mask是一个unsigned类型的变量
typedef unsigned __bitwise__ gfp_t;
- gfp_mask大致可以分为几类
1. 内存管理区修饰符(zone modifier)
2. 移动修饰符(mobility and placement modifier)
3. 水位修饰符(watermark modifier)
4. 页面回收修饰符(page reclaim modifier)
5. 行动修饰符(action modifier)
(1)内存管理区修饰符
用来表示从哪些内存管理区中来分配物理内存
- __GFP_DMA:从ZONE_DMA中分配内存
- __GFP_DMA32:从ZONE_DMA32中分配内存
- __GFP_HIGHMEM:优先从ZONE_HIGHMEM中分配内存
(2)移动修饰符
说明分配出来的页面具有的迁移属性
- __GFP_MOVABLE:页面可以被迁移或者回收
- __GFP_RECLAIMABLE:在slab中指定了SLAB_RECLAIM_ACCOUNT标志位,表示slab可以通过shrinkers回收
- __GFP_HARDWALL:使能cpuset分配策略
- __GFP_THISNODE:从指定的内存节点中分配内存
- __GFP_ACCOUNT:分配过程会被kmemcg记录
(3)水位修饰符
控制是否可以访问系统紧急预留的内存
- __GFP_HIGH:分配内存具有高优先级,并且这个分配很有必要
- __GFP_ATOMIC:分配内存的过程不能进行回收或睡眠,并具有很高优先级,常见场景是在中断上下文中分配内存
- __GFP_MEMALLOC:分配过程中允许访问所有的内存,包括系统预留的紧急内存
- __GFP_NOMEMALLOC:分配过程不允许访问系统预留的紧急内存
(4) 页面回收修饰符
(5)行动修饰符
- 定义常用的掩码组合,叫做类型标志(Type Flag)
标志 | 描述 |
---|---|
GFP_KERNEL | 最常见的标志位,主要用于分配内核使用的内存,分配过程会引起睡眠 |
GFP_ATOMIC | 和GFP_KERNEL相反,使用在不能睡眠的分配路径上 |
GFP_USER、GFP_HIGHUSER、GFP_HIGHUSER_MOVABLE | 为用户空间分配内存,GFP_HIGHUSER优先使用高端内存 |
GFP_NOIO、GFP_NOFS | 不会启动IO的操作,不会访问文件系统的操作 |
3.4.内存碎片化
伙伴系统算法的基本条件
- (1)两个块大小相同
- (2)两个块地址连续
- (3)两个块必须是同一个大块中分配出来的
- 在上图中P0和P3变成了空洞,产生了外碎片化(External Fragmentation)。
- 后果是明明系统又足够的内存,但无法输出一大段连续的物理内存供页面分配器使用
- 解决外碎片化的技术叫做内存规整(Memeory Compaction),即通过移动页面的位置让空闲页面连城一片
- 但是内核本身使用的内存页面不能随便迁移,
- 反碎片法(Anti-Fragmentation)是通过迁移类型实现的
- 迁移类型按页块(Page Block)来划分的,按迁移属性分为
1.不可移动类型UNMOVABLE:在内存中有固定的位置,使用GFP_KERNEL这个标志分配的内存就是不能迁移的
2.可移动类型MOVABLE:可移动的页面,通常指通过应用程序分配的页面
3. 可回收页面:不能直接移动但是可以回收
4.分配小块内存
slab来源于内部使用的数据结构,可以理解为一个内存池
4.1 分配接口
#创建slab描述符
struct kmem_cache *
kmem_cache_create(const char *name, size_t size,size_t align, unsigned long flags,
void (*ctor)(void*))
//name:slab描述符的名称
//size:缓存对象的大小
//align:缓存对象需要对齐的字节数
//flags:分配掩码
//ctor:对象的构造函数
#释放slab描述符
void kmem_cache_destroy(struct kmem_cache *s)
#分配缓存对象
void *kmem_cache_alloc(struct kmem_cache *,gfp_t flags)
#释放缓存对象
void kmem_cache_free(struct kmem_cache *,void *)
4.2 slab分配思想
slab机制的核心分配思想实在空闲时建立缓存对象池,包括本地对象缓存池和共享对象缓存池
本地对象缓冲池为每个CPU创建一个本地对象缓冲池,优先从当前CPU的本地对象缓冲池中分配
共享对象缓冲池当本地缓冲池没有空闲对象时,从共享对象缓冲池中取一批空闲对象搬移到本地缓冲池
4.2.1 slab描述符
struct kmem_cache时slab描述符
struct kmem_cache {
struct array_cache __percpu *cpu_cache;
/* 1) Cache tunables. Protected by slab_mutex */
unsigned int batchcount;
unsigned int limit;
unsigned int shared;
unsigned int size;
struct reciprocal_value reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */
slab_flags_t flags; /* constant flags */
unsigned int num; /* # of objs per slab */
/* 3) cache_grow/shrink */
/* order of pgs per slab (2^n) */
unsigned int gfporder;
/* force GFP flags, e.g. GFP_DMA */
gfp_t allocflags;
size_t colour; /* cache colouring range */
unsigned int colour_off; /* colour offset */
struct kmem_cache *freelist_cache;
unsigned int freelist_size;
/* constructor func */
void (*ctor)(void *obj);
/* 4) cache creation/removal */
const char *name;
struct list_head list;
int refcount;
int object_size;
int align;
struct kmem_cache_node *node[MAX_NUMNODES];
其中,cpu_cache表示本地CPU的对象缓冲池
4.2.2 slab
(1)slab机制原理
slab机制分两步完成
第一步:
- 使用
kmem_cache_create()
函数创建一个slab描述符,并使用struct kmem_cache
数据结构来描述. struct kmem_cache
数据结构中,struct array_cache
指向指向本地缓存池,kmem_list3
是指向slab节点的node指针- slab节点中有三个链表:slab_partial,slab_full,slab_free
第二步:
- 从slab描述符中分配空闲对象
- 一个CPU要从一个slab描述符中分配对象,首先从本地缓冲池获取
- 如果本地缓冲池中没有对象,则访问共享缓冲池
建立slab所使用的物理页面需要向页面分配器申请,这个过程可能会睡眠。建立好一个slab后会把这个slab添加到slab空闲链表中,通过lru成员挂入链表
4.2.3 slab回收
slab通过两种方式回收内存:
- 使用kmem_cache_free释放一个对象。
- 通过slab系统注册的定时器扫描slab描述符,回收一部分空闲对象
4.3 kmalloc机制
- kmalloc()函数的核心是slab机制
- 分配一个30字节的小内存,可以用kmalloc(30,GFP_KERNEL)
- 系统会从kmalloc-32的slab描述符中分配一个对象
void *kmalloc(size_t size,gfp_t flags)
void kfree(const void *)