内存作为系统宝贵的资源之一,一直是系统管理的重要部分。这次我们从头说起。
先来一张层次结构
+-------------------------------+
| |
| slub |
| |
+-------------------------------+
| |
| page |
| |
+--------+------+-------+-------+
| Zone |Zone |Zone |Zone |
+--------+------+-------+-------+
| Node | Node |
+---------------+---------------+
| |
| memblock |
| |
+-------------------------------+
| |
| e820 |
| |
+-------------------------------+
内核在层次上经过这么四层的抽象,将字节粒度的内存做了不同层次的封装,以便适应不同层次的需求。
最底层的e820是体系结构相关的层次,所以在不同的体系结构上可能使用的是不同的硬件和数据结构。而从memblock层次以上,则是体系结构无关的。尤其是page和slub,在平时的工作中会经常遇到。
万丈高楼平地起,虽然我们通常使用的是上层的接口,但如果能对底层的原理和逻辑有一定的了解将会对系统的运行有着更深入的了解。
处于底层的e820的工作就是将保存在硬件中的内存信息转化成软件可以识别数据的过程。可以说,它是内存的侦察兵,探测器。没有它,系统对内存的布局可以说是一无所知。
上文中也提到了,e820是和体系结构相关的。不同的硬件平台采用不同的方式探测系统的内存布局。内核为了更好适配不同架构,第一个统一起来的内存数据结构就是memblock。
内存会将从e820获取的内存信息转换成memblock的格式,并用这个数据结构来做最初的内存分配。
或许是因为memblock出现的比较早,或许是因为要兼容不容体系架构和软件架构,虽然memblock的结构中包含了NUMA信息,但这部分信息的初始化是在另一个地方进行的。你看e820种自身是不携带这部分信息的,所以在x86上也无法同时进行,需要一段额外的工作来获取NUMA信息,并添加到memblock结构中。对这部分感兴趣的童鞋可以参考上文。
从层次的角度上看,memblock之上就是page了。但是限制于硬件访问,内存访问速度,系统中不得不对内存进行划分。实际上,Node和Zone两种对内存划分的方式。前者是不同cpu对内存的访问速度,后者是内存地址空间。只是可巧,在数据结构的表达中形成了Node->Zone->Page的数据结构。
这样的数据结构主要目的是为了Page的分配形成的,这样我们就可以方便的分配指定Node和Zone的内存了。
这算是一个不在内存层次结构中的概念,但是却十分重要必不可少。
page这个次在内核中有两个概念:
- 一块页面大小的内存
- 描述一块页面大小的数据结构
有意思的是,我们用来描述页面的数据结构,本身也是存放在内存中的。
那这部分的数据放在哪,要如何放呢?这就是SPARSEMEM设计的作用所在了。有兴趣的朋友可以作为选读。
当我们了解了Node->Zone->Page的层次结构,基本上就已经理解了页分配器的运行机制。所以这篇文章篇幅较少。可能重点大家可以看的就是page都是从哪里来的。而在这个过程中有意思的一点就是我们常说的buddy system是如何工作的.
PCPU变量是内核中经常用到的基础设施。说白了很简单,就是一个变量的名字,在每个cpu上都有各自的副本。上面两个文章分别简单描述了这两种变量的实现方式。希望给到大家一点启发。
这两篇文章之所以放在这个位置,是因为静态PCPU变量的实现基于了memblock层,而动态PCPU变量的实现基于了page层。所以或许放在这里比较合适。
终于,在辛勤的攀爬过程中,我们从最底层的硬件来到了最上面的slub分配器。slub分配器可以说是内存管理层次中最复杂也是最精妙的部分。希望我的一点点解说能够帮助大家对它产生一定的感性认识。