自底而上话内存

内存模块是内核中一个非常重要的部分。我们现在的计算机都被称作为存储程序计算机,也就是所有待运行的程序和数据都需要加载在内存当中方能被执行。正因为如此,很多朋友都希望学习内存模块的工作机制,但碍于内核代码的庞大以及文档的缺失和稀少,总是感觉无从下手。

经过一段时间的摸索,我终于对内存模块有了一点点的了解。今天整理成文,希望能给想要探究内存模块的朋友一点点借鉴。

内存模块的层次结构

首先内存模块具有一定层次结构的,从物理内存到软件控制的内存经过了几个层次的隔离。

e820从硬件获取内存分布

原始内存分配器--memblock

页分配器

Slub分配器

大致我们能看到这么四个层次的内存管理结构。前两者基本在内核启动时使用,而平时大多使用的是后两者。

页分配器的探究

页分配器是一个相对牵着概念较多的层次,也可以说正是这个层次把物理上的内存差异屏蔽,从而向用户呈现了一致的使用接口。

第一个让我好奇的是页结构体究竟是存放在哪里的?

最原始的版本中页结构体是作为一个大的静态数组存放在内存中的,而随着内存变大,空洞变多,静态数组显然不符合设计理念。之后则提出了SPARSEMEM的概念,按实际情况分配页结构体。

寻找页结构体的位置

知道页结构体在那里,顺便来瞥一眼结构体的样子。为啥说是瞥一眼呢?因为这个结构体实在是太大(乱)了。为了满足各种需求,这个结构体中进行了多重复用。先放在这里,作为一个参考文档把。

眼花的页结构体

所谓的内存物理差异无非就两点:

  • 硬件是否能访问

  • 访问速度的差异

而这两点对应到软件上的概念是:

  • ZONE

  • NUMA NODE

那内核中是如何把这两个信息保存起来,并用来指导内存非配的呢?

首先,系统为了获取NUMA信息,新增了numa_meminfo结构从硬件中获取NUMA信息,然后在转换到了memblock层。

NUMA信息获取

接下来就是大名鼎鼎的pg_data_t结构体出场了。所有的页分配工作都是基于这个数据结构的信息所作出的。

Node-Zone-Page

有了这样的概况之后,我们就可以来看看页是如何初始化和被分配的了。

传说的伙伴系统

将内存划分为node/zone之后,分配内存时是不是有办法去控制从哪个node哪个zone上去分配呢?答案是有的。

GFP的功效

为了加速页的分配和回收,减少多cpu之间对伙伴系统的竞争,内核给每个zone增加了per_cpu_pageset。

per_cpu_pageset

slub分配器

页分配以一个页为最小单位,而在系统运行过程中往往不需要这么大的空间。对于这些内存请求,则有slub完成。

slub的理念

为了理解,小编还针对各种情况用图来解说,希望能更直观和形象。

图解slub

页分配器的用户们

说实话原理谁都清楚,但是魔鬼在细节中。为了更好管理内存,内核中会给分配出去的内存做一些标记,这样方便在回收,出错等时候判断内存的用途。

为了更好的理解内存管理中的代码流程,我们需要了解页分配器的用户们

内存管理的不同粒度

越是学习,越发现内存管理是一个比较大的话题。这么一路走来,我们从最底层的物理信息一直走到了slub分配器,可以说对内存管理有了一定的认知。

此刻请允许我站的远一点来观察整个内存管理架构,也从另一个角度 -- 粒度 来观察内存管理的奥妙。

内存管理的不同粒度

挑战和进化

内存管理的层次结构已经逐渐清晰,接着发现在内存子系统随着应用场景和硬件环境的变化也会遇到新的挑战,并对此做出自身的进化。

在此仅以一点点的记录来进一步窥探这个神秘的世界。

挑战和进化

参考文献

Understand Linux VM

Last updated