From b714befe262fb174f7b1c8d1218a6ace24165f07 Mon Sep 17 00:00:00 2001 From: gatieme Date: Sun, 2 May 2021 17:04:13 +0800 Subject: [PATCH] desription/memory: debug info --- study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md | 158 ++++++++++++------ 1 file changed, 111 insertions(+), 47 deletions(-) diff --git a/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md b/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md index 617ccbc..b7b35b6 100755 --- a/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md +++ b/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md @@ -107,13 +107,13 @@ **-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 正文 -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*** -内存管理的目标是提供一种方法, 为实现各种目的而在各个用户之间实现内存共享. 内存管理方法应该实现以下两个功能: +内存管理的目标是提供一种方法, 为实现各种目的而在各个用户之间实现内存共享. 内存管理方法应该实现以下两个功能: * 最小化管理内存所需的时间 * 最大化用于一般应用的可用内存(最小化管理开销) -内存管理实际上是一种关于权衡的零和游戏. 您可以开发一种使用少量内存进行管理的算法, 但是要花费更多时间来管理可用内存. 也可以开发一个算法来有效地管理内存, 但却要使用更多的内存. 最终, 特定应用程序的需求将促使对这种权衡作出选择. +内存管理实际上是一种关于权衡的零和游戏. 您可以开发一种使用少量内存进行管理的算法, 但是要花费更多时间来管理可用内存. 也可以开发一个算法来有效地管理内存, 但却要使用更多的内存. 最终, 特定应用程序的需求将促使对这种权衡作出选择. | 时间 | 特性 | 描述 | 是否合入主线 | 链接 | @@ -123,7 +123,7 @@ | 2016/06/27 | [mm: mirrored memory support for page buddy allocations](http://lore.kernel.org/patchwork/patch/574230) | 内存镜像的功能 | RFC v2 ☐ | [PatchWork](https://lore.kernel.org/patchwork/patch/574230) | | 2018/05/17 | [Speculative page faults](http://lore.kernel.org/patchwork/patch/906210) | SPF | v11 ☐ | [PatchWork](https://lore.kernel.org/patchwork/patch/906210) | | 2018/07/09 | [Improve shrink_slab() scalability (old complexity was O(n^2), new is O(n))](http://lore.kernel.org/patchwork/patch/960597) | 内存镜像的功能 | RFC v2 ☐ | [PatchWork](https://lore.kernel.org/patchwork/patch/960597) | -| 2020/02/24 | [Fine grained MM locking](https://patchwork.kernel.org/project/linux-mm/cover/20200224203057.162467-1-walken@google.com) | MM lockless | RFC ☐ | [PatchWork](https://patchwork.kernel.org/project/linux-mm/cover/20200224203057.162467-1-walken@google.com), [fine_grained_mm.pdf](https://linuxplumbersconf.org/event/4/contributions/556/attachments/304/509/fine_grained_mm.pdf) | +| 2020/02/24 | [Fine grained MM locking](https://patchwork.kernel.org/project/linux-mm/cover/20200224203057.162467-1-walken@google.com) | MM lockless | RFC ☐ | [PatchWork](https://patchwork.kernel.org/project/linux-mm/cover/20200224203057.162467-1-walken@google.com), [fine_grained_mm.pdf](https://linuxplumbersconf.org/event/4/contributions/556/attachments/304/509/fine_grained_mm.pdf) | 社区的内存开发者 @@ -135,9 +135,10 @@ [Joonsoo Kim](https://lore.kernel.org/patchwork/project/lkml/list/?submitter=13703&state=%2A&archive=both) + * 关于碎片化 -内存按 chunk 分配, 每个程序保留的 chunk 的大小和时间都不同. 一个程序可以多次请求和释放 `memory chunk`. 程序一开始时, 空闲内存有很多并且连续, 随后大的连续的内存区域碎片化, 变成更小的连续区域, 最终程序无法获取大的连续的 memory chunk. +内存按 chunk 分配, 每个程序保留的 chunk 的大小和时间都不同. 一个程序可以多次请求和释放 `memory chunk`. 程序一开始时, 空闲内存有很多并且连续, 随后大的连续的内存区域碎片化, 变成更小的连续区域, 最终程序无法获取大的连续的 memory chunk. | 碎片化类型 | 描述 | 示例 | @@ -156,11 +157,11 @@ -每个内存管理器都使用了一种基于堆的分配策略. 在这种方法中, 大块内存(称为 堆)用来为用户定义的目的提供内存. 当用户需要一块内存时, 就请求给自己分配一定大小的内存. 堆管理器会查看可用内存的情况(使用特定算法)并返回一块内存. 搜索过程中使用的一些算法有first-fit(在堆中搜索到的第一个满足请求的内存块) 和 best-fit(使用堆中满足请求的最合适的内存块). 当用户使用完内存后, 就将内存返回给堆. +每个内存管理器都使用了一种基于堆的分配策略. 在这种方法中, 大块内存(称为 堆)用来为用户定义的目的提供内存. 当用户需要一块内存时, 就请求给自己分配一定大小的内存. 堆管理器会查看可用内存的情况(使用特定算法)并返回一块内存. 搜索过程中使用的一些算法有first-fit(在堆中搜索到的第一个满足请求的内存块) 和 best-fit(使用堆中满足请求的最合适的内存块). 当用户使用完内存后, 就将内存返回给堆. -这种基于堆的分配策略的根本问题是碎片(fragmentation). 当内存块被分配后, 它们会以不同的顺序在不同的时间返回. 这样会在堆中留下一些洞, 需要花一些时间才能有效地管理空闲内存. 这种算法通常具有较高的内存使用效率(分配需要的内存), 但是却需要花费更多时间来对堆进行管理. +这种基于堆的分配策略的根本问题是碎片(fragmentation). 当内存块被分配后, 它们会以不同的顺序在不同的时间返回. 这样会在堆中留下一些洞, 需要花一些时间才能有效地管理空闲内存. 这种算法通常具有较高的内存使用效率(分配需要的内存), 但是却需要花费更多时间来对堆进行管理. -另外一种方法称为 buddy memory allocation, 是一种更快的内存分配技术, 它将内存划分为 2 的幂次方个分区, 并使用 best-fit 方法来分配内存请求. 当用户释放内存时, 就会检查 buddy 块, 查看其相邻的内存块是否也已经被释放. 如果是的话, 将合并内存块以最小化内存碎片. 这个算法的时间效率更高, 但是由于使用 best-fit 方法的缘故, 会产生内存浪费. +另外一种方法称为 buddy memory allocation, 是一种更快的内存分配技术, 它将内存划分为 2 的幂次方个分区, 并使用 best-fit 方法来分配内存请求. 当用户释放内存时, 就会检查 buddy 块, 查看其相邻的内存块是否也已经被释放. 如果是的话, 将合并内存块以最小化内存碎片. 这个算法的时间效率更高, 但是由于使用 best-fit 方法的缘故, 会产生内存浪费. ## 2.1.1 页分配器: 伙伴分配器[12](#ref-anchor-12) ------- @@ -209,7 +210,7 @@ ### 2.1.1.2 通过迁移类型分组来实现反碎片 ------- -从2.6.32.25开始, linux在伙伴管理系统中引入迁移类型(migrate type)这么一个概念, 用于避免系统在长期运行过程中产生外碎片. +从2.6.32.25开始, linux在伙伴管理系统中引入迁移类型(migrate type)这么一个概念, 用于避免系统在长期运行过程中产生外碎片. 为什么要引入迁移类型. 我们都知道伙伴系统是针对于解决外碎片的问题而提出的, 那么为什么还要引入这样一个概念来避免碎片呢? @@ -287,7 +288,7 @@ Linux 为每个 zone 都设置了独立的 min, low 和 high 三个档位的 wat -当发现某个 ZONE 上内存分配可能出现紧张的时候, 那么有两种选择: +当发现某个 ZONE 上内存分配可能出现紧张的时候, 那么有两种选择: | 方式 | 描述 | 注意 | 触发场景 | |:---:|:----:|:---:|:-------:| @@ -302,7 +303,7 @@ Linux 为每个 zone 都设置了独立的 min, low 和 high 三个档位的 wat 之前在 `__alloc_pages_nodemas` 中. 页面分配器基于每个节点的 ZONE 迭代, 而没有考虑抗碎片化. 补丁 1 [mm, page_alloc: spread allocations across zones before introducing fragmentation](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6bb154504f8b496780ec53ec81aba957a12981fa), 因此引入了一个新的选项 ALLOC_NOFRAGMENT, 来实现页面分配时候的抗外碎片化. 在特殊的场景下, 倾向于有限分配较低 ZONE 区域的内存, 而不是对较高的区域做切片. 补丁 1 增加了 `__alloc_pages_nodemas` 的 FRAGMENT AWARE 支持后, 页面分配的行为发生了变化. 如果发现当前 ZONE 中存在碎片化倾向(开始尝试执行 `__rmqueue_fallback` 去其他分组窃取内存)的时候: -* 如果分配内存的 order < pageblock_order 时, 就认为此操作即将导致外碎片化问题, 则倾向于从低端的 ZONE 中分配. +* 如果分配内存的 order < pageblock_order 时, 就认为此操作即将导致外碎片化问题, 则倾向于从低端的 ZONE 中分配. * 如果已经发生了碎片化, 或者分配的内存超过了 pageblock_order, 则倾向于从其他 MIGRATE_TYPE 中窃取内存过来分配. @@ -310,7 +311,7 @@ Linux 为每个 zone 都设置了独立的 min, low 和 high 三个档位的 wat 补丁 2-4 [1c30844d2dfe mm: reclaim small amounts of memory when an external fragmentation event occurs ](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1c30844d2dfe) 则引入了 boost_watermark 机制, 在外部碎片事件发生时暂时提高水印. kswapd 唤醒以回收少量旧内存, 然后在完成时唤醒 kcompactd 以稍微恢复系统. 这在slowpath中引入了一些开销. 提升的级别可以根据对碎片和分配延迟的容忍程度进行调整或禁用. -补丁5暂停了一些可移动的分配请求, 以让补丁4的 kswapd 取得一些进展. 档位的持续时间很短, 但是如果可以容忍更大的档位, 则可以调整系统以避免碎片事件. +补丁5暂停了一些可移动的分配请求, 以让补丁4的 kswapd 取得一些进展. 档位的持续时间很短, 但是如果可以容忍更大的档位, 则可以调整系统以避免碎片事件. 整个补丁在测试场景下将外碎片时间减少 94% 以上, 这收益大部分来自于补丁 1-4, 但补丁 5 可以处理罕见的特例, 并为THP分配成功率提供调整系统的选项, 以换取一些档位来控制碎片化. @@ -321,13 +322,13 @@ Linux 为每个 zone 都设置了独立的 min, low 和 high 三个档位的 wat 在使用伙伴系统申请内存页面时, 如果所请求的 migratetype 的空闲页面列表中没有足够的内存, 伙伴系统尝试从其他不同的页面中窃取内存. -这会造成减少永久碎片化, 因此伙伴系统使用了各种各样启发式的方法, 尽可能的使这一事件不要那么频繁地触发, 最主要的思路是尝试从拥有最多免费页面的页面块中窃取, 并可能一次窃取尽量多的页面. 但是精确地搜索这样的页面块, 并且一次窃取整个页面块, 是昂贵的, 因此启发式方法是免费的列出从MAX_ORDER到请求的顺序, 并假设拥有最高次序空闲页面的块可能也拥有总数最多的空闲页面. +这会造成减少永久碎片化, 因此伙伴系统使用了各种各样启发式的方法, 尽可能的使这一事件不要那么频繁地触发, 最主要的思路是尝试从拥有最多免费页面的页面块中窃取, 并可能一次窃取尽量多的页面. 但是精确地搜索这样的页面块, 并且一次窃取整个页面块, 是昂贵的, 因此启发式方法是免费的列出从MAX_ORDER到请求的顺序, 并假设拥有最高次序空闲页面的块可能也拥有总数最多的空闲页面. -很有可能, 除了最高顺序的页面, 我们还从同一块中窃取低顺序的页面. 但我们还是分走了最高订单页面. 这是一种浪费, 会导致碎片化, 而不是避免碎片化. +很有可能, 除了最高顺序的页面, 我们还从同一块中窃取低顺序的页面. 但我们还是分走了最高订单页面. 这是一种浪费, 会导致碎片化, 而不是避免碎片化. -因此, 这个补丁将__rmqueue_fallback()更改为仅仅窃取页面并将它们放到请求的migratetype的自由列表中, 并且只报告它是否成功. 然后我们使用__rmqueue_least()选择(并最终分割)最小的页面. 这一切都是在区域锁定下发生的, 所以在这个过程中没有人能从我们这里偷走它. 这应该可以减少由于回退造成的碎片. 在最坏的情况下, 我们只是窃取了一个最高顺序的页面, 并通过在列表之间移动它, 然后删除它而浪费了一些周期, 但后退并不是真正的热门路径, 所以这不应该是一个问题. 作为附带的好处, 该补丁通过重用__rmqueue_least()删除了一些重复的代码. +因此, 这个补丁将__rmqueue_fallback()更改为仅仅窃取页面并将它们放到请求的migratetype的自由列表中, 并且只报告它是否成功. 然后我们使用__rmqueue_least()选择(并最终分割)最小的页面. 这一切都是在区域锁定下发生的, 所以在这个过程中没有人能从我们这里偷走它. 这应该可以减少由于回退造成的碎片. 在最坏的情况下, 我们只是窃取了一个最高顺序的页面, 并通过在列表之间移动它, 然后删除它而浪费了一些周期, 但后退并不是真正的热门路径, 所以这不应该是一个问题. 作为附带的好处, 该补丁通过重用__rmqueue_least()删除了一些重复的代码. | 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | @@ -385,36 +386,36 @@ https://lore.kernel.org/patchwork/cover/408914 **2.0 版本时代(1996年引入)** -这是最早引入的对象分配器, Linux 所使用的 slab 分配器的基础是 Jeff Bonwick 为 SunOS 操作系统首次引入的一种算法. Jeff 的分配器是围绕对象缓存进行的. 在内核中, 会为有限的对象集(例如文件描述符和其他常见结构)分配大量内存. Jeff 发现对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间. 因此他的结论是不应该将内存释放回一个全局的内存池, 而是将内存保持为针对特定目而初始化的状态. 例如, 如果内存被分配给了一个互斥锁, 那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数(mutex_init)即可. 后续的内存分配不需要执行这个初始化函数, 因为从上次释放和调用析构之后, 它已经处于所需的状态中了. 这里想说的主要是我们得到一块很原始的内存, 必须要经过一定的初始化之后才能用于特定的目的. 当我们用slab时, 内存不会被释放到全局内存池中, 所以还是处于特定的初始化状态的. 这样就能加速我们的处理过程. +这是最早引入的对象分配器, Linux 所使用的 slab 分配器的基础是 Jeff Bonwick 为 SunOS 操作系统首次引入的一种算法. Jeff 的分配器是围绕对象缓存进行的. 在内核中, 会为有限的对象集(例如文件描述符和其他常见结构)分配大量内存. Jeff 发现对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间. 因此他的结论是不应该将内存释放回一个全局的内存池, 而是将内存保持为针对特定目而初始化的状态. 例如, 如果内存被分配给了一个互斥锁, 那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数(mutex_init)即可. 后续的内存分配不需要执行这个初始化函数, 因为从上次释放和调用析构之后, 它已经处于所需的状态中了. 这里想说的主要是我们得到一块很原始的内存, 必须要经过一定的初始化之后才能用于特定的目的. 当我们用slab时, 内存不会被释放到全局内存池中, 所以还是处于特定的初始化状态的. 这样就能加速我们的处理过程. -Linux slab 分配器使用了这种思想和其他一些思想来构建一个在空间和时间上都具有高效性的内存分配器. +Linux slab 分配器使用了这种思想和其他一些思想来构建一个在空间和时间上都具有高效性的内存分配器. -1. 小对象的申请和释放通过slab分配器来管理. +1. 小对象的申请和释放通过slab分配器来管理. SLAB 基于页分配器分配而来的页面(组), 实现自己的对象缓存管理. 它提供预定尺寸的对象缓存, 也支持用户自定义对象缓存 -2. slab分配器有一组高速缓存, 每个高速缓存保存同一种对象类型, 如i节点缓存、PCB缓存等. +2. slab分配器有一组高速缓存, 每个高速缓存保存同一种对象类型, 如i节点缓存、PCB缓存等. 维护着每个 CPU , 每个 NUMA node 的缓存队列层级, 可以提供高效的对象分配 -3. 内核从它们各自的缓存种分配和释放对象. +3. 内核从它们各自的缓存种分配和释放对象. 4. 每种对象的缓存区由一连串slab构成, 每个slab由一个或者多个连续的物理页面组成 - * 这些页面种包含了已分配的缓存对象, 也包含了空闲对象. + * 这些页面种包含了已分配的缓存对象, 也包含了空闲对象. * 还支持硬件缓存对齐和着色, 所谓着色, 就是把不同对象地址, 以缓存行对单元错开, 从而使不同对象占用不同的缓存行, 从而提高缓存的利用率并获得更好的性能 -与传统的内存管理模式相比, slab 缓存分配器提供了很多优点. 首先, 内核通常依赖于对小对象的分配, 它们会在系统生命周期内进行无数次分配. slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能, 从而避免了常见的碎片问题. slab 分配器还支持通用对象的初始化, 从而避免了为同一目而对一个对象重复进行初始化. 最后, slab 分配器还可以支持硬件缓存对齐和着色, 这允许不同缓存中的对象占用相同的缓存行, 从而提高缓存的利用率并获得更好的性能. +与传统的内存管理模式相比, slab 缓存分配器提供了很多优点. 首先, 内核通常依赖于对小对象的分配, 它们会在系统生命周期内进行无数次分配. slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能, 从而避免了常见的碎片问题. slab 分配器还支持通用对象的初始化, 从而避免了为同一目而对一个对象重复进行初始化. 最后, slab 分配器还可以支持硬件缓存对齐和着色, 这允许不同缓存中的对象占用相同的缓存行, 从而提高缓存的利用率并获得更好的性能. ### 2.1.2.2 SLUB ------- -随着大规模多处理器系统和NUMA系统的广泛应用, slab终于暴露出不足: +随着大规模多处理器系统和NUMA系统的广泛应用, slab终于暴露出不足: 1. 复杂的队列管理 2. 管理数据和队列存储开销较大 3. 长时间运行partial队列可能会非常长 4. 对NUMA支持非常复杂 -为了解决这些高手们开发了slub: 改造 page 结构来削减 slab 管理结构的开销、每个 CPU 都有一个本地活动的 slab(kmem_cache_cpu)等. 对于小型的嵌入式系统存在一个 slab模拟层slob, 在这种系统中它更有优势. +为了解决这些高手们开发了slub: 改造 page 结构来削减 slab 管理结构的开销、每个 CPU 都有一个本地活动的 slab(kmem_cache_cpu)等. 对于小型的嵌入式系统存在一个 slab模拟层slob, 在这种系统中它更有优势. **2.6.22(2007年7月发布)** @@ -453,7 +454,7 @@ SLUB 在解决了上述的问题之上, 提供与 SLAB 完全一样的接口, ### 2.1.3.1 VMALLOC 大内存分配器 ------- -小内存的问题算是解决了, 但还有一个大内存的问题: 用伙伴系统分配 8 x 4KB 大小以上的的数据时, 只能从 16 x 4KB 的空闲列表里面去找(这样得到的物理内存是连续的), 但很有可能系统里面有内存, 但是伙伴系统分配不出来, 因为他们被分割成小的片段. 那么, vmalloc 就是要用这些碎片来拼凑出一个大内存, 相当于收集一些"边角料", 组装成一个成品后"出售". +小内存的问题算是解决了, 但还有一个大内存的问题: 用伙伴系统分配 8 x 4KB 大小以上的的数据时, 只能从 16 x 4KB 的空闲列表里面去找(这样得到的物理内存是连续的), 但很有可能系统里面有内存, 但是伙伴系统分配不出来, 因为他们被分割成小的片段. 那么, vmalloc 就是要用这些碎片来拼凑出一个大内存, 相当于收集一些"边角料", 组装成一个成品后"出售". | 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | @@ -867,7 +868,7 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 -## 2.7.1 HUGETLB支持 +## 2.7.1 HUGETLB支持 ------- @@ -889,7 +890,7 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 这一功能的主要使用者是数据库程序. -## 2.7.2 透明大页的支持 +## 2.7.2 透明大页的支持 ------- @@ -986,7 +987,7 @@ https://lwn.net/Articles/761118 -## 2.10.1 内存热插入支持 +## 2.10.1 内存热插入支持 ------- **2.6.15(2006年1月发布)** @@ -1093,7 +1094,7 @@ Linux 内核 从 3.X 系列开始陆续加入 tmem 相关的基础设施支持, -## 2.11.1 前端接口之 CLEANCACHE +## 2.11.1 前端接口之 CLEANCACHE ------- **3.0(2011年7月发布)** @@ -1107,7 +1108,7 @@ Linux 内核 从 3.X 系列开始陆续加入 tmem 相关的基础设施支持, -## 2.11.2 前端接口之 FRONTSWAP +## 2.11.2 前端接口之 FRONTSWAP ------- **3.5(2012年7月发布)** @@ -1121,7 +1122,7 @@ Linux 内核 从 3.X 系列开始陆续加入 tmem 相关的基础设施支持, -## 2.11.3 后端之 ZCACHE +## 2.11.3 后端之 ZCACHE ------- @@ -1210,7 +1211,7 @@ FRONTSWAP 对应的另一个后端叫 ZSWAP[35](#refer-anchor-35). ZS -## 2.12.2 DAX +## 2.12.2 DAX ------- @@ -1236,7 +1237,7 @@ FRONTSWAP 对应的另一个后端叫 ZSWAP[35](#refer-anchor-35). ZS -## 2.13.1 页分配的调试支持 +## 2.13.1 页分配的调试支持 ------- @@ -1276,9 +1277,11 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, - 对上一次分配者进行记录的支持等 +## 2.13.3 内存检测工具 +------- -## 2.13.3 错误注入机制 +### 2.13.3.1 错误注入机制 ------- **2.6.20(2007年2月发布)** @@ -1286,9 +1289,7 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, 内核有着极强的健壮性, 能对各种错误异常情况进行合理的处理. 然而, 毕竟有些错误实在是极小可能发生, 测试对这种小概率异常情况的处理的代码实在是不方便. 所以, 2.6.20中内核引入了错误注入机制, 其中跟 MM 相关的有两个, 一个是对页分配器的失败注入, 一个是对 SLAB 对象分配器的失败注入. 这两个注入机制, 可以触发内存分配失败, 以测试极端情况下(如内存不足)系统的处理情况. - - -## 2.13.4 KMEMCHECK - 内存非法访问检测工具 +### 2.13.3.2 KMEMCHECK - 内存非法访问检测工具 ------- **2.6.31(2009年9月发布)** @@ -1314,8 +1315,7 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, 这里面有个问题是, **present** 标记在第一次缺页异常后将被置位, 之后如果再次访问, KMEMCHECK 如何再次利用该机制来检测呢? 答案是内核在上述处理后还**打开了单步调试功能,** 所以 CPU 接着执行下一条指令前, 又陷入了调试陷阱(debug trap, 详情请查看 CPU 文档), 在处理程序中, 内核又会把该页的 **present** 标记会清除. - -## 2.13.4 KMEMLEAK - 内存泄漏检测工具 +## 2.13.3.3 KMEMLEAK - 内存泄漏检测工具 ------- **2.6.31(2009年9月发布)** @@ -1340,9 +1340,7 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, 这个工具当然也存在着显著的影响系统性能的问题, 所以也只是作为调试使用. - - -## 2.13.5 KASan - 内核地址净化器 +### 2.13.3.4 KASan - 内核地址净化器 ------- **4.0(2015年4月发布)** @@ -1352,14 +1350,30 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, 4.0引入的这个工具[42](#refer-anchor-42)可以看作是 KMEMCHECK 工具的替代器, 它的目的也是为了检测诸如**释放后访问(use-after-free), 访问越界(out-of-bouds)**等非法访问问题. 它比后者更快, 因为它利用了编译器的 **Instrument** 功能, 也即编译器会在访问内存前插入探针, 执行用户指定的操作, 这通常用在性能剖析中. 在内存的使用上, KASan 也比 KMEMCHECK 有优势: 相比后者1:1的内存使用 , 它只要1:1/8. - 总的来说, 它利用了 GCC 5.0的新特性, 可对内核内存进行 Instrumentaion, 编译器可以在访问内存前插入指令, 从而检测该次访问是否合法. 对比前述的 KMEMCHECK 要用到 CPU 的陷阱指令处理和单步调试功能, KASan 是在编译时加入了探针, 因此它的性能更快. -## 2.13.6 debugfs & sysfs 接口 +### 2.13.3.5 KFENCE 一个新的内存安全检测工具 ------- -### 2.13.1 ptdump + +KFENCE 是一种低开销的基于采样的内存安全错误检测器, 用于检测堆后释放、无效释放和越界访问错误. 该系列使 KFENCE 适用于 x86 和 arm64 体系结构, 并将 KFENCE 钩子添加到 SLAB 和 SLUB 分配器. + +KFENCE 被设计为在生产内核中启用, 它的性能开销几乎为零. 与 KASAN 相比, KFENCE 以性能换取精度. KFENCE 设计背后的主要动机是, 如果总正常运行时间足够长, KFENCE 将检测非生产测试工作负载通常不会执行的代码路径中的 BUG. + +KFENCE的灵感来自于 [GWP-ASan](http://llvm.org/docs/GwpAsan.html), 这是一个具有类似属性的用户空间工具. "KFENCE" 这个名字是对 [Electric Fence Malloc Debugger](https://linux.die.net/man/3/efence) 的致敬. + + +| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | +|:----:|:----:|:---:|:----:|:---------:|:----:| +| 2020/11/03 | Marco Elver | [KFENCE: A low-overhead sampling-based memory safety error detector](https://lore.kernel.org/patchwork/cover/1331483) | 轻量级基于采样的内存安全错误检测器 | v7 ☑ 5.12-rc1 | [PatchWork v24](https://lore.kernel.org/patchwork/cover/1331483) | +| 2020/04/21 | Marco Elver | [kfence: optimize timer scheduling](https://lore.kernel.org/patchwork/cover/1416384) | ARM 支持 PTDUMP | RFC ☑ 3.19-rc1 | [PatchWork v24](https://lore.kernel.org/patchwork/cover/1416384) | + + +## 2.13.4 debugfs & sysfs 接口 +------- + +### 2.13.4.1 ptdump ------- | 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | @@ -1369,6 +1383,58 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, | 2019/12/19 | Colin Cross | [Generic page walk and ptdump](https://lore.kernel.org/patchwork/cover/1169746) | 重构了 ARM64/X86_64 的 PTDUMP, 实现了通用的 PTDUMP 框架. 目前许多体系结构都有一个 debugfs 文件用于转储内核页表. 目前, 每个体系结构都必须为此实现自定义函数, 因为内核使用的页表的遍历细节在不同的体系结构之间是不同的. 本系列扩展了walk_page_range()的功能, 使其能够处理内核的页表(内核没有vma, 可以包含比用户空间现有页面更大的页面). 通用的 PTDUMP 实现是利用walk_page_range()的新功能实现的, 最终arm64和x86将转而使用它, 删除了自定义的表行器. | v17 ☑ 5.6-rc1 |[PatchWork RFC](https://lore.kernel.org/patchwork/cover/1169746) | | 2020/2/10 | Zong Li | [RISC-V page table dumper](https://patchwork.kernel.org/project/linux-arm-kernel/patch/20131024071600.GC16735@n2100.arm.linux.org.uk/) | ARM 支持 PTDUMP | RFC ☑ 3.19-rc1 | [LKML](https://lkml.org/lkml/2020/2/10/100) | +### 2.13.4.2 Page Owner +------- + +这是之前引入的页面所有者跟踪代码. 它栖息在 Andrew 的源代码树上, 然而, 没有人试图 upstream 到 mainline. 但是已经有不少公司使用这个特性来调试内存泄漏或寻找内存占用者, 所以最终 Joonsoo Kim 在一个重构中, 将这个特性推到主线. + +这个功能帮助我们知道谁分配了页面. 当分配一个页面时, 我们将一些关于分配的信息存储在额外的内存中. 之后, 如果我们需要知道所有页面的状态, 我们可以从这些存储的信息中获取并分析它. + +虽然我们已经有了跟踪页分配/空闲的跟踪点, 使用它来分析页面所有者是相当复杂的. 我们需要扩大跟踪缓冲区, 以防止重叠, 直到用户空间程序启动. 而且, 启动的程序不断地将跟踪缓冲区转储出来供以后分析, 它将更有可能改变系统行为, 而不是仅仅将其保存在内存中, 因此不利于调试. + +此外, 我们还可以将 page_owner 特性用于各种目的. 例如, 我们可以借助 page_owner 实现的碎片统计以及一些CMA故障调试特性. + + +| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | +|:----:|:----:|:---:|:----:|:---------:|:----:| +| 2013/11/24 | Joonsoo Kim | [Resurrect and use struct page extension for some debugging features](https://lore.kernel.org/patchwork/cover/520462) | 1. 引入 page_ext 作为 page 的扩展来存储调试相关的变量
2. upstream page_owner | v7 ☑ 3.19-rc1 | [PatchWork v3](https://lore.kernel.org/patchwork/cover/520462), [Kernel Newbies](https://kernelnewbies.org/Linux_3.19#Memory_management) | + + +### 2.13.4.3 数据访问监视器 DAMON +------- + +对指定的程序进行内存相关优化, 了解业务给定工作负载的数据访问模式至关重要. 但是, 从庞大和复杂的工作量中手动提取此类模式非常详尽. 更糟糕的是, 现有的内存访问分析工具会为不必要的详细分析结果带来不可接受的高开销. + +内存管理在很大程度上基于预测 : 给定进程在不久的将来将需要哪些内存页? + +不幸的是, 事实证明, 预测是困难的, 尤其是对于未来事件的预测. 在没有从未来发送回的有用信息的情况下, 内存管理子系统被迫依赖于对最近行为的观察, 并假设该行为可能会继续. 但是, 内核的内存管理决策对于用户空间是不透明的, 并且常常导致性能不佳. SeongJae Park 实现的 [DAMON](https://lwn.net/Articles/812707) 试图使内存使用模式对用户空间可见, 并让用户空间作为响应来更改内存管理决策. + +亚马逊工程师公开了他们关于 ["DAMON"](https://github.com/awslabs/damo) 的工作, 这是一个Linux内核模块, 用于监控特定用户空间过程的数据访问. + +[DAMON](https://damonitor.github.io/_index) 允许用户监控特定用户空间过程的实际内存访问模式. 从概念上讲, 它的操作很简单; + +1. 首先, 将进程的地址空间划分为多个大小相等的区域. + +2. 然后, 它监视对每个区域的访问, 并提供对每个区域的访问次数的直方图作为其输出. + +由此, 该信息的使用者(在用户空间或内核中)可以请求更改以优化进程对内存的使用. + +它的目标是 : + +1. 足够准确, 可用于实际性能分析和优化; + +2. 足够轻量, 以便它可以在线使用; + +DAMON 利用两个核心机制 : **基于区域的采样**和**自适应区域调整**, 允许用户将跟踪开销限制在有界范围内, 而与目标工作负载的大小和复杂性无关, 同时保留结果的质量. + +1. 基于区域的抽样允许用户在监控质量和开销之间做出自己的权衡, 并限制监控开销的上限. + +2. 自适应区域调整机制使 DAMON 在保持用户配置的权衡的同时, 最大限度地提高精度, 减少开销. + +| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | +|:----:|:----:|:---:|:----:|:---------:|:----:| +| 2021/02/04 | SeongJae Park | [Introduce Data Access MONitor (DAMON)](https://damonitor.github.io) | 数据访问监视器 DAMON | v24 ☐ | [PatchWork v24](https://lore.kernel.org/patchwork/cover/1375732), [LWN](https://lwn.net/Articles/812707) | + # 2.14 杂项 ------- @@ -1394,8 +1460,6 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, 值得一提的是, 在匹配相同页面时, 一种常规的算法是对页面进行哈希, 放入哈希列表, 用哈希值来进行匹配. 最开始 KSM 确定是用这种方法, 不过 VMWare 公司拥有跟该做法很相近的算法专利, 所以后来采用了另一种算法, 用红黑树代替哈希表, 把页面内容当成一个字符串来做内容比对, 以代替哈希比对. 由于在红黑树中也是以该"字符串值"大小作为键, 因此查找两个匹配的页面速度并不慢, 因为大部分比较只要比较开始若干位即可. 关于算法细节, 感兴趣者可以参考这两篇文章:[43](#refer-anchor-43), [44](#refer-anchor-44). - - ## 2.14.2 HWPoison - 内存页错误的处理 ------- @@ -1418,7 +1482,7 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, -## 2.14.3 Cross Memory Attach - 进程间快速消息传递 +## 2.14.3 Cross Memory Attach - 进程间快速消息传递 ------- **3.2(2012年1月发布)**