memory management - Jokacer/Learn GitHub Wiki

x86 CPU采用了段页式地址映射模型。进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存。通常32位Linux内核地址空间划分0-3G为用户空间,3-4G为内核空间。注意这里是32位内核地址空间划分,64位内核地址空间划分是不同的。

当内核模块代码或线程访问内存时,代码中的内存地址都为逻辑地址,而对应到真正的物理内存地址,需要地址一对一的映射,如逻辑地址0xc0000003对应的物理地址为0x3,0xc0000004对应的物理地址为0x4,… …,逻辑地址与物理地址对应的关系为:物理地址 = 逻辑地址 – 0xC0000000。若机器中安装8G物理内存,那么内核就只能访问前1G物理内存,后面7G物理内存将会无法访问,显然不能将内核地址空间0xc0000000 ~ 0xfffffff全部用来简单的地址映射。因此x86架构中将内核地址空间划分三部分:ZONE_DMAZONE_NORMALZONE_HIGHMEM。ZONE_HIGHMEM即为高端内存,这就是内存高端内存概念的由来。

在x86结构中,三种类型的区域如下:

ZONE_DMA:内存开始的16MB

ZONE_NORMAL :16MB~896MB

ZONE_HIGHMEM:896MB ~ 结束

内核把物理也作为内存管理的基本单位,内存管理单元(MMU,管理内存并把虚拟地址转换成物理地址的硬件)通常以页为单位进行处理,从虚拟内存的角度来看,页是最小单位,内核用结构体page表示每个物理页:

struct page {
    unsigned long flags;//页状态
    atomic_t _count;//页的引用计数
    atomic_t _mapcount;
    unsigned long private;//私有数据
    struct address_space *mapping;//和这个页关联的address_space对象
    pgoff_t index;
    struct list_head lru;
    void *virtual;//页虚拟地址
};

内核将页划分成不同的区(zone),使用区对具有相似特性的页进行分组,区的实际使用和分布与体系结构相关。区的划分没有任何物理意义,只是内核为了管理页而采取的一种逻辑分组。

空间申请操作有:malloc()返回的页在进程的虚拟地址空间内是连续的,kmalloc()确保页在物理地址和虚拟地址上是连续的,vmalloc()值确保页在虚拟地址空间内是连续的,它通过分配非连续的物理内存块,再修正页表,把内存映射到逻辑地址空间的连续区域。vmalloc()函数为了把物理上不连续的页转换为虚拟地址空间上连续的页,必须专门建立页表项,通过vmalloc()获得的页必须一个一个的进行映射,会导致比直接内存映射大得多的TLB抖动,所以大多内核代码使用kmalloc()来获得内存。

slab层

为了便于数据的频繁分配和回收,程序员使用空闲链表担任数据结构的缓存层。空闲链表包含可供使用的、已经分配好的数据结构块,当代码需要一个新的数据结构实例时,可以从空闲链表中抓取一个,而不需要分配内存,再把数据放进去,当不再需要这个实例时就把它放回链表,而不是释放,这样空闲链表起一个缓存的作用。

slab层把不同的对象划分为高速缓存组,每个高速缓存组都存放不同类型的对象每种对象类型对应一个高速缓存组。slab由一个或多个物理上连续的页组成,一般情况下slab也就仅仅由一页组成,每个高速缓存可以由多个slab组成。

slab有三种状态:满、部分满和空,在分配新对象时首先选择部分满的slab,如果没有再考虑空的slab。

slab层的关键就是避免频繁分配和释放页,在每个高速缓存的基础上进行管理,通过提供给整个内核一个简单的接口来完成,描述符为:

struct slab {
    struct list_head list;//满、部分满或空链表
    unsigned long colouroff;//slab着色偏量
    void *s_mem;//slab中的第一个对象
    unsigned int inuse;//slab中已分配的对象数
    kmem_bufctl_t free;//第一个空闲对象(如果有)