最近频频遇到系统负载不高且当下也没有大量的 IO 操作,但是内存使用率却较高的情况。 因此有必要仔细研究下 Linux 下的内存管理机制,其他类 Unix 平台也多少大同小异,但是也不可一概而论,待时间充裕再比较分析其他系统下的内存管理机制。
首先 Linux 同时涉及 虚拟地址空间 和 物理内存区域 两个层次。同时 用户空间与内核空间则属于虚拟地址空间的划分,而在物理上又被分为三块:ZONE_DMA,ZONE_NORMAL 和ZONE_HIGH。这里的 ZONE_DMA 是可直接存取访问的内存区域,即不需要任何的逻辑映射,大小为 0~16MiB;ZONE_NORAML 为内核所保留使用的,其大小为16~896MiB;剩下的 896~End 都是 ZONE_HIGH 的区域,该段区域是应用程序进行 malloc() 时所能访问的地址(当然应用程序可以通过系统调用来访问 ZONE_NORMAL 区域)。说完内存的映射,那么就应该讲解操作系统如何管理内存了——**内存的分配与回收:**内存的分配在 kernel 中是通过 Buddy 算法(即伙伴算法)来做统一分配的,伙伴系统能够在一定程度上缓解外部碎片,并通过伙伴合并提高连续物理内存分配的成功率,从而在很大程度上提高了内存的使用效率(但无法完全消除内存碎片);内存回收则是通过 LRU 算法,即基于最近最少使用算法来进行回收的。LRU 算法的核心思想是:如果资源已较长时间未使用了,那么在最近的某段时间内,该资源用到的可能性也几乎是不可能的。同时内核维护着两张链表,分别为 Active 和 Inactive 链表,当进程不再活动或是分配的内存已不再使用时,内存将有原 Active 链表移到 Inactive 链表尾部,当操作系统发现有必要进行内存回收时,将由 Inactive 链表表头开始,从而保证所回收的内存地址空间确实是不再使用并且可以进行回收的。


那么知道内存的划分与分配回收算法,剩下的就是内核具体如何分配和回收的问题了。内存的管理在 kernel 中是 VM(Virtual Memory)子系统承担的。VM 主要做两件事:线性映射 和 动态释放。线性映射即将用户态的物理内存映射为逻辑上线性连续的内存空间,然后当应用程序申请内存时将其分配给应用,而申请操作是由应用程序自身发起的,常见的即 malloc();动态释放即内存的回收,内存回收在内核中由线程 kswapd 完成。kswapd 是 Linux 内核创建的后台内核线程,负责在内存压力达到一定条件时执行后台内存平衡和回收。其具体实例和作用范围与内存节点、内核配置等有关,其虽然没有独立的内存空间但也占用非常少量的内存,kswapd 的运行方式有两种:内核主动调用 和 时钟源中断。时钟中断即每若干时钟滴答后,kswapd 即由 wakeup_kswapd 进行唤醒;第二种方式是当可用内存低于一定的水位后,由内核主动发生调用。如下图所示:

那么具体地,这些水位的值该如何计算呢?核心代码其实在 /mm/page_alloc.c 中,我们来简单地看下(暂时只关心 ZONE_HIGHMEM部分,因为这部分才是和我们的应用程序息息相关的):