1 基础知识
1.1 页
物理地址被分为离散的单元,称之为页。
系统内部许多对内存的操作都是基于单个页的。每个页的大小随体系架构不同而不同,但是目前大多数系统都使用每页 4096 个字节。常量 PAGE_SIZE 给出了在任何指定体系架构下的页大小。
仔细观察内存地址,无论是虚拟的还是物理的,它们都被分为页号和一个页内的偏移量。举个例子,如果使用页大小为 4096 个字节,那么最后的 12 位是偏移量,而剩余的高位则指定了页号。
如果忽略了地址偏移量,并将除去偏移量的剩余位移到右端,称该结构为页帧数。移动位以在页帧数和地址间进行转换是一个常用操作;宏 PAGE_SHIFT 将告诉程序员,必须移动多少位才能完成这个转换。
《LINUX 设备驱动程序》(第三版)P410
1.2 页表
把线性地址映射到物理地址的数据结构称为页表(page table)。页表存放在主存中,并在启用分页单元之前必须由内核对页表进行适当的初始化。 《深入理解 LINUX 内核》P51
用来将虚拟地址空间映射到物理地址空间的数据结构称为页表。 《深入 LINUX 内核架构》P9
对驱动程序作者来说,在 2.6 版内核中删除了对页表直接操作的需求。 《LINUX 设备驱动程序》(第三版)P414
1.3 UMA(一致性访问) / NUMA(非一致性访问)
UMA (uniform memory access)
NUMA(non-uniform memory access)
UMA 计算机将可用内存以连接方式组织起来。SMP 系统中每个处理器访问各个内存区都是同样快。
NUMA 计算机总是多处理器计算机。系统的各个 CPU 都有本地内存,可支持特别快速的访问。各个处理器之间通过总线连接起来,以支持对其他 CPU 的本地内存访问,当然比访问本地内存慢些。
真正的 NUMA 会设置配置选项 CONFIG_NUMA。
《深入 LINUX 内核架构》P108
非一致内存访问(NUMA)模型,在这种模型中,给定 CPU 对不同内存单元的访问时间可能不一样。《深入理解 LINUX 内核》P51
1.4 高端内存和低端内存
虚拟地址空间的内核部分必然小于 CPU 理论地址空间的最大长度。如果物理内存比可以映射到内核地址空间中的数量要多,那么内核必须借助于高端内存(highmem)方法来管理“多余的”内存。
《深入 LINUX 内核架构》P107
使用 32 位系统只能在 4GB 的内存中寻址
在不破坏 32 位应用程序和系统兼容性的情况下,为了能使用更多的内存,处理器制造厂家为他们的产品增加了“地址扩展”特性。其结果是在许多情况下,即使 32 位的处理器都可以在大于 4GB 的物理地址空间寻址。
然而有多少内存可以直接映射到逻辑地址的限制依然存在。只有内存的低端部分(依赖与硬件和内核的设置,一般为 1 到 2GB)用于逻辑地址;剩余部分(高端内存)是没有的。在访问特定的高端内存页前,内核必须建立明确的虚拟映射,使该页可在内核地址空间中被访问。
因此,许多内核数据结构必须被放置在低端内存中;而高端内存更趋向于为用户空间进程页所保留。
《LINUX 设备驱动程序》(第三版)P411
存在于内核空间上的逻辑地址内存。几乎所有现在读者遇到的系统,它全部的内存都是低端内存。
是指那些不存在逻辑地址的内存,这是因为它们处于内核虚拟地址之上。
《LINUX 设备驱动程序》(第三版)P412
64 位地址空间避免了古怪的高端内存域。《深入 LINUX 内核架构》P151
1.5 内存模型
内存模型是从处理器角度看到的物理内存分布情况,内核管理不同内存模型的方式存在差异。内存管理子系统支持3 种内存模型:
(1) 平坦内存(Flat Memory):内存的物理地址空间是连续的,没有空洞。
(2) 不连续内存(Discontiguous Memory):内存的物理地址空间存在空洞,这种模型可以高效地处理空洞。
(3) 稀疏内存(Sparse Memory):内存的物理地址空间存在空洞。如果要支持内存热插拔,只能选择稀疏内存模型。
《Linux 内核深度解析》P140
1.6 ECC / EDAC
1.7 在设备树 或者 ACPI设备信息表中指定内存信息
2 物理内存的管理
2.1 物理内存的组织:node 、ZONE 和 page
2.1.1 简介
内存管理子系统使用节点 (node) 、区域 (zone) 和页 (page) 三级结构描述物理内存。
内存节点使用一个 pglist_data 结构体描述内存布局。内核定义了宏 NODE_DATA(nid) ,它用来获取节点的pglist_data 实例。对于平坦内存模型,只有一个 pglist_data 实例 :contig_page_data 。
《Linux 内核深度解析》P141
Linux2.6 支持非一致性内存访问(NUMA)模型,在这种模型中,给定的 CPU 对不同内存单元的访问时间可能不一样。系统的物理内存被划分为几个节点(node)。在一个单独的节点内,任一给定 CPU 访问页面所需时间都是相同的。
每个节点中的物理内存又可以分为几个管理区(zone)。
所有节点的描述符存放在一个单向链表中,它的第一个元素由 pgdat_list 变量指向。
《深入理解 LINUX 内核》P298
内存划分为结点。每个结点关联到系统中的一个处理器,在内核中表示为 pg_data_t 的实例。
各个结点又划分为内存域,是内存的进一步细分。
一个结点由最多 3 个内存域组成。内核引入下列常量来区分它们。
ZONE_DMA: 标记适合 DMA 的内存域。该区域的长度依赖于处理器类型。
ZONE_DMA32: 标记了使用 32 位地址字可寻址、适合 DMA 的内存域。
ZONE_NORMAL: 标记了可直接映射到内核段的普通内存域。
ZONE_HIGHMEM: 标记了超出内核段的物理内存。
《深入 LINUX 内核架构》P109
ZONE_MOVABLE: 它是一个伪内存区域,用来防止内存碎片。
ZONE_DEVICE: 为支持持久内存(persistent memory)热插拔增加的内存区域。
《Linux 内核深度解析》P142
2.1.2 备用区域列表
如果首选的内存节点和区域不能满足页分配请求,可以从备用的内存区域借用物理页,借用必须遵守以下原则。
<1> 一个内存节点的某个区域类型可以从另一个内存节点的相同区域类型借用物理页,例如节点 0 的普通区域可以从节点 1 的普通区 域借用物理页。
<2> 高区域类型可以从低区域类型借用物理页,例如普通区域可以从 DMA 区域借用物理页。
<3> 低区域类型不能从高区域类型借用物理页,例如 DMA 区域不能从普通区域借用物理页。
《Linux内核深度解析》P154
2.1.3 区域水线 / 内存域水印
2.1.3.1 简介
首选的内存区域在什么情况下从备用区域借用物理页?这个问题要从区域水线开始说起。每个内存区域有 3 个水线。
<1> 高水线:如果内存区域的空闲页数大于高水线,说明该内存区域的内存充足。
<2> 低水线:如果内存区域的空闲页数小于低水线,说明该内存区域的内存轻微不足。
<3> 最低水线:如果内存区域的空闲页数小于最低水线,说明该内存区域的内存严重不足。
最低水线以下的内存称为紧急保留内存。
《Linux 内核深度解析》P156
最低水线以下的内存称为紧急保留内存,在内存严重不足的紧急情况下,给承诺“给我少量紧急保留内存使用,我可以释放更多的内存”的进程使用。
设置了进程标志位 PF_MEMALLOC 的进程可以使用紧急保留内存,标志位 PF_MEMALLOC 表示“给我少量紧急保留内存使用,我可以释放更多的内存”。内存管理子系统以外的子系统不应该使用这个标志位,典型的例子是页回收内核线程 kswapd ,在回收页的过程中可能需要申请内存。
《Linux 内核深度解析》P156
2.1.3.2 设置水线的地方
<1> 内核启动时
core_initcall(init_per_zone_wmark_min)
init_per_zone_wmark_min();
-> setup_per_zone_wmarks();
<2> 通过/proc/sys/vm/min_free_kbytes
min_free_kbytes_sysctl_handler();
-> setup_per_zone_wmarks();
<3> 通过/proc/sys/vm/watermark_scale_factor
watermark_scale_factor_sysctl_handler();
-> setup_per_zone_wmarks();
2.1.3.3 计算水线的算法
《Linux 内核深度解析》P157
《深入理解 LINUX 内核》P303
2.1.3.4 查看每个ZONE的水线
cat /proc/zoneinfo
......
pages free 3328
min 16 (最低水线)
low 20 (低水线)
high 24 (高水线)
......
2.1.3.5 水线对内存分配的影响
请看“3.1.3 内存页分配流程”
2.1.3.6 相关/proc/文件
/proc/sys/vm/min_free_kbytes
/proc/sys/vm/watermark_scale_factor 《Linux 内核深度解析》P156 《深入 LINUX 内核架构》P116
/proc/sys/vm/numa_zonelist_order
/proc/zoneinfo 《Linux 内核深度解析》P155
2.1.4 相关函数
NODE_DATA(nid); //它用来获取节点的 pglist_data 实例;《Linux 内核深度解析》P141
page_to_nid(); //用来得到物理页所属的内存节点的编号;《Linux 内核深度解析》P143
2.3 memblock
2.4 预留物理内存
3 内存分配
3.1 伙伴系统(分配连续的物理页,以页为单位)
3.1.1 简介
在内核初始化完成后,内存管理的责任由伙伴系统承担。伙伴系统基于一种相对简单然而令人吃惊的强大算法,已经伴随我们几乎 40 年。它结合了优秀内存分配器的两个关键特征:速度和效率。
《深入 LINUX 内核架构》P159
内核中很多时候要求分配连续页。为快速检测内存中的连续区域,内核采用了一种父老而历经检验的技术:伙伴系统
《深入 LINUX 内核架构》P11
Linux 采用著名的伙伴系统(buddy system)算法来解决外碎片问题。
《深入理解 LINUX 内核》P312
3.1.2 最大阶数、物理内存页分组 和 struct free_area;
3.1.2.1 内核配置:CONFIG_FORCE_MAX_ZONEORDER
默认值
default "14" if (ARM64_64K_PAGES && TRANSPARENT_HUGEPAGE)
default "12" if (ARM64_16K_PAGES && TRANSPARENT_HUGEPAGE)
default "11"
《arch/arm64/Kconfig》
CONFIG_FORCE_MAX_ZONEORDER配置可以通过make menuconfig修改默认值。
3.1.2.2 物理内存页分组 和 struct free_area;
当内核使用4K页时,CONFIG_FORCE_MAX_ZONEORDER的默认值是11,对应的物理内存页分成11组。
用于管理伙伴数据的主要数组:struct free_area free_area[MAX_ORDER];
free_area[] 数组中各个元素的索引也解释为阶,用于指定对应链表中的连续内存区包含多少个
页帧。第0个链表包含的内存区为单页(2的0次方 =1),第1个链表管理的内存区为两页(2的1次方 =2),第3个管理
的内存区为4页,依次类推。
《深入 LINUX 内核架构》P159,P160
3.1.2.3 /proc/buddyinfo 中获得伙伴系统的当前信息
$ cat /proc/buddyinfo
Node 0, zone DMA 0 0 0 0 0 0 0 0 1 2 2
Node 0, zone DMA32 9 8 8 9 8 7 10 7 8 9 542
Node 0, zone Normal 1812 461 3821 1937 505 323 223 88 38 11 1705
上述输出给出了各个内存域中每个分配阶中空闲项的数目,从左至右,阶依次升高。
《深入 LINUX 内核架构》P161
3.1.4.2 计算单次可申请的连续物理内存最大值
算法:2 的 (CONFIG_FORCE_MAX_ZONEORDER - 1)次方 x PAGE_SIZE
《Linux内核深度解析》P152
例如:
当PAGE_SIZE == 4K时,CONFIG_FORCE_MAX_ZONEORDER默认值是11,单次可申请的连续物理内存最大值 = 2的10次方 x 4K = 4M。
当PAGE_SIZE == 16K时,CONFIG_FORCE_MAX_ZONEORDER默认值是12,单次可申请的连续物理内存最大值 = 2的11次方 x 16K = 32M。
当PAGE_SIZE == 64K时,CONFIG_FORCE_MAX_ZONEORDER默认值是14,单次可申请的连续物理内存最大值 = 2的13次方 x 16K = 128M。
3.1.3 接口函数: alloc_pages();alloc_page();get_zeroed_page();......
alloc_pages();
alloc_page();
__get_free_pages();
__get_free_page();
get_zeroed_page();
《Linux内核深度解析》P163
《奔跑吧Linux内核》卷1:基础架构; P158,P154
3.1.4 内存页分配流程
3.1.4.1 大致流程
《奔跑吧Linux内核》卷1:基础架构; P159
3.1.4.2 快速路径: get_page_from_freelist();
《奔跑吧Linux内核》卷1:基础架构; P164
《Linux内核深度解析》P169
3.1.4.3 慢速路径: __alloc_pages_slowpath();
如果使用低水线分配失败,那么执行慢速路径。
《Linux内核深度解析》P176
3.2 块分配器(slab、slub 和 lsob; 分配连续物理内存,以字节为单位)
《LINUX 设备驱动程序》(第三版)P217
《Linux 内核设计与实现》P197
《深入理解 LINUX 内核》P323
《深入 LINUX 内核架构》P205
3.2.1 slab简介
伙伴系统在分配内存时是以物理页面为单位的,在实际中有很多内存需求是以字节为单位的。slab分配器就是用来解决小块内存分配问题的。
slab分配器最终还是使用伙伴系统来分配实际的物理页面。
《奔跑吧Linux内核》卷1:基础架构; P170
分配和释放数据结构是所有内核中最普遍的操作之一。为了便于数据的频繁分配和回收,编程人员常常会用到空闲链表。空闲链表包含可供使用的、已经分配好的数据结构块。当代码需要一个新的数据结构实例时,就从空闲链表中抓取一个,而不需要分配内存,再把数据放进区。以后,当不再需要这个数据结构的实例时,就把它放回空闲链表,而不是释放它。从这个意义上说,空闲链表相当于对象高速缓存——快速存储频繁使用的对象类型。
在内核中,空闲链表面临的主要问题之一是不能全局控制。当可以内存变得紧缺时,内核无法通知每个空闲链表,让其收缩缓存的大小以便释放出一些内存来。实际上内核根本不知道存在空闲链表。为了弥补之一缺陷,也为了使代码更加稳固,linux 内核提供了 slab 层(也就是所谓的 slab 分配器)。slab 分配器扮演了通用数据结构缓存层的角色。
《Linux 内核设计与实现》P197
Linux 内核的高速缓存管理有时称为“slab 分配器”。
《LINUX 设备驱动程序》(第三版)P217
3.2.2 接口函数(通用缓存):kmalloc() / kfree() / kzalloc()
3.2.2.1 简介
kmalloc()接口建立在 slab 层之上,使用了一组通用高速缓存。
《Linux 内核设计与实现》P197
每次调用 kmalloc 时,内核找到最适合的缓存,并从中分配一个对象满足请求(如果没有刚好适合的缓存,则分配稍大的对象,但不会分配更小的对象)。
《深入 LINUX 内核架构》P209
《Linux内核深度解析》P185
3.2.2.2 注意点
对 kmalloc 能够分配的内存大小,存在一个上限。这个限制随着体系架构的不同以及内存配置选项的不同而变化。如果我们希望代码具有完整的可移植性,则不应该分配大于 128KB 的内存。但是,如果希望得到多于几千字节的内存,则最好使用除了 kmalloc 之外的内存获取方法。
《LINUX 设备驱动程序》(第三版)P217
3.2.2.3 kmalloc(size, flags)接口中 flags 参数的说明
《LINUX 设备驱动程序》(第三版)P214
《深入 LINUX 内核架构》P174
3.2.3 接口函数(专用缓存)
3.2.3.1 创建内存缓存:kmem_cache_create();
《LINUX 设备驱动程序》(第三版)P217
《深入理解 LINUX 内核》P328
《Linux内核深度解析》P185,P186
3.2.3.2 分配对象:kmem_cache_alloc();
《LINUX 设备驱动程序》(第三版)P218
《深入理解 LINUX 内核》P337
《Linux内核深度解析》P185,P186
《奔跑吧Linux内核》卷1:基础架构; P183
3.2.3.3 释放对象:kmem_cache_free();
《LINUX 设备驱动程序》(第三版)P218
《深入理解 LINUX 内核》P338
《Linux内核深度解析》P185,P186
《奔跑吧Linux内核》卷1:基础架构; P183
3.2.3.4 销毁内存缓存:kmem_cache_destroy();
这个释放操作只有在已将从缓存中分配的所有对象都归还后才能成功。所以,模块应该检查 kmem_cache_destroy 的返回状态;如果失败,则表明模块中发生了内存泄漏(因为有一些对象被漏掉了)。
《LINUX 设备驱动程序》(第三版)P219
《Linux内核深度解析》P185,P186
3.2.4 SLUB
3.2.4.1 简介
在配备了大量物理内存的大型计算机上,SLAB分配器的管理数据结构的内存开销比较大,所以设计了SLUB分配器。
《Linux内核深度解析》P185
3.2.5 SLOB
3.2.5.1 简介
在小内存的嵌入式设备上,SLAB分配器的代码太多、太复杂,所以设计了一个精简的SLOB分配器。
《Linux内核深度解析》P185
SLOB分配器的最大特点就是简洁,代码只有600多行,特别适合小内存的嵌入式设备。
《Linux内核深度解析》P204
3.2.6 调试
3.2.6.1 SLUB
内核配置
CONFIG_DEBUG_SLUB
CONFIG_SLUB_DEBUG_ON
CONFIG_SLUB_STATS
启动参数
slub_debug=<调试选项> //为所有内存缓存打开调试选项
slub_debug=<调试选项>,<内存缓存名称> //只为指定的内存缓存打开调试选项
《奔跑吧Linux内核》卷2:调试与案例分析,P197,P198
《Linux内核深度解析》P204
3.2.6.2 /proc/slabinfo
所有活动缓存的列表保存在 /proc/slabinfo 中。
# cat /proc/slabinfo
slabinfo - version: 2.1
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
......
MPTCPv6 0 0 2048 16 8 : tunables 0 0 0 : slabdata 0 0 0
ip6-frags 0 0 184 22 1 : tunables 0 0 0 : slabdata 0 0 0
PINGv6 0 0 1216 26 8 : tunables 0 0 0 : slabdata 0 0 0
RAWv6 182 182 1216 26 8 : tunables 0 0 0 : slabdata 7 7 0
UDPv6 192 192 1344 24 8 : tunables 0 0 0 : slabdata 8 8 0
tw_sock_TCPv6 0 0 248 33 2 : tunables 0 0 0 : slabdata 0 0 0
request_sock_TCPv6 0 0 304 26 2 : tunables 0 0 0 : slabdata 0 0 0
TCPv6 104 104 2432 13 8 : tunables 0 0 0 : slabdata 8 8 0
......
dma-kmalloc-8k 0 0 8192 4 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-4k 0 0 4096 8 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-2k 0 0 2048 16 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-1k 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-512 0 0 512 32 4 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-256 0 0 256 32 2 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-128 0 0 128 32 1 : tunables 0 0 0 : slabdata 0 0 0
......
kmalloc-8k 414 436 8192 4 8 : tunables 0 0 0 : slabdata 109 109 0
kmalloc-4k 3776 3776 4096 8 8 : tunables 0 0 0 : slabdata 472 472 0
kmalloc-2k 2272 2272 2048 16 8 : tunables 0 0 0 : slabdata 142 142 0
kmalloc-1k 2317 2368 1024 32 8 : tunables 0 0 0 : slabdata 74 74 0
kmalloc-512 10790 10880 512 32 4 : tunables 0 0 0 : slabdata 340 340 0
kmalloc-256 9125 9344 256 32 2 : tunables 0 0 0 : slabdata 292 292 0
kmalloc-192 10857 10857 192 21 1 : tunables 0 0 0 : slabdata 517 517 0
kmalloc-128 2112 2112 128 32 1 : tunables 0 0 0 : slabdata 66 66 0
kmalloc-96 3864 3864 96 42 1 : tunables 0 0 0 : slabdata 92 92 0
......
《深入 LINUX 内核架构》P208
《深入理解 LINUX 内核》P329
《Linux内核深度解析》P185
3.2.6.3 slabinfo命令
源码:<kernel-src>/tools/vm/slabinfo.c
help内容
# ./slabinfo -h
slabinfo 4/15/2011. (c) 2007 sgi/(c) 2011 Linux Foundation.
slabinfo [-aABDefhilLnoPrsStTUvXz1] [N=K] [-dafzput] [slab-regexp]
-a|--aliases Show aliases
-A|--activity Most active slabs first
-B|--Bytes Show size in bytes
-D|--display-active Switch line format to activity
-e|--empty Show empty slabs
-f|--first-alias Show first alias
-h|--help Show usage information
-i|--inverted Inverted list
-l|--slabs Show slabs
-L|--Loss Sort by loss
-n|--numa Show NUMA information
-N|--lines=K Show the first K slabs
-o|--ops Show kmem_cache_ops
-P|--partial Sort by number of partial slabs
-r|--report Detailed report on single slabs
-s|--shrink Shrink slabs
-S|--Size Sort by size
-t|--tracking Show alloc/free information
-T|--Totals Show summary information
-U|--Unreclaim Show unreclaimable slabs only
-v|--validate Validate slabs
-X|--Xtotals Show extended summary information
-z|--zero Include empty slabs
-1|--1ref Single reference
-d | --debug Switch off all debug options
-da | --debug=a Switch on all debug options (--debug=FZPU)
-d[afzput] | --debug=[afzput]
f | F Sanity Checks (SLAB_CONSISTENCY_CHECKS)
z | Z Redzoning
p | P Poisoning
u | U Tracking
t | T Tracing
Sorting options (--Loss, --Size, --Partial) are mutually exclusive
使用slabinfo命令发现内存越界访问
《奔跑吧Linux内核》卷2:调试与案例分析,P197,P198,P199
3.2.6.3 slabtop 命令
Active / Total Objects (% used) : 1172773 / 1178933 (99.5%)
Active / Total Slabs (% used) : 31201 / 31201 (100.0%)
Active / Total Caches (% used) : 121 / 174 (69.5%)
Active / Total Size (% used) : 307404.12K / 310802.16K (98.9%)
Minimum / Average / Maximum Object : 0.01K / 0.26K / 10.69K
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
171885 171762 99% 0.19K 8185 21 32740K dentry
155550 155099 99% 0.02K 915 170 3660K lsm_file_cache
142038 142038 100% 0.10K 3642 39 14568K buffer_head
87426 86882 99% 1.15K 3238 27 103616K ext4_inode_cache
69927 69426 99% 0.20K 1793 39 14344K vm_area_struct
61888 61508 99% 0.12K 1934 32 7736K kernfs_node_cache
59264 59264 100% 0.03K 463 128 1852K kmalloc-32
37248 37248 100% 0.06K 582 64 2328K anon_vma_chain
35600 35600 100% 0.62K 1424 25 22784K inode_cache
30016 29247 97% 0.57K 1072 28 17152K radix_tree_node
29376 29376 100% 0.04K 288 102 1152K ext4_extent_status
24512 24512 100% 0.06K 383 64 1532K kmalloc-rcl-64
24448 24261 99% 0.06K 382 64 1528K kmalloc-64
23673 23673 100% 0.10K 607 39 2428K anon_vma
20992 20992 100% 0.02K 82 256 328K kmalloc-16
14656 13374 91% 0.25K 458 32 3664K filp
13770 13770 100% 0.05K 162 85 648K ftrace_event_field
13608 13608 100% 0.07K 243 56 972K Acpi-Operand
12800 12800 100% 0.01K 25 512 100K kmalloc-8
10976 10840 98% 0.50K 343 32 5488K kmalloc-512
10899 10899 100% 0.19K 519 21 2076K kmalloc-192
10465 10465 100% 0.70K 455 23 7280K proc_inode_cache
9376 9211 98% 0.25K 293 32 2344K kmalloc-256
5632 5632 100% 0.02K 22 256 88K kmalloc-cg-16
5184 5184 100% 0.06K 81 64 324K vmap_area
5000 5000 100% 0.31K 200 25 1600K mnt_cache
4914 4914 100% 0.74K 234 21 3744K shmem_inode_cache
4096 4096 100% 0.01K 8 512 32K kmalloc-cg-8
3864 3864 100% 0.09K 92 42 368K kmalloc-96
3864 3864 100% 0.09K 84 46 336K trace_event_file
3840 3840 100% 0.03K 30 128 120K fsnotify_mark_connector
3822 3822 100% 0.19K 182 21 728K ext4_groupinfo_4k
3784 3774 99% 4.00K 473 8 15136K kmalloc-4k
3358 3358 100% 0.69K 146 23 2336K squashfs_inode_cache
3072 3072 100% 0.06K 48 64 192K kmalloc-cg-64
《Linux性能优化》P54
3.2.6.4 vmstat -m 1
# vmstat -m 1
......
Cache Num 总共 大小 Pages
dma-kmalloc-8k 0 0 8192 4
dma-kmalloc-4k 0 0 4096 8
dma-kmalloc-2k 0 0 2048 16
dma-kmalloc-1k 0 0 1024 32
dma-kmalloc-512 0 0 512 32
dma-kmalloc-256 0 0 256 32
dma-kmalloc-128 0 0 128 32
......
Cache Num 总共 大小 Pages
kmalloc-4k 3776 3784 4096 8
kmalloc-2k 2266 2288 2048 16
kmalloc-1k 2297 2368 1024 32
kmalloc-512 10860 10976 512 32
kmalloc-256 9207 9376 256 32
kmalloc-192 10878 10878 192 21
kmalloc-128 2144 2144 128 32
kmalloc-96 3864 3864 96 42
kmalloc-64 24298 24512 64 64
......
3.2.6.5 /sys/kernel/slab/
# ls /sys/kernel/slab/kmalloc-1k/
aliases cpu_slabs min_partial objs_per_slab reclaim_account shrink store_user validate
align ctor objects order red_zone slabs total_objects
cache_dma destroy_by_rcu object_size partial remote_node_defrag_ratio slabs_cpu_partial trace
cpu_partial hwcache_align objects_partial poison sanity_checks slab_size usersize
Documentation/ABI/testing/sysfs-kernel-slab
3.3 不连续页分配器(vmalloc() / vfree(); vmap() / vunmap())
3.3.1 简介
vmalloc 是一个接口函数,内核代码使用它来分配在虚拟内存中连续但在物理内存中不一定连续的内存。
《深入 LINUX 内核架构》P196
当设备长时间运行后,内存碎片化,很难找到连续的物理页。在这种情况下,如果需要分配长度超过一页的内存块,可以使用不连续页分配器,分配的虚拟地址连续但是物理地址不连续的内存块。
《Linux内核深度解析》P207
3.3.2 vmalloc()流程
《奔跑吧Linux内核》卷1:基础架构; P194
3.3.3 注意点
在大多数情况下不鼓励使用 vmalloc。通过 vmalloc 获得的内存使用起来效率不高。
《LINUX 设备驱动程序》(第三版)P225
vmalloc 的开销要比__get_free_pages 大,因为它不但要获取内存,还要建立页表。
vmalloc 函数的一个小缺点是它不能在原子上下文中使用。
《LINUX 设备驱动程序》(第三版)P226
vmalloc()仅在不得已时才会使用——典型的就是为了获得大块内存时,例如,当模块被动态插入到内核中时,就把模块装载到由 vmalloc()分配的内存上。
《Linux 内核设计与实现》P196
3.4 kvmalloc() / kvfree()
先尝试使用kmalloc()分配内存块,如果失败,那么使用vmalloc()函数分配不连续的物理页。
《Linux内核深度解析》P207
3.5 每处理器内存分配器
《Linux内核深度解析》P210
3.6 分配函数的选择
《Linux 内核设计与实现》P209
6 页
6.1 页回收
6.2 缺页异常 / 缺页错误
6.3 巨型页
6.4 页迁移
6.5 页压缩
6.6 页交换
7 避免内存碎片(反碎片)
7.1 简介
伙伴系统确实工作得非常好。但是在 Linux 内存管理方面,有一个长期存在的问题:在系统启动并长期运行后,物理内存会产生很多碎片。 《深入 LINUX 内核架构》P161
通过伙伴系统可以在某种程度上减少内存碎片,但无法完全消除。 《深入 LINUX 内核架构》P12
内核的方式是反碎片(anti-fragmentation),即试图从最开始尽可能防止碎片。《深入 LINUX 内核架构》P161
内存碎片分为内部碎片和外部碎片,内部碎片指内存页里面的碎片,外部碎片指空闲的内存页分散,很难找到一组物理地址连续的空闲内存页。 《Linux 内核深度解析》P288
7.2 反碎片的技术
<1> 2.6.23 版本引入了虚拟可移动区域。
<2> 2.6.23 版本引入了成块回收(lumpy reclaim,有的书中翻译为集中回收),3.5 版本废除,被内存碎片整理技术取代。成块回收不是一个完整的解决方案,它只是缓解了碎片问题。
<3> 2.6.24 版本引入了根据可移动性分组的技术,把物理页分为不可移动页、可移动页和可回收页 3 种类型。
<4> 2.6.35 版本引入了内存碎片整理技术。
《Linux 内核深度解析》P288
7.3 虚拟可移动区域
7.3.1 简介
可移动区域(ZONE_MOVABLE)是一个伪内存区域,基本思想很简单:把物理内存分为两个区域,一个区域用于分配不可移动的页,另一个区域用于分配可移动的页,防止不可移动页向可移动区域引入碎片。 《Linux 内核深度解析》P289
7.3.2 使用方法
《Linux 内核深度解析》P289
7.4 根据可移动性分组
为了预防内存碎片,内核根据可移动性把物理页分为 3 种类型。
<1> 不可移动页:位置必须固定,不能移动,直接映射到内核虚拟地址空间的页属于这一类。
<2> 可移动页:使用页表映射的属于这一类,可移动到其它位置,然后修改页表映射。
<3> 可回收页:不能移动,但可以回收,需要数据的时候可以重新从数据源获取。后备存储设备支持的页属于这一类。
《Linux 内核深度解析》P158
<1> 不可移动页:在内存中有固定的位置,不能移动到其它地方。核心内核分配的大多数内存属于该类别。
<2> 可回收页:不能直接移动,但可以删除,其内存可以从某些源重新生成。例如,映射自文件的数据数据该类别。kswapd 守护进程会根据可回收页访问的频繁程度,周期性的释放此类内存。
<3> 可移动页可以随意移动。属于用户空间的应用程序属于该类别。它们是通过页表映射的。
《深入 LINUX 内核架构》P162
迁移类型:
MIGRATE_UNMOVABLE,
MIGRATE_RECLAIMABLE,
MIGRATE_MOVABLE,
MIGRATE_PCPTYPES,
......
7.5 碎片指数
7.5.1 简介
碎⽚指数趋向0表⽰分配失败是因为内存不⾜,碎⽚指数趋向1000表⽰分配失败是因为内存碎⽚。
《Linux内核深度解析》P292
The extfrag/extfrag_index file in debugfs shows what the fragmentation index for each order is in each zone in the system. Values tending towards 0 imply allocations would fail due to lack of memory, values towards 1000 imply failures are due to fragmentation and -1 implies that the allocation will succeed as long as watermarks are met. 《Documentation/admin-guide/sysctl/vm.rst》
7.5.2 查看碎片指数
cat /sys/kernel/debug/extfrag/extfrag_index
7.5.3 设置碎片的阈值
/proc/sys/vm/extfrag_threshold
如果碎⽚指数阈值,那么选择内存碎⽚整理。《《Linux内核深度解析》P292》
7.6 内存碎片整理(memory compaction)
7.6.1 简介
基本思想是:从内存区域的底部扫描已分配的可移动页,从内存区域的顶部扫描空闲页,把底部的可移动页移动到顶部的空闲页,在底部形成连续的空闲页。 《Linux 内核深度解析》P291
7.6.2 使用方法
打开内核配置:CONFIG_COMPACTION
向/proc/sys/vm/compact_memory 文件写入任何整数值(数值没有意义),触发内存碎片整理。
文件/proc/sys/vm/compact_unevictable_allowed 用来设置是否允许内存碎片整理移动不可回收的页,设置为 1 表示允许。
文件/proc/sys/vm/extfrag_threshold 用来设置外部碎片的阀值,取值范围 0~1000,默认 500。
《Linux 内核深度解析》P292
7.6.3 触发内存碎片整理的时机
- 向/proc/sys/vm/compact_memory 文件写入任意值。
- ⻚分配器使⽤最低⽔线分配⻚失败以后。
- ⻚分配器直接回收⻚以后分配连续⻚依然失败。
- 每个内存节点有⼀个⻚回收线程 和 ⼀个内存碎⽚整理线程,当页回收线程准备睡眠一小段时间的时候,唤醒内存碎片整理线程。
《Linux内核深度解析》P293、《奔跑吧Linux内核》 卷1:基础架构;P330
7.6.4 内存碎片整理线程
线程名称:kcompactd<node_id>
《Linux内核深度解析》P293
8 内存耗尽杀手
8.1 简介
当内存严重不足的时候,页分配器在多次尝试直接页回收失败后,就会调用内存耗尽杀手(OOM killer,OOM 是“Out of Memory”的缩写),选择进程杀死,释放内存。 《Linux 内核深度解析》P338
8.2 使用方法
内存耗尽杀手没有配置宏,可配置的参数如下:
<1> /proc/sys/vm/oom_kill_allocating_task ,是否允许杀死正在申请分配内存并触发内存耗尽的进程。
<2> /proc/sys/vm/oom_dump_tasks ,是否允许内存耗尽杀手杀死进程的时候打印所有用户进程的内存使用信息。
<3> /proc/sys/vm/panic_on_oom,是否允许在内存耗尽的时候内核恐慌,重启系统。
《Linux 内核深度解析》P338
8.3 内存耗尽杀手计算进程的坏蛋分数
《Linux 内核深度解析》P338
9 内存资源控制器 / 控制组(cgroup) / 内存控制组(memcg)
9.1 简介
控制组(cgroup)的内存资源控制器用来控制一组进程的内存使用量,启用内存资源控制器的指控组简称内存控制组(memcg)。控制组把各种资源控制器称为子系统,内存资源控制器也称为内存子系统。 《Linux 内核深度解析》P340
控制组版本 1 和控制组版本 2 的内存资源控制器是互斥的。如果使用了控制组版本 1 的内存资源控制器,就不能使用控制组版本 2 的内存资源控制器;同样,如果使用了控制组版本 2 的内存资源控制器,就不能使用控制组版本 1 的内存资源控制器。
《Linux 内核深度解析》P341
9.2 控制组版本 1 的内存资源控制器的配置方法
<1> 在目录”/sys/fs/cgroup”下挂载 tmpfs 文件系统。
mount -t tmpfs none /sys/fs/cgroup
<2> 在目录”/sys/fs/cgroup”下创建目录”memory”
mkdir /sys/fs/cgroup/memory
<3> 在目录”/sys/fs/cgroup/memory/”下挂载 cgroup 文件系统,把内存资源控制器关联到控制组层级树。
mount -t cgroup -o memory none /sys/fs/cgroup/memory/
<4> 创建新的控制组
mkdir /sys/fs/cgroup/memory/memcg0
<5> 设置内存使用的限制。例如把控制组的内存使用限制设置为 4MB
echo 4M > /sys/fs/cgroup/memory/memcg0/memory.limit_in_bytes
<6> 把线程加入控制组
echo <pid> /sys/fs/cgroup/memory/memcg0/tasks
<7> 页可以把线程组加入控制组,指定线程组中任意一个线程的标识符,就会把线程组的所有线程加入任务组。
echo <pid> /sys/fs/cgroup/memory/memcg0/cgroup.procs
《Linux 内核深度解析》P342
10 文件缓存
a 内存调试工具
a.1 内存错误检测工具 KASAN
a.1.1 简介
内核地址消毒剂(Kernel Address SANitizer,KASAN),是一个动态的内存错误检查工具,为发现“释放后使用”和“越界访问”这两类缺陷提供了快速和综合的解决方案。 《Linux 内核深度解析》P401
a.1.2 对编译器的要求 和 对内核代码版本的要求
KASAN 使用编译时插桩检查每个内存访问,要求 GCC 编译器的版本至少是 4.9.2,检查栈和全局变量的越界访问需要 GCC 编译器的版本至少是 5.0
内核支持 KASAN 的进展如下
<1> 4.0 版本引入 KASAN,仅 x86_64 架构支持,只有 SLUB 分配器支持 KASAN。
<2> 4.4 版本的 AMD64 架构支持 KASAN。
<3> 4.6 版本的 SLAB 分配器支持 KASAN。
《Linux 内核深度解析》P401
a.1.3 使用方法
《Linux 内核深度解析》P401
a.2 BCC工具集
a.3 bpftrace工具集
11 在系统引导时获取缓冲区(bootmem分配器)
《LINUX 设备驱动程序》(第三版)P230