Linux内存管理
页
内核把物理页作为内存管理的基本单位。尽管处理器的最小可寻址单位通常为字(甚至字节),但是内存管理单元(MMU 管理内存并把虚拟地址转换为物理地址的硬件)通常以页为单位进行处理。正是因为如此,MMU以页(page)大小为单位来管理系统中的页表。从虚拟内存的角度来看,页就是最小单位。
struct page{
unsigned long flags;
atomic_t _count;
atomic_t _mapcount;
unsigned long private;
struct address_space *mapping;
pgoff_t index;
struct list_head lru;
void *virtual;
};
- flag域用来存放页的状态,这些状态包括页是不是脏的,是不是被锁定在内存中等。
- _count域存放页的引用计数,当计数器值变为-1时,就说明当前内核并没有引用这一页,在新的分配中就可以使用它。
- virtual域是页的虚拟地址。在高端内存并不永久映射到内核地址空间上,这时域的值为null,需要的时候动态映射这些页。
内核用这一个结构来管理系统中所有的页,因为内核需要知道一个页是否空闲。如果已经分配,内核还需要知道谁拥有这个页。拥有者可能是用户空间进程、动态分配的内核数据、静态内核代码或页高速缓存等。
计算页管理内存开销,假设struct page占40字节,假定系统物理页为8kb大小,系统有4GB物理内存,那么系统中一共有524288个页,page结构体要消耗20MB,这个体量相对于4GB而言只占很小一部分。
区
由于硬件的限制,内核并不能对所有页一视同仁。有些页位于内存中特定的物理地址上,所以不能将其用于一些特定的任务。由于存在了这种限制,所以内核把页划分为不同的区(zone)。内核使用区对具有相似特征的页进行分组。
Linux必须处理如下两种由于硬件存在缺陷而引起的内存寻址问题:
- 一些硬件只能用某些特定的内存地址来执行DMA(直接访问内存)
- 一些体系结构的内存的物理寻址访问比虚拟寻址范围大的多,这样就有一些内存永远不能映射到内核空间上。
因为这些限制条件,Linux主要使用了四种区:
- ZONE_DMA -- 这个区包含的页功能来执行DMA操作
- ZONE_DMA32 -- 和ZONE_DMA类似,该区包含的页面可用来执行DMA操作,而和ZONE_DMA不同之处在于,这些页面只能被32位设备访问。
- ZONE_NORMAL 这个区浩航的都是能正常映射的页。
- ZONE_HIGHEM 这个区包含“高端内存”,其中的页并不能永久地映射到内核地址空间。
Linux把系统的页划分为区,形成不同的内存池,不是所有的体系机构都定义了全部的四个区,有些64位的体系结构,如Intel的x86-64体系机构可以映射和处理64位的内存空间,所以x86-64没有ZONE_HIGHMEM区,所有的物理内存都处于ZONE_DMA和ZONE_NORMAL区。
slab层
分配和释放数据结构是所有内核中最普遍的操作之一。为了便于数据的频繁分配和回收,编程人员常常会用到空闲链表。空闲链表包含可供使用的、已经分配好的数据结构块。当代码需要一个新的数据结构实例是,就可以从空闲链表中抓取一个,而不需要分配内存,再把数据放进去,以后当不需要这个数据结构的实例,就把它放回空闲链表,而不是释放它。从这个角度讲,空闲链表相当于对象的高速缓存。
内核中,空闲链表面临的主要问题之一是不能全局控制。当可用内存变得紧缺时,内核无法通知每个空闲链表,让其收缩缓存的大小以便释放出一些内存来。实际上,内核根本就不知道存在任何空闲链表。为了弥补这一缺陷,也为了使代码更加稳固,Linux内核提供了slab层(也就是slab分配器)。slab分配器扮演了通用数据结构缓存层的角色。
slab分配器试图在几个基本原则之间寻求一种平衡:
- 频繁使用的数据结构也会频繁分配和释放,因此应当缓存他们。
- 频繁分配和回收必然导致内存碎片,因为已释放的数据结构又会放回空闲链表,因此不会导致碎片。
- 回收的对象可以立即投入下一次分配,因此,对于频繁释放和分配,空闲链表能提高性能。
- 如果分配器知道对象大小,页大小和总的高速缓存的大小这样的概念,它会做出更明智的决策。
- 如果让部分缓存专属于单个处理器,那么分配和释放就可以在不加SMP锁的情况下进行。
- 如果分配器是与NUMA相关的,它就可以从相同的内存节点为请求者进行分配。
- 对存放的独享进行着色(color),以防止多个对象映射到相同的高速缓存行(cache line)。
slab层把不同的对象划分为所谓的高速缓存组。其中每个高速缓存组存放不同类型的对象,每种对象类型对应一个高速缓存。slab由一个或多个物理上连续的页组成。一般情况下,slab也就仅仅由一页组成。每个高速缓存可以由多个slab组成。每个slab有三种状态:满、部分、空闲。