页
内核以页作为内存管理的基本单位。32位机器支持4KB的页,64位支持8KB的页。
内核用struct page结构表示页,位于 linux/mm_types.h 中:
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; // 页的虚拟地址
}
区
内核把页划分为不同的区。
区的实际使用和分布是与体系结构相关的,在x86-32上的区:
区 | 描述 | 物理内存 |
---|---|---|
ZONE_DMA | DMA使用的页 | <16MB |
ZONE_NORMAL | 正常可寻址的页 | 16 ~ 896MB |
ZONE_HIGHMEM | 动态映射的页 | >896MB |
内存的使用
1. 以页为单位
内核提供了一种请求内存的底层机制,并提供了对它进行访问的几个接口。所有这些接口都以页为单位分配内存,定义于 linux/gfp.h 中:
标志 | 描述 |
---|---|
alloc_page(gfp_mask) | 只分配一页,返回指向页结构的指针 |
alloc_pages(gfp_mask, order) | 分配2^order个页,返回指向第一页页结构的指针 |
__get_free_page(gfp_mask) | 只分配一页,返回指向其逻辑地址的指针 |
__get_free_pages(gfp_mask, order) | 分配2^order页,返回指向第一页逻辑地址的指针 |
get_zeroed_page(gfp_mask) | 只分配一页,让其内容填充0,返回指向其逻辑地址的指针 |
2. 以字节为单位
kmalloc()
kmalloc()函数可以从内核内存中获得一块以字节为单位的、指定大小的、物理上连续的内存。在 linux/slab.h 中申明:
void * kmalloc(size_t size, gfp_t flags);
kfree()
kfree()函数释放由kmalloc()分配出来的内存块。与用户空间类似,分配与回收要注意配对使用,以避免内存泄露和其他bug。kfree()函数在 linux/slab.h 中申明:
void kfree(const void *ptr)
vmalloc()
vmalloc()函数的工作方式类似于kmalloc(),只不过前者分配的内存虚拟地址是连续的,而物理地址则无需连续。kmalloc()函数确保页在物理地址上是连续的,虚拟地址自然也是连续的。vmalloc()函数在 linux/vmalloc.h 中声明:
void * vmalloc(unsigned long size)
释放通过下面的函数:
void vfree(const void *addr)
slab层
为什么需要slab?文章《Linux slab 分配器剖析》讲得非常的好。这里摘录一段:
Linux 所使用的 slab 分配器的基础是 Jeff Bonwick 为 SunOS 操作系统首次引入的一种算法。Jeff 的分配器是围绕对象缓存进行的。在内核中,会为有限的对象集(例如文件描述符和其他常见结构)分配大量内存。Jeff发现对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间。因此他的结论是不应该将内存释放回一个全局的内存池,而是将内存保持为针对特定目而初始化的状态。例如,如果内存被分配给了一个互斥锁,那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数(mutex_init)即可。后续的内存分配不需要执行这个初始化函数,因为从上次释放和调用析构之后,它已经处于所需的状态中了。
slab分配器
Linux内核提供了slab层(slab分配器),其扮演了通用数据结构缓存层的角色。slab层把不同的对象划分为所谓高速缓存组,其中每个高速缓存组都存放不同类型的对象,每种对象类型对应一个高速缓存。例如,一个高速缓存用于存放进程描述符,而另一个高速缓存存放索引节点对象,然后这些高速缓存又被划分为slab。slab 由一个或多个物理上连续的页组成。一般情况下,slab也就是仅仅由一页组成,每个高速缓存可以由多个slab组成。
slab分配器把每一个请求的内存称之为对象。每个slab都包含一些对象成员,这里的对象指的是被缓存的数据结构。每个slab处于三种状态之一:满、部分满或空。一个满的slab没有空闲的对象(slab中的对象都已被分配)。一个空的slab没有分配出任何对象(slab中所有对象都是空闲的)。一个部分满的slab有一些对象已分配出去,有些对象还空闲着。当内核的某一部分需要一个新的对象时,先从部分满的slab中进行分配,如果没有部分满的slab,就从空的slab中进行分配。如果没有空的slab,就要创建一个slab了。
下面图示高速缓存、slab、对象之间的关系:
slab分配器接口
(1) 创建高速缓存:
struct kmem_cache * kmem_cache_create(
const char *name, // 高速缓存名称
size_t size, // 每个元素大小
size_t align, // 第一个对象的偏移,确保页内对齐
unsigned long flags, // 可选设置项
void (*ctor)(void *)); // 构造函数,一般为null
(2) 创建高速缓存后,可以通过下列函数获取对象:
void * kmem_cache_alloc(
struct kmem_cache *cachep, // 指定高速缓存
gfp_t flags) // 与前面相同,可用GFP_KERNEL或GFP_ATOMIC
(3) 释放对象
void kmem_cache_free(
struct kmem_cache *cachep,
void *objp)