讲解复杂繁琐的机制原理, 最通俗的方法就是用模型架构的方式向读者呈现, 先要在整体上了解大方向大架构, 再根据大方向大架构来进行分支深入, 犹如毛主席那句话 "战略上蔑视敌人, 战术上重视敌人". 下面我也以这种方式把各个大模型方式向大家画出, 并作出简略解述.
一. 地址划分.
1. CPU 地址.
CPU 地址是指 CPU 的地址总线能寻址的范围, 32bit-CPU 寻址范围为 4G, 这个地址是虚拟的, 实际上外部物理内存是不会使用这么大的内存.
CPU 虚拟地址的 4G 空间, 通常划分为两部分, 一部分为内核虚拟地址, 通常为 3G-4G 之间, 另一部分为用户虚拟地址, 通常为 0G-3G 之间, 显然, 用户进程能使用的虚拟地址范围远大于内核可以使用的虚拟地址空间, 但是, 物理内存只有局限性的几 M, 几 G, 内核虚拟地址如何使用物理内存, 用户空间如何使用物理内存, 这些问题正是 linux 内存管理的关键.
2. 物理内存
物理内存是指外部存储数据的设备, 有可以被 CPU 寻址到的地址总线, 受到 CPU 的 Cache 和 TLB/MMU 管理寻址.
需要澄清一个概念: 任何代码是在 CPU 上运行的, 而不是在物理内存上, 物理内存是个设备, 用于存放用户进程空间的可执行代码或者内核关键数据结构, 这些代码或结构终将是要受到 CPU 通过 MMU 寻址, Cache 命中指令数据来获取的.
NUMA 的全称是非一致性内存访问, 它通常是多核访问的概念, 每一个 CPU 核都会有一个节点对应使用一部分物理内存, 对这些节点的管理附加这些数据结构: perCPU 变量, list 表串联各节点遍历, zone 的划分, zonelist 的管理等等. 为了使问题更加简单化, 我们只分析 UMA 的一个节点的情况, 当然它也包含 NUMA 的一些数据结构特征, 这个后面会有所简述.
下图是 NUMA 的一个简略图抽象如图 2-1 所示.
图 2-1 NUMA 多核物理内存 zone 示意图
3. 内核虚拟地址空间划分.
如果读者仅仅了解一些皮毛, 必然认为内核的虚拟地址空间仅有逻辑地址这一说, 其实这只是内存内核虚拟地址划分的一个特例, 并非全部的完整表述, 现在我划出完整的图形, 并且改变改变对内核虚拟地址空间名称的叫法, 如图 2-2
图 2-2 内核虚拟地址空间划分及其对物理内存的映射
下面来改改名字咯, 直接映射的地址我们可以叫为内核物理直接映射地址或者逻辑地址. linux 原则上只能使用虚拟空间 1G 中的 896M, 剩下的 128M 留作它用, 所以直接映射之外的物理内存称为高端内存. 128M 之间的空间又划分为多个 gap 安全间隙, 虚拟地址, 固定映射和持久映射, 注意这里的虚拟地址叫法通常和前述的内核虚拟地址有些混杂, 后者是指 CPU 内核虚拟地址, 是更广的概念. 由于直接映射的部分有了名字叫逻辑地址, 那么这里的虚拟地址空间常专指这个部分.
虚拟地址有以下用途, 使用 vm_struct 结构体经内核管理高端内存, 它可以使用 kmap 方式获取高端物理内存的空间; 也可以不映射物理高端内存, 将这段地址直接作为外部物理设备的 ioremap 地址, 从而可以直接操纵设备, 当然这也将外部设备地址空间暴露出来并且容易造成干扰, 所以通常不能直接访问 ioremap 映射的地址而是用 readb/writeb 读写, 而且要做好优化屏障设置并且用 iounmap 释放, 因为映射了的设备常具有'边际效应'.
如果没有高端内存,(当然 32bit 的嵌入式系统通常不会使用高端内存, 至少我见过的那么多关于 ARM,powerPC,MIPS32 的嵌入式应用都是没有使用高端内存的), 那么固定映射和持久映射也多半不会用到. 固定映射可以指定长期持有物理内存某些地址页的占用, 这个映射关系可以在初始阶段进行配置, 而持久映射在启用时就建立了同高端内存物理页的映射关系, 它在其他阶段都不会被解除.
强调的是, 我这里不关心高端内存, 内核的直接映射逻辑地址就可以涵盖全部物理内存.
4. 用户虚拟地址空间的划分
用户虚拟地址空间图构并不复杂, 复杂的是它在虚拟内存空间中的应用, 如何映射文件, 如何组织区间映射, 关联的进程是谁, 对应的内存结构体实例是什么等等问题才是用户虚拟映射最难的地方, 下面仅仅划出图示, 对用户虚拟内存空间可以先有一个大了解, 如图 2-3.
图 2-3 用户空间虚拟内存布局
既然用户空间是虚拟的, 那么它是怎么访问物理内存的呢, 当然就是 PGD,PUD,PMD,PTE,OFFSET 及其 TLB 快表查询了, 上层目录入口 PUD 和中间目录入口一般不考虑, 考虑二级目录就可以了. 从网上摘的图 2-4:
图 2-4 用户进程空间访问物理内存的方法
二. 伙伴系统
伙伴系统是按阶管理外界物理内存的方法, 最大有 11 阶, 每一阶有一个或者多个页合并的集合并使用指针串联起来, 同时在同一阶中的一个或多个页集合中形成各自的伙伴, 要强调的是各个阶的伙伴都是等页个数的, 用下图 2-5 是比较好理解的.
图 2-5 伙伴系统在内存中的大致模型
当内核申请一段按页却并非按照阶数分配的内存时候, 通常会使用伙伴系统原理将其按照该申请空间的最大阶数分配, 多出来的页按照伙伴系统算法归并到其他阶的链表当中形成其他阶的新伙伴. 释放该内存空间的时候, 释放的空间会尝试找到能以它为伙伴的那个阶进行连接, 如果大小超过, 则劈开, 多余的再寻找其他可以以它为伙伴的阶. 够拗口的, 但还是很容易理解的, 后面会有源代码呈现出来以实例详细分析.
三. 反碎片技术:
反碎片机制其实还在伙伴系统之前, 它主要是将各个 zone 区域的物理内存分成可回收 reclaimable 但不可移动 unmovable, 可移动 movable, 不可移动 unmovable. 这些标记按照一定得 list 串联起来管理, 当外部条件申请物理内存导致许多碎片的时候, 它可以按照这些数据结构的标志, 来从新组织归类物理内存, 从而减少碎片页或者孤独页. 反碎片技术在嵌入式系统当中少用, 绝大部分由伙伴系统占据江山了, 因此不会对此做具体分析, 简略过之.
四. Slab 分配机制.
众所周知, 操作系统使用伙伴系统管理内存, 不仅会造成大量的内存碎片, 同时处理效率也较低下. SLAB 是一种内存管理机制, 其拥有较高的处理效率, 同时也有效的避免内存碎片的产生, 其核心思想是预分配. 其按照 SIZE 对内存进行分类管理的, 当申请一块大小为 SIZE 的内存时, 分配器就从 SIZE 集合中分配一个内存块 (BLOCK) 出去, 当释放一个大小为 SIZE 的内存时, 则将该内存块放回到原有集合, 而不是释放给操作系统. 当又要申请相同大小的内存时, 可以复用之前被回收的内存块(BLOCK), 从而避免了内存碎片的产生.[注: 因 SLAB 处理过程的细节较多, 在此只是做一个原理上的讲解
1. 总体结构
图 1 SLAB 内存结构
2. 处理流程
如图 1 中所示: SLAB 管理机制将内存大体上分为 SLAB 头, SLOT 数组, PAGES 数组, 可分配空间, 被浪费空间等模块进行分别管理, 其中各模块的功能和作用:
SLAB 头: 包含 SLAB 管理的汇总信息, 如最小分配单元 (min_size), 最小分配单元对应的位移(min_shift), 页数组地址(pages), 空闲页链表(free), 可分配空间的起始地址(start), 内存块结束地址(end) 等等信息(如代码 1 所示), 在内存的管理过程中, 内存的分配, 回收, 定位等等操作都依赖于这些数据.
SLOT 数组: SLOT 数组各成员分别负责固定大小的内存块 (BLOCK) 的分配和回收. 在 nginx 中 SLOT[0]~SLOT[7]分别负责区间在 [1~8],[9~16],[17~32],[33~64],[65~128],[129~256],[257~512],[513~1024] 字节大小内存的分配, 但为方便内存块 (BLOCK) 的分配和回收, 每个内存块 (BLOCK) 的大小为各区间的上限 (8,16,32,64,128,256,512,1024). 比如说: 假如应用进程请求申请 5 个字节的空间, 因 5 处在[1~8] 的区间内, 因此由 SLOT[0]负责该内存的分配, 但区间 [1~8] 的上限为 8, 因此即使申请 5 个字节, 却依然分配 8 字节给应用进程. 以此类推: 假如申请 12 字节, 12 处于区间 [9~16] 之间, 取上限 16, 因此由 SLOT[1]分配 16 个字节给应用进程; 假如申请 50 字节, 50 处于区间 [33~64] 之间, 取上限 64, 因此由 SLOT[2]分配 64 个字节给应用进程; 假如申请 84 字节, 84 处于区间 [65~128] 之间, 取上限 128, 因此由 SLOT[3]分配 128 个字节;...; 假如申请 722 字节, 722 处于区间 [513~1024] 之间, 取上限 1024, 因此由 SLOT[7]分配 1024 字节.
PAGES 数组: PAGES 数组各成员分别负责可分配空间中各页的查询, 分配和回收, 其处理流程可参考 3.2 节的说明.
可分配空间: SLAB 在逻辑上将可分配空间划分成 M 个内存页, 每页大小为 4K. 每页内存与 PAGES 数组成员一一对应, 由 PAGES 数组各成员负责各内存页的分配和回收.
被浪费空间: 按照每页 4K 的大小对空间进行划分时, 满足 4K 的空间, 将作为可分配空间被 PAGES 数组进行管理, 而最后剩余的不足 4K 的内存将会被舍弃, 也就是被浪费了!
来源: http://stor.51cto.com/art/201808/580961.htm