From eecdef52aa8b54e4296cee47b2c28c9fa275162d Mon Sep 17 00:00:00 2001 From: gatieme Date: Sun, 5 Jul 2020 16:22:00 +0800 Subject: [PATCH] =?UTF-8?q?=E7=8E=B0=E5=9C=A8=E7=9A=84=20Linux=20=E5=86=85?= =?UTF-8?q?=E6=A0=B8=E5=92=8C=20Linux=202.6=20=E7=9A=84=E5=86=85=E6=A0=B8?= =?UTF-8?q?=E6=9C=89=E5=A4=9A=E5=A4=A7=E5=8C=BA=E5=88=AB--https://www.zhih?= =?UTF-8?q?u.com/question/35484429?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md | 232 ++++++++---------- study/kernel/00-DESCRIPTION/SCHEDULER.md | 52 ++-- 2 files changed, 134 insertions(+), 150 deletions(-) diff --git a/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md b/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md index 96b2627..48b4c77 100755 --- a/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md +++ b/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md @@ -107,7 +107,7 @@ -## 2.1 内存分配 +# 2.1 内存分配 ------- ## 2.1.1 页分配器: 伙伴分配器[12](#ref-anchor-12) @@ -135,7 +135,7 @@ **关于NUMA 支持:** Linux 内核中, 每个 zone 都有上述的链表数组, 从而提供精确到某个 node 的某个 zone 的伙伴分配需求. -## 2.2 对象分配器: 内核级别的 malloc 分配器** +## 2.1.2 对象分配器: 内核级别的 malloc 分配器** ------- @@ -143,7 +143,7 @@ -### 2.2.1 SLAB, 2.0 版本时代(1996年引入)** +### 2.1.2.1 SLAB, 2.0 版本时代(1996年引入)** ------- @@ -152,7 +152,7 @@ -### 2.2.2 SLUB, 2.6.22(2007年7月发布)** +### 2.1.2.2 SLUB, 2.6.22(2007年7月发布)** ------- @@ -168,7 +168,7 @@ SLUB 在解决了上述的问题之上, 提供与 SLAB 完全一样的接口, -### 2.2.3 SLOB, 2.6.16(2006年3月发布)** +### 2.1.2.3 SLOB, 2.6.16(2006年3月发布)** ------- @@ -178,7 +178,7 @@ SLUB 在解决了上述的问题之上, 提供与 SLAB 完全一样的接口, -## 2.3 连续内存分配器(CMA), 3.5(2012年7月发布)** +### 2.1.2.4 连续内存分配器(CMA), 3.5(2012年7月发布)** ------- @@ -198,23 +198,23 @@ CMA 的做法也是启动时预留, 但不同的是, 它允许这部分内存被 -**2 内存去碎片化** - +# 2.2 内存去碎片化** +------- 前面讲了运行较长时间的系统存在的内存碎片化问题, Linux 内核也不能幸免, 因此有开发者陆续提出若干种方法. -**2.1 成块回收(Lumpy Reclaim) 2.6.23引入(2007年7月), 3.5移除(2012年7月)** - +## 2.2.1 成块回收(Lumpy Reclaim) 2.6.23引入(2007年7月), 3.5移除(2012年7月)** +------- 这不是一个完整的解决方案, 它只是缓解这一问题. 所谓回收是指 MM 在分配内存遇到内存紧张时, 会把一部分内存页面回收. 而成块回收[14](#refer-anchor-14), 就是尝试成块回收目标回收页相邻的页面, 以形成一块满足需求的高阶连续页块. 这种方法有其局限性, 就是成块回收时没有考虑被连带回收的页面可能是“热页”, 即被高强度使用的页, 这对系统性能是损伤. -**2.2 基于页面可移动性的页面聚类(Page Clustering by Page Mobility) 2.6.23(2007年7月发布)** - +## 2.2.2 基于页面可移动性的页面聚类(Page Clustering by Page Mobility) 2.6.23(2007年7月发布)** +------- 这个名字是我造的, 有点拗口. 所谓可移动性, 是基于对下列事实的思考: 在去碎片化时, 需要移动或回收页面, 以腾出连续的物理页面, 但可能一颗“老鼠屎就坏了整锅粥”——由于某个页面无法移动或回收, 导致整个区域无法组成一个足够大的连续页面块. 这种页面通常是内核使用的页面, 因为内核使用的页面的地址是直接映射(即物理地址加个偏移就映射到内核空间中), 这种做法不用经过页表翻译, 提高了效率, 却也在此时成了拦路虎. @@ -242,8 +242,8 @@ Mel Gorman观察到, 所有使用的内存页有三种情形: -**2.3 内存紧致化(Memory Compaction) 2.6.35(2010年8月发布)** - +## 2.2.3 内存紧致化(Memory Compaction) 2.6.35(2010年8月发布)** +------- @@ -262,12 +262,12 @@ Mel Gorman观察到, 所有使用的内存页有三种情形: -**3 页表管理** +# 2.3 页表管理** +------- - -**3.1 四级页表 2.6.11(2005年3月发布)** - +## 2.3.1 四级页表 2.6.11(2005年3月发布)** +------- 页表实质上是一个虚拟地址到物理地址的映射表, 但由于程序的局部性, 某时刻程序的某一部分才需要被映射, 换句话说, 这个映射表是相当稀疏的, 因此在内存中维护一个一维的映射表太浪费空间, 也不现实. 因此, 硬件层次支持的页表就是一个多层次的映射表. @@ -288,8 +288,8 @@ Linux 一开始是在一台i386上的机器开发的, i386 的硬件页表是2 -**3.2 延迟页表缓存冲刷 (Lazy-TLB flushing), 极早引入, 时间难考** - +## 2.3.2 延迟页表缓存冲刷 (Lazy-TLB flushing), 极早引入, 时间难考** +------- @@ -298,8 +298,8 @@ Linux 一开始是在一台i386上的机器开发的, i386 的硬件页表是2 -**4\. 页面回收** - +# 2.4 **页面回收** +------- 从用户角度来看, 这一节对了解 Linux 内核发展帮助不大, 可跳过不读; 但对于技术人员来说, 本节可以展现教材理论模型到工程实现的一些思考与折衷, 还有软件工程实践中由简单粗糙到复杂精细的演变过程 @@ -310,8 +310,8 @@ Linux 一开始是在一台i386上的机器开发的, i386 的硬件页表是2 -**4.1 增强的LRU算法 (2.6前引入, 具体时间难考)** - +## 2.4.1 增强的LRU算法 (2.6前引入, 具体时间难考)** +------- 教科书式的 PFRA 会提到要用 LRU (Least-Recently-Used) 算法, 该算法思想基于: 最近很少使用的页, 在紧接着的未来应该也很少使用, 因此, 它可以被当作替换掉的候选页. @@ -342,8 +342,8 @@ active 头(热烈使用中) > active 尾 > inactive 头 > inactive 尾(被驱逐 -**4.2 active 与 inactive 链表拆分, 2.6.28(2008年12月)** - +## 2.4.2 active 与 inactive 链表拆分, 2.6.28(2008年12月)** +------- 4.1 中描述过一个用户可配置的接口 : **_swappiness_**. 这是一个百分比数(取值 0 -100, 默认60), 当值越靠近100, 表示更倾向替换匿名页; 当值越靠近0, 表示更倾向替换文件缓存页. 这在不同的工作负载下允许管理员手动配置. @@ -357,8 +357,8 @@ active 头(热烈使用中) > active 尾 > inactive 头 > inactive 尾(被驱逐 解决方法就是把链表拆分为匿名页链表和文件缓存页链表[18](#refer-anchor-18),[19](#refer-anchor-19), 现在 active 链表分为 active 匿名页链表 和 active 文件缓存页链表了;inactive 链表也如此. 所以, 当 **_swappiness_** 为0时, 只要扫描 active 文件缓存页链表就够了. -**4.3 再拆分出被锁页的链表, 2.6.28(2008年12月)** - +## 2.4.3 再拆分出被锁页的链表, 2.6.28(2008年12月)** +------- 虽然现在拆分出4个链表了, 但还有一个问题, 有些页被**"钉"**在内存里(比如实时算法, 或出于安全考虑, 不想含有敏感信息的内存页被交换出去等原因, 用户通过 **_mlock()_**等系统调用把内存页锁住在内存里). 当这些页很多时, 扫描这些页同样是徒劳的. @@ -369,8 +369,8 @@ active 头(热烈使用中) > active 尾 > inactive 头 > inactive 尾(被驱逐 -**4.4 让代码文件缓存页多待一会, 2.6.31(2009年9月发布)** - +## 2.4.4 让代码文件缓存页多待一会, 2.6.31(2009年9月发布)** +------- 试想, 当你在拷贝一个非常大的文件时, 你发现突然电脑变得反应慢了, 那么可能发生的事情是: @@ -382,8 +382,8 @@ active 头(热烈使用中) > active 尾 > inactive 头 > inactive 尾(被驱逐 解决方法是在扫描这些在使用中的代码文件缓存页时, 跳过它, 让它有多一点时间待在 active 链表上, 从而避免上述问题. [22](#refer-anchor-22), [23](#refer-anchor-23) -**4.5 工作集大小的探测, 3.15(2014年6月发布)** - +## 2.4.5 工作集大小的探测, 3.15(2014年6月发布)** +------- 一个文件缓存页(代码)一开始进入 inactive 链表表头, 如果它没被再次访问, 它将被慢慢推到 inactive 链表表尾, 最后在回收时被回收走; 而如果有再次访问, 它会被提升到 active 链表尾, 再再次访问, 提升到 active 链表头. 因此, 可以定义一个概念: **访问距离, 它指该页面第一次进入内存到被踢出的间隔, 显然至少是 inactive 链表的长度.** @@ -406,11 +406,11 @@ active 头(热烈使用中) > active 尾 > inactive 头 > inactive 尾(被驱逐 -**5 页面写回** +# 2.5 **页面写回** +------- - -/* 从用户角度来看, 这一节对了解 Linux 内核发展帮助不大, 可跳过不读; 但对于技术人员来说, 本节可以展现教材理论模型到工程实现的一些思考与折衷, 还有软件工程实践中由简单粗糙到复杂精细的演变过程 */ +从用户角度来看, 这一节对了解 Linux 内核发展帮助不大, 可跳过不读; 但对于技术人员来说, 本节可以展现教材理论模型到工程实现的一些思考与折衷, 还有软件工程实践中由简单粗糙到复杂精细的演变过程 @@ -422,8 +422,8 @@ active 头(热烈使用中) > active 尾 > inactive 头 > inactive 尾(被驱逐 -**5.1 由全局的脏页门槛到每设备脏页门槛 2.6.24(2008年1月发布)** - +## 2.5.1 由全局的脏页门槛到每设备脏页门槛 2.6.24(2008年1月发布)** +------- 内核采取的第2个手段看起来很高明 - 扼制生成脏页的进程, 使其停止生成, 反而开始写回脏页, 这一进一退中, 就能把全局的脏页数量拉低到全局门槛下. 但是, 这存在几个微妙的问题: @@ -445,8 +445,8 @@ active 头(热烈使用中) > active 尾 > inactive 头 > inactive 尾(被驱逐 -**5.2 引入更具体扩展性的回写线程 2.6.32(2009年12月发布)** - +## 2.5.2 引入更具体扩展性的回写线程 2.6.32(2009年12月发布)** +------- Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 _sync_ 命令时, 会启用后台线程来写回脏页, 线程的数量会根据写回的工作量在2个到8个之间调整. 这些写回线程是面向脏页的, 而不是面向后备设备的. 换句话说, 每个回写线程都是在认领系统全局范围内的脏页来写回, 而这些脏页是可能属于不同后备设备的, 所以回写线程不是专注于某一个设备. @@ -466,8 +466,8 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 -**5.3 动态的脏页生成扼制和写回扼制算法 3.1(2011年11月发布), 3.2(2012年1月发布)** - +## 2.5.3 动态的脏页生成扼制和写回扼制算法 3.1(2011年11月发布), 3.2(2012年1月发布)** +------- 本节一开始说的写回扼制算法, 其核心就是**谁污染谁治理: 生成脏页多的进程会被惩罚, 让其停止生产, 责成其进行义务劳动, 把系统脏页写回.** 在5.1小节里, 已经解决了这个方案的针对于后备设备门槛的一些问题. 但还存在一些别的问题. @@ -492,13 +492,13 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 > **2\.** 平缓地修改扼制的门槛. 之前进程被罚的门槛会随着一个重量级进程的启动而走人骤降, 在吴峰光的算法中, 增加了对全局内存压力的评估, 从而平滑地修改这一门槛. > **3\.** 在进程生成脏页的扼制方面, 吴峰光同样采取反馈调节的做法, 针对写回工作量和写回速度, 平缓地(尽量)把系统的脏页生成控制在定点附近. -**6 页面预读** +## 2.5.6 **页面预读** +------- - -/* 从用户角度来看, 这一节对了解 Linux 内核发展帮助不大, 可跳过不读; 但对于技术人员来说, 本节可以展现教材理论模型到工程实现的一些思考与折衷, 还有软件工程实践中由简单粗糙到复杂精细的演变过程 */ +从用户角度来看, 这一节对了解 Linux 内核发展帮助不大, 可跳过不读; 但对于技术人员来说, 本节可以展现教材理论模型到工程实现的一些思考与折衷, 还有软件工程实践中由简单粗糙到复杂精细的演变过程 @@ -508,16 +508,16 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 -**6.1 原始的预读方案 (时间很早, 未可考)** - +### 2.5.6.1 原始的预读方案 (时间很早, 未可考)** +------- 一开始, 内核的预读方案如你所想, 很简单. 就是在内核发觉可能在做顺序读操作时, 就把后面的 128 KB 的页面也读进来. -**6.2 按需预读(On-demand Readahead) 2.6.23(2007年10月发布)** - +### 2.5.6.2 **按需预读(On-demand Readahead) 2.6.23(2007年10月发布)** +------- @@ -534,8 +534,8 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 -**7 大内存页支持** - +# 2.7 **大内存页支持** +------- 我们知道现代操作系统都是以页面(page)的方式管理内存的. 一开始, 页面的大小就是4K, 在那个时代, 这是一个相当大的数目了. 所以众多操作系统, 包括 Linux , 深深植根其中的就是一个页面是4K大小这种认知, 尽管现代的CPU已经支持更大尺寸的页面(X86体系能支持2MB, 1GB). @@ -550,8 +550,8 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 -**7.1 HUGETLB支持 (2.6前引入)** - +## 2.7.1 HUGETLB支持 (2.6前引入)** +------- @@ -570,10 +570,8 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 这一功能的主要使用者是数据库程序. - - -**7.2 透明大页的支持 2.6.38(2011年3月发布)** - +## 2.7.2 透明大页的支持 2.6.38(2011年3月发布)** +------- @@ -590,8 +588,8 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 -**8 内存控制组(Memory Cgroup)支持 2.6.25(2008年4月发布)** - +# 2.8 内存控制组(Memory Cgroup)支持 2.6.25(2008年4月发布)** +------- 在Linux轻量级虚拟化的实现 container 中(比如现在挺火的Docker, 就是基于container), 一个重要的功能就是做资源隔离. Linux 在 2.6.24中引入了cgroup(control group, 控制组)的资源隔离基础框架(将在最后一个部分详述), 提供了资源隔离的基础. @@ -602,8 +600,8 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 -**9 内存热插拔支持** - +# 2.9 ** 内存热插拔支持** +------- @@ -633,8 +631,8 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 -**9.1 内存热插入支持 2.6.15(2006年1月发布)** - +## 2.9.1 **内存热插入支持 2.6.15(2006年1月发布)** +------- 这提供了最基本的内存热插入支持(包括物理/逻辑阶段). 注意, **此时热拔除还不支持.** @@ -643,8 +641,8 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 -**9.2 初步的内存逻辑热拔除支持 2.6.24(2008年1月发布)** - +## 2.9.2 初步的内存逻辑热拔除支持 2.6.24(2008年1月发布)** +------- @@ -659,8 +657,8 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 -**9.3 完善的内存逻辑热拔除支持 3.8(2013年2月发布)** - +## 2.9.3 完善的内存逻辑热拔除支持 3.8(2013年2月发布)** +------- @@ -671,8 +669,8 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 -**9.4 物理热拔除的支持 3.9(2013年4月支持)** - +## 2.9.4 物理热拔除的支持 3.9(2013年4月支持)** +------- @@ -685,8 +683,8 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 -**10 超然内存(Transcendent Memory)支持** - +# 2.10 超然内存(Transcendent Memory)支持** +------- @@ -734,8 +732,8 @@ Linux 内核 从 3.X 系列开始陆续加入 tmem 相关的基础设施支持, -**10.1 前端接口之 CLEANCACHE 3.0(2011年7月发布)** - +## **2.10.1 前端接口之 CLEANCACHE 3.0(2011年7月发布)** +------- 前面章节提到过, 内核会利用空闲内存缓存后备外设中的内容, 以期在近期将要使用时不用从缓慢的外设读取. 这些内容叫文件缓存页. 它的特点就是只要是页是干净的(没有被写过), 那么在系统需要内存时, 随时可以直接丢弃这些页面以腾出空间(因为它随时可以从后备文件系统中读取). @@ -746,8 +744,8 @@ Linux 内核 从 3.X 系列开始陆续加入 tmem 相关的基础设施支持, -**10.2 前端接口之 FRONTSWAP 3.5(2012年7月发布)** - +## **2.10.2 前端接口之 FRONTSWAP 3.5(2012年7月发布)** +------- 除了文件缓存页, 另一大类内存页面就是匿名页. 在系统内存紧张时, 内核必须要把这些页面写出到外设的交换设备或交换分区中, 而不能简单丢弃(因为这些页面没有后备文件系统). 同样, 涉及到读写外设, 又有性能考量了, 此时又是 tmem 起作用的时候了. @@ -758,16 +756,16 @@ Linux 内核 从 3.X 系列开始陆续加入 tmem 相关的基础设施支持, -**10.3 后端之 ZCACHE (没能进入内核主线)** - +## 2.10.3 后端之 ZCACHE (没能进入内核主线)** +------- 讲完了两个前端接口, 接下来说后端的管理策略. 对应于 CLEANCACHE, 一开始是有一个专门的后端叫 zcache[34](#refer-anchor-34), **不过最后被删除了.** 它的做法就是把这些被内核逐出的文件缓存页压缩, 并存放在内存中. 所以, zcache 相当于把内存页从内存中的一个地方移到另一个地方, 这乍一看, 感觉很奇怪, 但这正是 tmem 的灵活性所在. 它允许后端有不同的管理策略, 比如在这个情况下, 它把内存页压缩后仍然放在内存中, 这提高了内存的使用. 当然, 毕竟 zcache 会占用一部分物理内存, 导致可用的内存减小. 因此, 这需要有一个权衡. 高效(压缩比, 压缩时间)的压缩算法的使用, 从而使更多的文件页待在内存中, 使得其带来的避免磁盘读写的优势大于减少的这部分内存的代价. 不过, 也因为如此, 它的实现过于复杂, **以至最终没能进入内核主线.** 开发者在开始重新实现一个新的替代品, 不过截止至 4.2 , 还没有看到成果. -**10.4后端之 ZRAM 3.14(2014年3月发布)** - +## 2.10.4后端之 ZRAM 3.14(2014年3月发布)** +------- @@ -780,16 +778,16 @@ ZRAM 是一个在内存中的块设备(块设备相对于字符设备而言, 信 -**10.5 后端之 ZSWAP 3.11(2013年9月发布)** - +## 2.10.5 后端之 ZSWAP 3.11(2013年9月发布)** +------- FRONTSWAP 对应的另一个后端叫 ZSWAP[35](#refer-anchor-35). ZSWAP 的做法其实也是尝试把内核交换出去的页面压缩存放到一个内存池子中. 当然, ZSWAP 空间也是有限的. 但同 ZRAM 不同的是, ZSWAP 会智能地把其中一些它认为近期不会使用的页面解压缩, 写回到真正的磁盘外设中. 因此, 大部分情况下, 它能避免磁盘写操作, 这比 ZRAM 不知高明到哪去了. -**10.6 一些细节** - +## 2.10.6 一些细节** +------- 这一章基本说完了, 但牵涉到后端, 其实还有一些细节可以谈, 比如对于压缩的效率的考量, 会影响到后端实现的选择, 比如不同的内存页面的压缩效果不同(全0页和某种压缩文件占据的内存页的压缩效果显然差距很大)对压缩算法的选择; 压缩后页面的存放策略也很重要, 因为以上后端都存在特殊情况要把页面解压缩写回到磁盘外设, 写回页面的选择与页面的存放策略关系很大. 但从用户角度讲, 以上内容足以, 就不多写了. @@ -804,8 +802,8 @@ FRONTSWAP 对应的另一个后端叫 ZSWAP[35](#refer-anchor-35). ZS -**11 非易失性内存 (NVDIMM, Non-Volatile DIMM) 支持** - +# 2.11 非易失性内存 (NVDIMM, Non-Volatile DIMM) 支持** +------- @@ -816,7 +814,6 @@ FRONTSWAP 对应的另一个后端叫 ZSWAP[35](#refer-anchor-35). ZS ![](https://pic4.zhimg.com/50/0c0850cde43c84764e65bc24942bc6d3_hd.jpg) -![](data:image/svg+xml;utf8,) @@ -832,8 +829,8 @@ FRONTSWAP 对应的另一个后端叫 ZSWAP[35](#refer-anchor-35). ZS -**11.1 NVDIMM 支持框架: libnvdimm 4.2(2015年8月30日发布)** - +## 2.11.1 NVDIMM 支持框架: libnvdimm 4.2(2015年8月30日发布)** +------- 2015年4月发布的ACPI 6.0规范[39](#refer-anchor-39), 定义了NVDIMM Firmware Interface Table (NFIT), 详细地规定了 NVDIMM 的访问模式, 接口数据规范等细节. 在 Linux 4.2 中, 内核开始支持一个叫 libnvdimm 的子系统, 它实现了 NFIT 的语义, 提供了对 NVDIMM 两种基本访问模式的支持, 一种即内核所称之的 PMEM 模式, 即把 NVDIMM 设备当作持久性的内存来访问; 另一种则提供了块设备模式的访问. 开始奠定 Linux 内核对这一新兴技术的支持. @@ -842,8 +839,8 @@ FRONTSWAP 对应的另一个后端叫 ZSWAP[35](#refer-anchor-35). ZS -**11.2 DAX 4.0(2015年4月发布)** - +## 2.11.2 DAX 4.0(2015年4月发布)** +------- @@ -856,8 +853,8 @@ FRONTSWAP 对应的另一个后端叫 ZSWAP[35](#refer-anchor-35). ZS -**12 内存管理调试支持** - +# 2.12 内存管理调试支持** +------- @@ -866,8 +863,8 @@ FRONTSWAP 对应的另一个后端叫 ZSWAP[35](#refer-anchor-35). ZS -**12.1 页分配的调试支持 2.5(2003年7月之后发布)** - +## 2.12.1 页分配的调试支持 2.5(2003年7月之后发布)** +------- 前面提到过, 内核自己用的内存, 由于是绕过寻常的逐级页表机制, 采用直接映射(提高了效率), 即虚拟地址与页面实际的物理地址存在着一一线性映射的关系. 另一方面, 内核使用的内存又是出于各种重要管理目的, 比如驱动, 模块, 文件系统, 甚至 SLAB 子系统也是构建于页分配器之上. 以上二个事实意味着, 相邻的页, 可能被用于完全不同的目的, 而这两个页由于是直接映射, 它们的虚拟地址也是连续的. 如果某个使用者子系统的编码有 bug, 那么它的对其内存页的写操作造成对相邻页的覆写可能性相当大, 又或者不小心读了一个相邻页的数据. 这些操作可能不一定马上引起问题, 而是在之后的某个地方才触发, 导致数据损坏乃至系统崩溃. @@ -888,8 +885,8 @@ FRONTSWAP 对应的另一个后端叫 ZSWAP[35](#refer-anchor-35). ZS -**12.2 SLAB 子系统的调试支持** - +## 2.12.2 SLAB 子系统的调试支持** +------- SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, 包括有: @@ -906,16 +903,16 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, -**12.3 错误注入机制 2.6.20(2007年2月发布)** - +## 2.12.3 错误注入机制 2.6.20(2007年2月发布)** +------- 内核有着极强的健壮性, 能对各种错误异常情况进行合理的处理. 然而, 毕竟有些错误实在是极小可能发生, 测试对这种小概率异常情况的处理的代码实在是不方便. 所以, 2.6.20中内核引入了错误注入机制, 其中跟 MM 相关的有两个, 一个是对页分配器的失败注入, 一个是对 SLAB 对象分配器的失败注入. 这两个注入机制, 可以触发内存分配失败, 以测试极端情况下(如内存不足)系统的处理情况. -**12.4 KMEMCHECK - 内存非法访问检测工具 2.6.31(2009年9月发布)** - +## 2.12.4 KMEMCHECK - 内存非法访问检测工具 2.6.31(2009年9月发布)** +------- 对内存的非法访问, 如访问未分配的内存, 或访问分配了但未初始化的内存, 或访问了已释放了的内存, 会引起很多让人头痛的问题, 比如程序因数据损坏而在某个地方莫名崩溃, 排查非常困难. 在用户态内存检测工具 valgrind 中, 有一个 Memcheck 插件可以检测此类问题. 2.6.31, Linux 内核也引进了内核态的对应工具, 叫 KMEMCHECK. @@ -940,8 +937,8 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, -**12.4 KMEMLEAK - 内存泄漏检测工具 2.6.31(2009年9月发布)** - +## 2.12.4 KMEMLEAK - 内存泄漏检测工具 2.6.31(2009年9月发布)** +------- 内存漏洞一直是 C 语言用户面临的一个问题, 内核开发也不例外. 2.6.31 中, 内核引入了 KMEMLEAK 工具[41](#refer-anchor-41), 用以检测内存泄漏. 它采用了标记-清除的垃圾收集算法, 对通过 SLAB 子系统分配的对象, 或通过 _vmalloc_ 接口分配的连续虚拟地址的对象, 或分配的per-CPU对象(per-CPU对象是指每个 CPU 有一份拷贝的全局对象, 每个 CPU 访问修改本地拷贝, 以提高性能)进行追踪, 把指向对象起始的指针, 对象大小, 分配时的栈踪迹(stack trace) 保存在一个红黑树里(便于之后的查找, 同时还会把对象加入一个全局链表中). 之后, KMEMLEAK 会启动一个每10分钟运行一次的内核线程, 或在用户的指令下, 对整个内存进行扫描. 如果某个对象**从其起始地址到终末地址**内没有别的指针指向它, 那么该对象就被当成是泄漏了. KMEMLEAK 会把相关信息报告给用户. @@ -966,8 +963,8 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, -**12.5 KASan - 内核地址净化器 4.0(2015年4月发布)** - +## 2.12.5 KASan - 内核地址净化器 4.0(2015年4月发布)** +------- @@ -978,16 +975,16 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, 总的来说, 它利用了 GCC 5.0的新特性, 可对内核内存进行 Instrumentaion, 编译器可以在访问内存前插入指令, 从而检测该次访问是否合法. 对比前述的 KMEMCHECK 要用到 CPU 的陷阱指令处理和单步调试功能, KASan 是在编译时加入了探针, 因此它的性能更快. -**13 杂项** - +## 2.13 杂项** +------- 这是最后一章, 讲几个 Linux 内存管理方面的属于锦上添花性质的功能, 它们使 Linux 成为一个更强大, 更好用的操作系统. -**13.1 KSM - 内存去重 2.6.32(2009年12月发布)** - +### 2.13.1 KSM - 内存去重 2.6.32(2009年12月发布)** +------- 现代操作系统已经使用了不少共享内存的技术, 比如共享库, 创建新进程时子进程共享父进程地址空间. 而 KSM(Kernel SamePage Merging, 内存同页合并, 又称内存去重), 可以看作是存储领域去重(de-duplication)技术在内存使用上的延伸, 它是为了解决服务器虚拟化领域的内存去重方案. 想像在一个数据中心, 一台物理服务器上可能同时跑着多个虚拟客户机(Guest OS), 并且这些虚拟机运行着很多相同的程序, 如果在物理内存上, 这些程序文本(text)只有一份拷贝, 将会节省相当可观的内存. 而客户机间是相对独立的, 缺乏相互的认知, 所以 KSM 运作在监管机(hypervisor)上. @@ -1002,8 +999,8 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, -**13.2 HWPoison - 内存页错误的处理 2.6.32(2009年12月发布)** - +## 2.13.2 HWPoison - 内存页错误的处理 2.6.32(2009年12月发布)** +------- @@ -1024,8 +1021,8 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, -**13.3 Cross Memory Attach - 进程间快速消息传递 3.2(2012年1月发布)** - +## 2.13.3 Cross Memory Attach - 进程间快速消息传递 3.2(2012年1月发布)** +------- 这一节相对于其他本章内容是独立的. MPI(Message Passing Interface, 消息传递接口) [46](#refer-anchor-46) 是一个定义并行编程模型下用于进程间消息传递的一个高性能, 可扩展, 可移植的接口规范(注意这只是一个标准, 有多个实现). 之前的 MPI 程序在进程间共享信息是用到共享内存(shared memory)方式, 进程间的消息传递需要 2 次内存拷贝. 而 3.2 版本引入的 "Cross Memory Attach" 的 patch, 引入两个新的系统调用接口. 借用这两个接口, MPI 程序可以只使用一次拷贝, 从而提升性能. @@ -1036,19 +1033,6 @@ SLAB 作为一个相对独立的子模块, 一直有自己完善的调试支持, -**\========== 内存管理子系统 结束分割线 ==========** - - - -* **中断与异常子系统(interrupt & exception)** -* **时间子系统(timer & timekeeping)** -* **同步机制子系统(synchronization)** -* **块层(block layer)** -* **文件子系统(Linux 通用文件系统层 VFS, various fs)** -* **网络子系统(networking)** -* **调试和追踪子系统(debugging, tracing)** -* **虚拟化子系统(kvm)** -* **控制组(cgroup)** --- diff --git a/study/kernel/00-DESCRIPTION/SCHEDULER.md b/study/kernel/00-DESCRIPTION/SCHEDULER.md index 6e4df8a..cfa1d8b 100755 --- a/study/kernel/00-DESCRIPTION/SCHEDULER.md +++ b/study/kernel/00-DESCRIPTION/SCHEDULER.md @@ -71,7 +71,7 @@ Linux 除了实现上述策略, 还额外支持以下策略: **-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- 正文 -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-** -## 1.1 抢占支持(preemption) +# 1.1 抢占支持(preemption) ------- **2.6 时代开始支持** (首次在2.5.4版本引入[37](#refer-anchor-37), 感谢知友 [@costa](https://www.zhihu.com/people/78ceb98e7947731dc06063f682cf9640) 考证! 关于 Linux 版本规则, 可看我文章[4](#refer-anchor-4). @@ -80,17 +80,17 @@ Linux 除了实现上述策略, 还额外支持以下策略: 可抢占性, 对一个系统的调度延时具有重要意义. 2.6 之前, 一个进程进入内核态后, 别的进程无法抢占, 只能等其完成或退出内核态时才能抢占, 这带来严重的延时问题, 2.6 开始支持内核态抢占. -## 1.2 进程调度类 +# 1.2 进程调度类 ------- -### 1.2.1 普通进程调度器(SCHED\_OTHER)之纠极进化史 +## 1.2.1 普通进程调度器(SCHED\_OTHER)之纠极进化史 ------- Linux 一开始, 普通进程和实时进程都是基于优先级的一个调度器, 实时进程支持 100 个优先级, 普通进程是优先级小于实时进程的一个静态优先级, 所有普通进程创建时都是默认此优先级, 但可通过 **nice()** 接口调整动态优先级(共40个). 实时进程的调度器比较简单, 而普通进程的调度器, 则历经变迁[5](#refer-anchor-5): -#### 1.2.1.1 O(1) 调度器: +## 1.2.1.1 O(1) 调度器: ------- 2.6 时代开始支持(2002年引入). @@ -98,7 +98,7 @@ Linux 一开始, 普通进程和实时进程都是基于优先级的一个调度 顾名思义, 此调度器为O(1)时间复杂度. 该调度器修正之前的O(n) 时间复杂度调度器, 以解决扩展性问题. 为每一个动态优先级维护队列, 从而能在常数时间内选举下一个进程来执行. -#### 1.2.1.2 夭折的 RSDL(The Rotating Staircase Deadline Scheduler)调度器 +## 1.2.1.2 夭折的 RSDL(The Rotating Staircase Deadline Scheduler)调度器 ------- **2007 年 4 月提出, 预期进入 2.6.22, 后夭折.** @@ -112,7 +112,7 @@ Con Kolivas (八卦: 这家伙白天是个麻醉医生)为解决这个问题提 -#### 1.2.1.3 完全公平的调度器(CFS) +## 1.2.1.3 完全公平的调度器(CFS) ------- **2.6.23(2007年10月发布)** @@ -129,7 +129,7 @@ Con Kolivas 的完全公平的想法启发了原 O(1) 调度器作者 Ingo Molna CFS 的测试性能比 RSDS 好, 并得到更多的开发者支持, 所以它最终替代了 RSDL 在 2.6.23 进入内核, 一直使用到现在. 可以八卦的是, Con Kolivas 因此离开了社区, 不过他本人否认是因为此事而心生龃龉. 后来, 2009 年, 他对越来越庞杂的 CFS 不满意, 认为 CFS 过分注重对大规模机器, 而大部分人都是使用少 CPU 的小机器, 开发了 BFS 调度器[48](#refer-anchor-48), 这个在 Android 中有使用, 没进入 Linux 内核. -#### 1.2.1.4 不那么重要的进程 SCHED\_IDLE +## 1.2.1.4 不那么重要的进程 SCHED\_IDLE ------- **2.6.23(2007年10月发布)** @@ -166,7 +166,7 @@ SCHED_IDLE 跟 SCHED_BATCH 一样, 是 CFS 中的一个策略, SCHED\_IDLE 的 -#### 1.2.1.5 吭哧吭哧跑计算 SCHED\_BATCH +## 1.2.1.5 吭哧吭哧跑计算 SCHED\_BATCH ------- **2.6.16(2006年3月发布)** @@ -180,7 +180,7 @@ SCHED_IDLE 跟 SCHED_BATCH 一样, 是 CFS 中的一个策略, SCHED\_IDLE 的 -### 1.2.2 十万火急, 限期完成 SCHED\_DEADLINE +## 1.2.2 十万火急, 限期完成 SCHED\_DEADLINE ------- **3.14(2014年3月发布)** @@ -195,12 +195,12 @@ SCHED_IDLE 跟 SCHED_BATCH 一样, 是 CFS 中的一个策略, SCHED\_IDLE 的 更多可参看此文章: [Deadline scheduling: coming soon? [LWN.net]](https://link.zhihu.com/?target=https%3A//lwn.net/Articles/575497/) -### 1.2.3 SCHED\_RT +## 1.2.3 SCHED\_RT ------- -### 1.2.4 其他一些调度类的尝试 +## 1.2.4 其他一些调度类的尝试 ------- @@ -222,10 +222,10 @@ SCHED_IDLE 跟 SCHED_BATCH 一样, 是 CFS 中的一个策略, SCHED\_IDLE 的 [sched: Add micro quanta scheduling class](https://lkml.org/lkml/2019/9/6/178) 在 RT 之后, CFS 之前实现了一个类似于 RT 的策略, 为在线任务提供服务, 来解决同样的问题. -## 1.3 组调度支持(Group Scheduling) +# 1.3 组调度支持(Group Scheduling) ------- -### 1.3.1 普通进程的组调度支持(Fair Group Scheduling) +## 1.3.1 普通进程的组调度支持(Fair Group Scheduling) ------- **2.6.24(2008年1月发布)** @@ -240,7 +240,7 @@ SCHED_IDLE 跟 SCHED_BATCH 一样, 是 CFS 中的一个策略, SCHED\_IDLE 的 该功能是基于控制组(control group, cgroup)的概念, 需要内核开启 CGROUP 的支持才可使用. 关于 CGROUP , 以后可能会写. -### 1.3.2 实时进程的组调度支持(RT Group Scheduling) +## 1.3.2 实时进程的组调度支持(RT Group Scheduling) ------- @@ -249,7 +249,7 @@ SCHED_IDLE 跟 SCHED_BATCH 一样, 是 CFS 中的一个策略, SCHED\_IDLE 的 该功能同普通进程的组调度功能一样, 只不过是针对实时进程的. -### 1.3.3 组调度带宽控制(CFS bandwidth control)** , **3.2(2012年1月发布)** +## 1.3.3 组调度带宽控制(CFS bandwidth control)** , **3.2(2012年1月发布)** ------- @@ -257,7 +257,7 @@ SCHED_IDLE 跟 SCHED_BATCH 一样, 是 CFS 中的一个策略, SCHED\_IDLE 的 -### 1.3.4 极大提高体验的自动组调度(Auto Group Scheduling) +## 1.3.4 极大提高体验的自动组调度(Auto Group Scheduling) ------- **2.6.38(2011年3月发布)** @@ -281,17 +281,17 @@ SCHED_IDLE 跟 SCHED_BATCH 一样, 是 CFS 中的一个策略, SCHED\_IDLE 的 该功能可以手动关闭. -## 1.4 负载跟踪机制 +# 1.4 负载跟踪机制 ------- -### 1.4.1 PELT +## 1.4.1 PELT ------- -### 1.4.2 WALT +## 1.4.2 WALT ------- -## 1.5 select_task_rq +# 1.5 select_task_rq ------- 调度器最关键的任务就是两个: @@ -309,16 +309,16 @@ Linux 下各个调度类都实现了这两个接口, 各个调度类按照既定 内核中主体的进程都是以 SCHED_NORMAL 为策略的普通 CFS 进程, 以 select_task_rq_fair 为例, 其代码就经过了不断的重构和优化. -### 1.5.1 机制的 WAKE_AFFINE +## 1.5.1 机制的 WAKE_AFFINE ------- -### 1.5.2 限制遍历的 CPU 数目 +## 1.5.2 限制遍历的 CPU 数目 ------- -## 1.6 基于调度域的负载均衡 +# 1.6 基于调度域的负载均衡 ------- @@ -357,7 +357,7 @@ Linux 下各个调度类都实现了这两个接口, 各个调度类按照既定 -## 1.7 更精确的调度时钟(HRTICK), 2.6.25(2008年4月发布)** +# 1.7 更精确的调度时钟(HRTICK), 2.6.25(2008年4月发布)** ------- @@ -373,7 +373,7 @@ CPU的周期性调度, 和基于时间片的调度, 是要基于时钟中断来 -## 1.8 自动 NUMA 均衡(Automatic NUMA balancing) +# 1.8 自动 NUMA 均衡(Automatic NUMA balancing) ------- **3.8(2013年2月发布)** @@ -404,7 +404,7 @@ NUMA 机器一个重要特性就是不同 node 之间的内存访问速度有差 -## 1.9 **CPU 调度与节能** +# 1.9 **CPU 调度与节能** -------