内核不支持简单快捷的内存分配方式。
一、页
内核把无力页作为内存管理的基本单位。内存管理单元(MMU,管理内存并把虚拟地址转换为物理地址的硬件)通常以页为单元进行处理。体系结构不同,页大小不尽相同。大多数32位体系结构支持4KB的页,64位支持8KB的页。
物理页的结构体:
struct page {
unsigned long flags;//页的状态
atomic_t _count;//引用计数
atomic_t _mapcount;
unsigned long private;
struct address_space *mapping;
pgoff_t index;
struct list_head;
void *virtual;//页的虚拟地址
};
page结构与物理页相关,而并非与虚拟页相关。
二、区
内核使用区对具有相似特性的页进行分组。Linux主要使用四种区:
- ZONE_DMA:包含的页用来执行DMA(直接内存访问)操作
- ZONE_DMA32:类似ZONE_DMA,但只能被32位设备访问
- ZONE_NORMAL:包含能正常映射的页
- ZONE_HIGHEM:高端内存,其中的页并不能永久地映射到内核地址空间
除了ZONE_HIGHEM,其余的内存就是低端内存。
Linux把系统的页划分为区,形成不通的内存池,以根据用途进行分配。区的划分并没有任何物理意义,只不过是内核为了管理页而采取的一种逻辑上的分组。分配可以从一个或多个区获取页,但不可能同时从两个区分配。
三、获得页
内核提供了一种请求内存的底层机制,并提供了对它进行访问的几个接口,以页为单位进行分配:
alloc_pages分配的是连续的物理页,可以通过void * page_address(struct page *page);把物理页转化为逻辑地址。
四、kmalloc
void * kmalloc(size_t size, gft_t flags);
kmalloc以字节为单位进行分配,分配的内存区在物理上是连续的。
gft_t标志可分为:
- 行为修饰符:表示内核应该如何分配所需的内存
- 区修饰符:表示从哪儿分配内存
- 类型修饰符:组合了行为修饰符和区修饰符
void kfree(const void *ptr);
释放kmalloc分配的内存块。kfree(NULL)是安全的。
五、vmalloc
vmalloc与kmalloc类似,但vmalloc分配的内存虚拟地址是连续的,而物理地址则无需连续。
大多数情况下,只有硬件设备需要得到物理地址连续的内存。在很多体系结构上,硬件设备存在于内存管理单元之外,不理解虚拟地址,因此硬件设备用的内存区都必须是物理上连续。仅供软件使用的内存块就可以使用只有虚拟地址连续的内存块。
void * vmalloc(unsigned long size);//分配
void vfree(const void *addr);//释放
以上两个函数都可能休眠,因此不能从中断上下文进行调用,也不能从其他不允许阻塞的情况下进行调用。
六、slab层
slab层把不同对象划分为高速缓存组,其中每个高速缓存组都存放不同类型的对象。每种对象对应一个高速缓存。
高速缓存又划分为一个或多个slab,slab由一个或多个物理上连续的页组成。每个slab都包含一些对象成员,处于满、部分满或空三种状态之一。当内核的某一部分需要一个新的对象时,先从部分满的slab中进行分配。如果没有部分满的slab,就从空的slab中进行分配。如果没有空的slab,就要创建一个slab。
//创建新的高速缓存:
struct kmem_cache * kmem_cache_create(const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void *));
//撤销高速缓存:
int kmem_cache_destroy(struct kmem_cache *cachep);
//获取对象
void * kmem_cache_alloc(struct kmem_cache *cachep, gft_t flags);
//释放对象
void kmem_cache_free(struct kmem_cache *cachep, void *objp);
七、在栈上的静态分配
内核栈小且固定。每个进程的内核栈大小既依赖体系结构,也与编译时的选项有关。历史上每个进程都有两页内核栈(中断处理程序与被中断进程共享一个内核栈),但2.6内核引入一个单页内核栈,这样中断处理程序就使用中断栈了。
八、高端内存的映射
高端内存中的页不能永久地映射到内核地址空间上,一旦这些页被分配,就必须映射到内核的逻辑地址空间上。
//永久映射:
void *kmap(struct page *page);
void kunmap(struct page *page);
//临时映射,不会阻塞
void *kmap_atomic(struct page *page, enum km_type type);
void kunmap_atomic(void *kvaddr, enum km_type type);
九、每个CPU的分配
一般来说,每个CPU的数据存放在一个数组中,数组中每一项对应着系统上一个存在的处理器,按当前处理器号确定这个数组的当前元素。
十、新的每个CPU接口
编译时:
//定义
DEFINE_PER_CPU(type, name);
//声明
DECLARE_PER_CPU(type, name);
//操作变量例程
get_cpu_var(name)//会禁止抢占
put_cpu_var(name)//重新激活抢占
per_cpu(name, cpu)//不禁止内核抢占,也不提供任何形式的锁保护
运行时:
void *alloc_percpu(type);
void *__alloc_percpu(size_t size, size_t align);
void free_percpu(const void *);