linux内核内存slab,伙伴系统,内存碎片,内存耗尽(OOM)杀手,内存资源控制器memcg,KASAN学习笔记(内容正在更新...)

4 篇文章 0 订阅
1 篇文章 0 订阅

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 触发内存碎片整理的时机

  1. 向/proc/sys/vm/compact_memory 文件写入任意值。
  2. ⻚分配器使⽤最低⽔线分配⻚失败以后。
  3. ⻚分配器直接回收⻚以后分配连续⻚依然失败。
  4. 每个内存节点有⼀个⻚回收线程 和 ⼀个内存碎⽚整理线程,当页回收线程准备睡眠一小段时间的时候,唤醒内存碎片整理线程。

                《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

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值