文章目录
1.内存管理概览
- MMU:虚拟地址到物理地址的转换机制。
- TLB:简称快表,页表的硬件缓存单元,用于加速物理地址的寻址。
- cache:为了与CPU的访问速度匹配,设计cache,有的平台支持L1 cache 、L2 cache,可以通过lscpu指令查看。
- DDR:内存,程序必须加载到内存中才能执行。
- 页表管理:地址映射机制。
- 伙伴系统/页面分配器:以页为单位管理和分配空闲页面。
- slab:分配比页小的内存。
- 页面回收机制:当页面使用完毕后,需要归还给伙伴系统或slab。
- page_cache:有文件背景的页面,用于缓存磁盘上数据。读过一遍的数据,进程下次再读的时候就直接从page cache里去拿,提升系统的整体性能。
- 匿名页面:没有文件背景的页面,如堆,栈,数据段等,不是以文件形式存在,因此无法和磁盘文件交换,但可以通过硬盘上划分额外的swap交换分区或使用交换文件进行交换。
- 缺页中断:也称请页机制,当执行进程时,若发现需要访问的页不在内存中,则触发请页机制。
- VMA管理:用户地址空间的区间管理,比如栈、堆、mmap、bss、data、text等,通过双向链表和红黑树管理。
- 反向映射:通过物理地址找到哪些虚拟地址使用它,在页面回收的时候会用到。
- KSM:基于写时复制机制COW,也就是将内容相同的页面合并成一个只读页面,从而释放出空闲物理页面。
- 页迁移:分配新页面,将旧页面内容拷贝到新页面。
2.物理内存管理
2.1 物理内存初始化流程
2.2 zone
由于硬件存在缺陷而引起了如下内存选址问题:
- 一些硬件只能用某些特定的内存地址来执行DMA。
- 一些体系结构的物理内存寻址范围比虚拟地址寻址范围大得多,会导致一些内存不能永久地映射到内核空间上。
所以,Linux内核主要将内存分为以下几个区,每个区由一个zone结构管理,在<linux/mmzone.h>中定义:
- ZONE_DMA: 用来执行DMA操作。
- ZONE_DMA32:只能被32位的设备用来DMA操作。
- ZONE_NORMAL:能正常映射的页。
- ZONE_HIGHMEM:高端内存页,这些页不能永久地映射到内核地址空间。
struct zone {
/* Read-mostly fields */
/* zone watermarks, access with *_wmark_pages(zone) macros */
unsigned long watermark[NR_WMARK];//每个zone在系统启动时会计算出三个水位值,分别是WMARK_MIN、WMARK_LOW、WMARK_HIGH,在页面分配器和kswapd页面回收中会用到。
unsigned long nr_reserved_highatomic;//为某些场景预留的内存
long lowmem_reserve[MAX_NR_ZONES];//zone中预留的内存
#ifdef CONFIG_NUMA//非同一内存访问,即便硬件上是一整块连续内存的UMA,Linux也可将其划分为若干的node。同样,即便硬件上是物理内存不连续的NUMA,Linux也可将其视作UMA。所以在Linux系统中,可以基于一个UMA的平台测试NUMA上的应用特性。从另一个角度,UMA就是只有一个node的特殊NUMA,所以两者可以统一用NUMA模型表示。
int node;
#endif
struct pglist_data *zone_pgdat;//指向内存结点
struct per_cpu_pageset __percpu *pageset;//用于维护Per_CPU上的一系列页面,以减少自旋锁的争用。
#ifndef CONFIG_SPARSEMEM//稀疏内存模型,为管理struct page而出现。
unsigned long *pageblock_flags;
#endif /* CONFIG_SPARSEMEM */
/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
unsigned long zone_start_pfn;//zone中开始页面的叶帧号
unsigned long managed_pages;//zone中被伙伴系统管理的页面数量
unsigned long spanned_pages;//zone中包含的页面数量
unsigned long present_pages;//zone里实际管理的页面数量,对一些体系结构来说,其值和spanned_pages一样
const char *name;//是一个以NULL结束的字符串表示这个区的名字。内核启动期间初始化你这个值,其代码位于mm/page_alloc.c中。三个区的名字分别为DMA、Normal、HighMem
#ifdef CONFIG_MEMORY_ISOLATION//内存隔离
unsigned long nr_isolate_pageblock;
#endif
#ifdef CONFIG_MEMORY_HOTPLUG//内存热拔插
/* see spanned/present_pages for more description */
seqlock_t span_seqlock;
#endif
int initialized;
/* Write-intensive fields used from the page allocator */
ZONE_PADDING(_pad1_)//可以让zone->lock 和 zone->lru_lock 这两个锁分布在不同的cache line中,为了性能而浪费空间
/* 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;//并行访问时,用于对zone保护的自旋锁
/* Write-intensive fields used by compaction and vmstats. */
ZONE_PADDING(_pad2_)
unsigned long percpu_drift_mark;
#if defined CONFIG_COMPACTION || defined CONFIG_CMA
/* pfn where compaction free scanner should start */
unsigned long compact_cached_free_pfn;
/* pfn where async and sync compaction migration scanner should start */
unsigned long compact_cached_migrate_pfn[2];
#endif
#ifdef CONFIG_COMPACTION //内核里的紧致内存机制,类似于磁盘碎片整理:把碎的页移动整合到连续的一段空间,就留出一段连续的内存了。在内核中打开CONFIG_COMPACTION就可以使用compaction功能了,要注意它只能整理可移动的页面。
unsigned int compact_considered;
unsigned int compact_defer_shift;
int compact_order_failed;
#endif
#if defined CONFIG_COMPACTION || defined CONFIG_CMA
/* Set to true when the PG_migrate_skip bits should be cleared */
bool compact_blockskip_flush;
#endif
bool contiguous;
ZONE_PADDING(_pad3_)
/* Zone statistics */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];//zone计数
atomic_long_t vm_numa_stat[NR_VM_NUMA_STAT_ITEMS];
} ____cacheline_internodealigned_in_smp;//因为zone会经常被访问到,因此用这个数据结构要求以L1 cache对齐
- watermark(水位),每个zone都有三个水位值:
enum zone_watermarks {
WMARK_MIN,// 最低水位,代表内存显然已经不够用了
WMARK_LOW,//低水位,代表内存已经开始吃紧,需要启动回收页内核线性kswapped去回收内存
WMARK_HIGH,//高水位,代表内存还是足够的
NR_WMARK
};
- lowmem_reserve : 这个zone区域保留的内存,当系统内存出现不足的时候,系统就会使用这些保留的内存来做一些操作,比如使用保留的内存进程用来可以释放更多的内存。
- free_area:用于维护空闲的页,其中数组的下标对应页的order数。最大order目前是11。free_are的结构体:
struct free_area {
struct list_head free_list[MIGRATE_TYPES];//用于将各个order的free page链接在一起,而每一个order中又根据迁移类型分成了几组
unsigned long nr_free;//代表这个order中还有多个空闲page
};
enum migratetype {
MIGRATE_UNMOVABLE,//不可移动的页,核心内核分配的大多数内存属于该类别
MIGRATE_MOVABLE,//可以移动的页,当出现内存碎片的时候,就可以移动此页,腾出更多连续的空间。属于用户空间应用程序的页属于该类别. 它们是通过页表映射的, 如果它们复制到新位置,页表项可以相应地更新,应用程序不会注意到任何事
MIGRATE_RECLAIMABLE,//可以回收的页, 例如,映射自文件的数据属于该类别,kswapd守护进程会根据可回收页访问的频繁程度,周期性释放此类内存. 页面回收本身就是一个复杂的过程. 内核会在可回收页占据了太多内存时进行回收, 在内存短缺(即分配失败)时也可以发起页面回收.
MIGRATE_PCPTYPES,//用来表示每CPU页框高速缓存的数据结构中的链表的迁移类型数目
MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
MIGRATE_CMA,//一块连续的物理内存
#endif
#ifdef CONFIG_MEMORY_ISOLATION
MIGRATE_ISOLATE,//不能从这个链表分配页,因为这个链表专门用于NUMA结点移动物理内存页,将物理内存页内容移动到使用这个页最频繁的CPU
#endif
MIGRATE_TYPES
};
通过 cat /proc/zoneinfo 可以看到每个zone区的详细信息:
potato@ubuntu:~$ cat /proc/zoneinfo
Node 0, zone DMA
per-node stats
nr_inactive_anon 3712
nr_active_anon 217352
nr_inactive_file 192440
nr_active_file 198710
nr_unevictable 4
nr_slab_reclaimable 42640
nr_slab_unreclaimable 19617
nr_isolated_anon 0
nr_isolated_file 0
workingset_nodes 0
workingset_refault 0
workingset_activate 0
workingset_restore 0
workingset_nodereclaim 0
nr_anon_pages 219892
nr_mapped 76009
nr_file_pages 392313
nr_dirty 296
nr_writeback 0
nr_writeback_temp 0
nr_shmem 4055
nr_shmem_hugepages 0
nr_shmem_pmdmapped 0
nr_file_hugepages 0
nr_file_pmdmapped 0
nr_anon_transparent_hugepages 0
nr_unstable 0
nr_vmscan_write 0
nr_vmscan_immediate_reclaim 0
nr_dirtied 292233
nr_written 273663
nr_kernel_misc_reclaimable 0
pages free 3968
min 68
low 85
high 102
spanned 4095
present 3997
managed 3976
protection: (0, 2914, 3836, 3836, 3836)
nr_free_pages 3968
nr_zone_inactive_anon 0
nr_zone_active_anon 0
nr_zone_inactive_file 0
nr_zone_active_file 0
nr_zone_unevictable 0
nr_zone_write_pending 0
nr_mlock 0
nr_page_table_pages 0
nr_kernel_stack 0
nr_bounce 0
nr_zspages 0
nr_free_cma 0
numa_hit 1
numa_miss 0
numa_foreign 0
numa_interleave 0
numa_local 1
numa_other 0
pagesets
cpu: 0
count: 0
high: 0
batch: 1
vm stats threshold: 4
cpu: 1
count: 0
high: 0
batch: 1
vm stats threshold: 4
node_unreclaimable: 0
start_pfn: 1
Node 0, zone DMA32
pages free 219272
min 12782
low 15977
high 19172
spanned 1044480
present 782288
managed 758575
protection: (0, 0, 922, 922, 922)
nr_free_pages 219272
nr_zone_inactive_anon 3595
nr_zone_active_anon 148063
nr_zone_inactive_file 138458
nr_zone_active_file 156085
nr_zone_unevictable 4
nr_zone_write_pending 255
nr_mlock 4
nr_page_table_pages 6215
nr_kernel_stack 4372
nr_bounce 0
nr_zspages 0
nr_free_cma 0
numa_hit 1026744
numa_miss 0
numa_foreign 0
numa_interleave 1
numa_local 1026744
numa_other 0
pagesets
cpu: 0
count: 149
high: 378
batch: 63
vm stats threshold: 24
cpu: 1
count: 235
high: 378
batch: 63
vm stats threshold: 24
node_unreclaimable: 0
start_pfn: 4096
Node 0, zone Normal
pages free 5010
min 4045
low 5056
high 6067
spanned 262144
present 262144
managed 236120
protection: (0, 0, 0, 0, 0)
nr_free_pages 5010
nr_zone_inactive_anon 117
nr_zone_active_anon 69289
nr_zone_inactive_file 53982
nr_zone_active_file 42625
nr_zone_unevictable 0
nr_zone_write_pending 41
nr_mlock 0
nr_page_table_pages 4508
nr_kernel_stack 7884
nr_bounce 0
nr_zspages 0
nr_free_cma 0
numa_hit 495142
numa_miss 0
numa_foreign 0
numa_interleave 32381
numa_local 495142
numa_other 0
pagesets
cpu: 0
count: 362
high: 378
batch: 63
vm stats threshold: 16
cpu: 1
count: 348
high: 378
batch: 63
vm stats threshold: 16
node_unreclaimable: 0
start_pfn: 1048576
Node 0, zone Movable
pages free 0
min 0
low 0
high 0
spanned 0
present 0
managed 0
protection: (0, 0, 0, 0, 0)
Node 0, zone Device
pages free 0
min 0
low 0
high 0
spanned 0
present 0
managed 0
protection: (0, 0, 0, 0, 0)
potato@ubuntu:~$
可以看到,当前系统将内存分为DMA、DMA32、Normal、Moveable四个zone区,以及每个区的水位值。
2.3 zonelist
当指定的节点Node无法满足分配请求时,需要从其他节点Node中分配, 因此内核在内存的结点pg_data_t中提供了一个zonelists,它是两个大小的数组,意味着一个是由本Node的zones组成,另一个是由从本node分配不到内存时可选的备用zones组成,相当于是选择了一个退路:
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[MAX_ZONELISTS];
...
}
struct zonelist {
struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];
};
struct zoneref {
struct zone *zone; /* Pointer to actual zone */
int zone_idx; /* zone_idx(zoneref->zone) */
};
2.4 fallbacks
当一种特定的 MIGRATE_TYPES 类型的页面不够分配时,需要从其他类型页面中分配来满足需求。从其他类型页面分配有一定的次序,由备用列表 fallbacks 来管理:
static int fallbacks[MIGRATE_TYPES][4] = {
//分配不可移动页面失败时的备用列表
[MIGRATE_UNMOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE, MIGRATE_TYPES },
//分配可回收页面失败时的备用列表
[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE, MIGRATE_MOVABLE, MIGRATE_TYPES },
//分配可移动页面失败时的备用列表
[MIGRATE_MOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_TYPES },
#ifdef CONFIG_CMA
[MIGRATE_CMA] = { MIGRATE_TYPES }, /* Never used */
#endif
#ifdef CONFIG_MEMORY_ISOLATION
[MIGRATE_ISOLATE] = { MIGRATE_TYPES }, /* Never used */
#endif
};
例如:每一行对应一个类型的分配次序, 在内核想要分配不可移动页MIGRATE_UNMOVABLE时, 如果对应链表为空, 则遍历fallbacks[MIGRATE_UNMOVABLE], 首先后退到可回收页链表MIGRATE_RECLAIMABLE, 接下来到可移动页链表MIGRATE_MOVABLE, 最后到紧急分配链表MIGRATE_TYPES。
2.5 伙伴系统
每个zone里面空闲页面由伙伴系统管理,可将伙伴系统中的 free_area 结构 和 page 结构表示为下图:
如图所示,内核采用伙伴系统以页为单位动态管理空闲页面,在用户提出申请时,分配一块大小合适的内存块给用户,用户用完后,归还给伙伴系统。在伙伴系统中,内存块是2的order次幂,order的最大值是11,也就是把所有的空闲页面组成11个内存块链表。每个内存块包括1,2,4,8,16,32,···,1024个连续的页面,1024对应着4MB大小的连续物理内存。
通过 cat /proc/buddyinfo 可以查看伙伴系统中剩余页面的情况:
也可以通过 cat /proc/pagetypeinfo 查看更加详细的页面剩余情况:
Free pages count per migrate type at order :不同order 按照migrate type的空闲page数量。
Number of blocks:连续内存块数量
Page block order: 9 //分配阶数
Pages per block: 512 //对应的页面数
Page block表示每个迁移链表应该有适当的页面。由内核中的两个全局变量pageblock_order 和 pageblock_nr_pages提供的. 第一个表示一个分配阶, pageblock_nr_pages则表示该分配阶对应的页面数:
#ifdef CONFIG_HUGETLB_PAGE
#ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE
/* Huge page sizes are variable */
extern unsigned int pageblock_order;
#else /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */
/* Huge pages are a constant size */
#define pageblock_order HUGETLB_PAGE_ORDER
#endif /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */
#else /* CONFIG_HUGETLB_PAGE */
/* If huge pages are not used, group by MAX_ORDER_NR_PAGES */
#define pageblock_order (MAX_ORDER-1)
2.6 pg_data_t
一个Node中的所有zone由一个pg_data_t 来管理,pg_data_t又由一个全局数组node_data来管理。node_data里面的每个数组项代表一个Node,在NUMA结构下,每个Node有自己的CPU、总线、内存,NUMA结构模型如下:
(图片来源于:https://blog.csdn.net/ustc_dylan/article/details/45667227)
因此可以用下图来表示各数据结构的关系:
3. 虚拟空间管理
3.1 进程虚拟地址空间
在32位系统中,进程虚拟地址空间由3G的用户空间和1G的内核空间组成,如上图所示。用户空间中的各个进程之间互不干扰,即使用到相同的虚拟地址,也不会引起地址冲突。但是内核空间是被各个进程所共享的,内核空间的地址冲突极可能会引起整个系统崩溃。
3.2 虚拟地址空间布局
虚拟地址空间的大致分布如图所示,不同的平台,虚拟地址空间划分可能存在差异。每个字段的说明如下:
3.2.1 内核空间
- 临时映射区:当所安装的物理内存超过1G时,内核空间无法触及整个物理空间,所以增加临时映射区完成剩余物理内存的临时映射。而在64位的系统上就不存在这样的问题了,因为可用的线性地址空间远大于可安装的内存。
- vmalloc区:内核空间调用vmalloc()可分配的空间,特点是虚拟地址连续,物理地址不连续。
- 永久内存映射区:内核专门留出的一块线性空间,从 PKMAP_BASE 到 FIXADDR_START ,用于映射高端内存。
- 固定内存区:内核保留了一些线性空间用于特殊需求。其特点是①每个CPU 占用一块空间,②其内部又分为多个小空间,每个小空间大小是1 个 page,每个小空间用于一个目的,这些目的定义在kmap_types.h 中的km_type 中。
- normal memroy:能够正常映射页的地址空间。
- kernel image:image相关的数据段。
- swapper_pg_dir:存放全局页目录的基地址。
3.2.2 用户空间
- Environment variables:环境变量空间。
- Command-line argument:保存命令行参数空间。
- stack:栈,为函数内部的局部变量提供存储空间。
- Memory mmap:映射文件相关的空间。
- heap:堆,malloc()分配区。
- BSS:存储未初始化或初始化为0的全局变量、静态变量。bss段与data段的区别是,编译时需为data段分配空间,而bss段不用。BSS段仅为未初始化的静态分配变量预留位置,在目标文件中并不占据空间,这样可减少目标文件体积。
- data:存储经过初始化的全局和静态变量
- text:文本段,也叫代码段,存储可执行文件的指令;也有可能包含一些只读的常数变量,例如字符串常量等。
3.2 描述用户空间的数据结构
如图所示。用户空间被划分为几段不同的区间,每段区间由一个vm_area_struct结构管理,每个区间来源不同,可能来源于可执行映像、共享库、动态分配等,并且进程对每个区间的访问权限也不相同。当vm_area_struct结构在内核中数量不多时,内核会采用双向链表来管里所有的vm_area_struct,由mm_struct中mmap域指向。当数量较多时,内核会采用红黑树来管理vm_area_struct结构,由mm_struct中的mm_rb指向
整个进程是由task_struct来管理,管理着与进程相关的所有信息,比如页目录、调度器、进程地址空间等,其中的mm_struct、vm_area_struc字段则用来管理用户空间。
4. 内存管理机制
内核是如何管理内存的,大致可分为以下几个机制:
Linux内核对虚拟地址的管理实现了五大机制,分别是地址映射机制、请页机制、内存分配与回收机制、交换机制、缓存与刷新机制。它们之间相互协作,如上图所示:
- 内核通过地址映射机制将进程从磁盘映射到虚拟地址空间中。
- ①当执行进程时,若发现需要访问的页不在内存中,则触发请页机制。
- ②如果系统有空闲内存可供分配,则请求分配内存。
- ③并且把正在使用的页记录在缓存中(最近访问的页,下一次很可能还会被访问到)。
- ④/⑤当系统中没有空闲内存分配时,则会触发交换机制腾出内存空间。
- ⑥交换机制中也会用到缓存机制。
- ⑦物理页交换到交换文件后,需要通过页表来修改映射后的文件地址。
- ⑧在地址映射中,会用到TLB来加速物理内存的寻找。
4.1 地址映射(页表映射)
32位虚拟地址的高12位作为访问一级页表的索引值,找到对应的表项,每个表项指向一个二级页表,以虚拟地址的次8位作为访问二级页表的索引值,得到对应的页表项,从这个页表项中找到20位的物理页面地址(页帧)。最后页帧和页面偏移组合成物理地址。在ARM32中,这个过程由硬件MMU来完成。
4.2 页面高速缓存
页表保存在内存中,每次访存都需要经过页表转换,降低了访问速度。为了提高访问速度,有一个页面高速缓存硬件诞生——TLB(转换旁路缓冲器),也称快表。当CPU访问某个地址空间时,先检查对应的页帧是否在TLB中,如果在(命中),就不需要经过页表转换了,如果不在(未命中),需要经过页表转换,此时硬件也会自动将未命中的页帧更新到TLB中,也便下一次访问。有时TLB的更新也需要软件更新,比如进程切换、更改内核页表等。
4.3 64位机器的页表映射
虽然64 位机器支持48位的选址空间,但其页表转换的模型与32几乎一致,不过是增加了两级页表,采用4级页表结构:
PGD:page Global directory(47-39), 页全局目录
PUD:Page Upper Directory(38-30),页上级目录
PMD:page middle directory(29-21),页中间目录
PTE:page table entry(20-12),页表项
5. 物理页面的分配
内核中常用以下函数来分配一个或多个连续的页面,有利于缓解系统内存的碎片化:
函数 | 描述 |
---|---|
alloc_page(gfp_mask) | 只分配一页,返回指向页结构的指针 |
alloc_pages(gfp_mask,order) | 分配2order个页,返回指向第一页页结构的指针 |
__get_free_page(gfp_mask) | 只分配一页,返回指向其逻辑地址的指针 |
__get_free_pages(gfp_mask,order) | 分配2order个页,返回指向第一页逻辑地址的指针 |
get_zeroed_page(gfp_mask) | 只分配一页,让其填充为0,返回指向逻辑地址的指针 |
参数 gfp_mask 是分配掩码,决定从哪一个zone分配页面,页面分配单位是1页。参数order是分配阶,决定从哪一个free_list中分配页面,即分配多少页面。
5.1 gfp_mask标志
gfp_mask标志可分为三类:行为修饰符、区修饰符及类型修饰符。
行为修饰符表示内核应该如何分配所需的内存,在某些特定的情况下,只能使用某些特定的方法分配内存,例如,中断处理程序要求内核在分配内存过程中不能睡眠(因为中断处理程序不能被重新调度)。
区修饰符表示从哪里分配内存,内核把物理内存分为多个区(zone),每个区具有不同的用途。
类型修饰符是行为修饰符和区修饰符的组合,简化了修饰符的使用。所有这些修饰符都在< linux/gfp.h >中:
区修饰符:
#define __GFP_DMA ((__force gfp_t)___GFP_DMA) //从ZONE_DMA分配
#define __GFP_HIGHMEM ((__force gfp_t)___GFP_HIGHMEM)//从ZONE_HIGHMEM或ZONE_NORMAL分配
#define __GFP_DMA32 ((__force gfp_t)___GFP_DMA32)//从ZONE_DMA32分配
#define __GFP_MOVABLE ((__force gfp_t)___GFP_MOVABLE) /* ZONE_MOVABLE allowed */
#define GFP_ZONEMASK (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)
行为修饰符:
#define __GFP_RECLAIMABLE ((__force gfp_t)___GFP_RECLAIMABLE)//slab分配器中可回收页面
#define __GFP_WRITE ((__force gfp_t)___GFP_WRITE)//表示调用者打算写物理页。在可能的情况下,尽量把该类型的页分布到本地节点的 所有区域,避免所有 脏页 在一个 内存区域
#define __GFP_HARDWALL ((__force gfp_t)___GFP_HARDWALL)//强制cpuset内存分配策略
#define __GFP_THISNODE ((__force gfp_t)___GFP_THISNODE)//强制从本地节点分配页
#define __GFP_ACCOUNT ((__force gfp_t)___GFP_ACCOUNT)//把分配的也记录在 内核内存控制组
#define __GFP_ATOMIC ((__force gfp_t)___GFP_ATOMIC)//指明调用者为 高优先级,不能回收页或者进入睡眠
#define __GFP_HIGH ((__force gfp_t)___GFP_HIGH)//指明调用者为 高优先级,系统必须通过请求
#define __GFP_MEMALLOC ((__force gfp_t)___GFP_MEMALLOC)//允许访问所有内存
#define __GFP_NOMEMALLOC ((__force gfp_t)___GFP_NOMEMALLOC)//进程访问 紧急保留内存
#define __GFP_IO ((__force gfp_t)___GFP_IO)//允许读写存储设备
#define __GFP_FS ((__force gfp_t)___GFP_FS)//允许向下调用到 底层文件系统。当 文件系统 申请页时,如果内存 严重不足,直接回收页,把 脏页 会写到 存储设备。为了避免调用 文件系统函数 可能会导致的 死锁,文件系统申请页的时候应该 清除 该标志位。
#define __GFP_DIRECT_RECLAIM ((__force gfp_t)___GFP_DIRECT_RECLAIM)//调用者可以 直接回收页
#define __GFP_KSWAPD_RECLAIM ((__force gfp_t)___GFP_KSWAPD_RECLAIM)//当 空闲页数 达到 低水线 时,调用者想要唤醒 页回收线程kswapd(即 异步回收页)
#define __GFP_RECLAIM ((__force gfp_t)(___GFP_DIRECT_RECLAIM|___GFP_KSWAPD_RECLAIM))//允许 直接回收页 和 异步回收页
#define __GFP_RETRY_MAYFAIL ((__force gfp_t)___GFP_RETRY_MAYFAIL)//允许重试,直到多次以后放弃,分配可能 失败
#define __GFP_NOFAIL ((__force gfp_t)___GFP_NOFAIL)//必须无限次重试,因为调用者 不能处理分配失败
#define __GFP_NORETRY ((__force gfp_t)___GFP_NORETRY)//不要重试,当 直接回收页 和 内存碎片整理 不能使得分配成功的时候,应该放弃。
#define __GFP_NOWARN ((__force gfp_t)___GFP_NOWARN)//如果分配失败,不要打印告警信息
#define __GFP_COMP ((__force gfp_t)___GFP_COMP)//把分配的页组成 复合页(compound page)
#define __GFP_ZERO ((__force gfp_t)___GFP_ZERO)//把 页 使用 0 进行初始化
[参考]:https://www.jianshu.com/p/78a978ff48f2
类型修饰符(组合):
#define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)//原子分配 内核使用的页,不能睡眠。调用者为 高优先级,允许 异步回收页
#define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS)//分配 内核 使用的页,可能睡眠。从 低端内存区域 分配,允许 直接回收页 和 异步回收页,允许 读写存储设备,允许 调用到底层文件系统。
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)//
#define GFP_NOWAIT (__GFP_KSWAPD_RECLAIM)//分配 内核 使用的页,不用等待。允许 异步回收页,不允许 直接回收页,不允许 读写存储设备,不允许 调用到底层文件系统。
#define GFP_NOIO (__GFP_RECLAIM)//不允许 读写存储设备,允许 直接回收页 和 异步回收页。
#define GFP_NOFS (__GFP_RECLAIM | __GFP_IO)//不允许 调用到底层文件系统,允许 读写存储设备,允许 直接回收页 和 异步回收页。
#define GFP_USER (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)//分配 用户空间 使用的页,内核或硬件 也可以直接访问,从 普通区域(线性映射区) 分配分配,允许 调用到底层文件系统,允许 读写存储设备,允许 直接回收页 和 异步回收页,允许 实施cpuset内存分配策略。
#define GFP_DMA __GFP_DMA
#define GFP_DMA32 __GFP_DMA32
#define GFP_HIGHUSER (GFP_USER | __GFP_HIGHMEM)//分配 用户空间 使用的页,内核或硬件 不直接访问,从 高端区域 分配分配,物理页使用过程中 不可以移动
#define GFP_HIGHUSER_MOVABLE (GFP_HIGHUSER | __GFP_MOVABLE)//分配 用户空间 使用的页,内核或硬件 不直接访问,从 高端区域 分配分配,物理页可以通过 页回收 或 页迁移技术移动
#define GFP_TRANSHUGE_LIGHT ((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
__GFP_NOMEMALLOC | __GFP_NOWARN) & ~__GFP_RECLAIM) //分配 用户空间 使用的 巨型页,把分配的页块组成 复合页,禁止使用 紧急保留内存,禁止 打印告警信息,不允许 直接回收页 和 异步回收页
#define GFP_TRANSHUGE (GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM)//和 GFP_TRANSHUGE_LIGHT 类似,不同之处在于允许 直接回收页
[参考]:https://www.jianshu.com/p/78a978ff48f2
5.2 常见的gfp_mask使用时机
情形 | 相应标志 |
---|---|
进程上下文,可以睡眠 | 使用GFP_KERNEL |
进程上下文,不可以睡眠 | 使用GFP_ATOMIC,在你睡眠之前或之后以GFP_KERNEL执行内存分配 |
中断处理程序 | 使用GFP_ATOMIC |
软中断 | 使用GFP_ATOMIC |
tasklet | 使用GFP_ATOMIC |
需要用于DMA的内存,可以睡眠 | 使用(GFP_DMA | GFP_KERNEL) |
需要用于DMA的内存,不可以睡眠 | 使用(GFP_DMA | GFP_ATOMIC),或在你睡眠之前执行内存分配 |
5.3 物理内存分配流程
以 alloc_pages为例,简要梳理一下物理页面的分配逻辑:
#define alloc_pages(gfp_mask, order) \
alloc_pages_node(numa_node_id(), gfp_mask, order)
alloc_pages是一个宏,实际分配页面的函数是__alloc_pages_nodemask():
[alloc_pages()->alloc_pages_node()->__alloc_pages()->__alloc_pages_nodemask()]
struct page *__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid,nodemask_t *nodemask)
- 根据gfp_mask计算出相关参数,比如计算出zone id,以便后续判断应该从哪一个zone开始分配内存,以及计算出页面迁移类型等。并将内容填充到 struct alloc_context结构中。
- 尝试分配页面。首先会判断应该从哪个zone分配。根据上一步得到的zone id ,从zone id 代表的zone 开始扫描内存节点中的zonelist去查找适合分配内存的zone。
- 找到合适的zone后,会先检查水位。如果低于水位(LOW),则会调zone_reclaim()函数回收页面。
- 调用buffer_rmqueue()函数从伙伴系统中分配物理页面。
6.物理页面的释放
释放内存页面的核心是把页面添加到伙伴系统适当的free_area链表中。其原理是:在释放内存页面时,首先会检查相邻的页面是否空闲,如果空闲,则会合并成一个大的内存页面,放置到高一阶的空闲链表free_area中。如果还能继续合并相邻的内存块,那么就会继续合并,转移到更高一阶的空闲链表中,这个过程会一直重复,直到所有可合并的页面都已合并。
当需要释放页时,可以使用以下函数释放页:
void __free_pages(struct page *page, unsigned int order)
void free_pages(unsigned long addr, unsigned int order)
#define __free_page(page) __free_pages((page), 0)
#define free_page(addr) free_pages((addr), 0)