zhoujiyu 2016-12-12
内核把物理页作为内存管理的基本单位。尽管处理器的最小可寻址单位通常为字(甚至字节),但是内存管理单元(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;
};
内核用这一个结构来管理系统中所有的页,因为内核需要知道一个页是否空闲。如果已经分配,内核还需要知道谁拥有这个页。拥有者可能是用户空间进程、动态分配的内核数据、静态内核代码或页高速缓存等。
计算页管理内存开销,假设struct page占40字节,假定系统物理页为8kb大小,系统有4GB物理内存,那么系统中一共有524288个页,page结构体要消耗20MB,这个体量相对于4GB而言只占很小一部分。
由于硬件的限制,内核并不能对所有页一视同仁。有些页位于内存中特定的物理地址上,所以不能将其用于一些特定的任务。由于存在了这种限制,所以内核把页划分为不同的区(zone)。内核使用区对具有相似特征的页进行分组。
Linux必须处理如下两种由于硬件存在缺陷而引起的内存寻址问题:
因为这些限制条件,Linux主要使用了四种区:
Linux把系统的页划分为区,形成不同的内存池,不是所有的体系机构都定义了全部的四个区,有些64位的体系结构,如Intel的x86-64体系机构可以映射和处理64位的内存空间,所以x86-64没有ZONE_HIGHMEM区,所有的物理内存都处于ZONE_DMA和ZONE_NORMAL区。
分配和释放数据结构是所有内核中最普遍的操作之一。为了便于数据的频繁分配和回收,编程人员常常会用到空闲链表。空闲链表包含可供使用的、已经分配好的数据结构块。当代码需要一个新的数据结构实例是,就可以从空闲链表中抓取一个,而不需要分配内存,再把数据放进去,以后当不需要这个数据结构的实例,就把它放回空闲链表,而不是释放它。从这个角度讲,空闲链表相当于对象的高速缓存。
内核中,空闲链表面临的主要问题之一是不能全局控制。当可用内存变得紧缺时,内核无法通知每个空闲链表,让其收缩缓存的大小以便释放出一些内存来。实际上,内核根本就不知道存在任何空闲链表。为了弥补这一缺陷,也为了使代码更加稳固,Linux内核提供了slab层(也就是slab分配器)。slab分配器扮演了通用数据结构缓存层的角色。
slab分配器试图在几个基本原则之间寻求一种平衡:
slab层把不同的对象划分为所谓的高速缓存组。其中每个高速缓存组存放不同类型的对象,每种对象类型对应一个高速缓存。slab由一个或多个物理上连续的页组成。一般情况下,slab也就仅仅由一页组成。每个高速缓存可以由多个slab组成。每个slab有三种状态:满、部分、空闲。