目录
一、页(page)
二、区(zone)
三、页操作
四、kmalloc()
五、vmalloc()
六、slab 分配器
七、在栈上的静态分配
内核把物理页作为内存管理的基本单位。尽管处理器的最小可寻 址单位通常为字或字节,但是,内存管理单元(MMU,管理内存并把虚拟地址转换为物理地址的硬件)通常以页为单位进行处理。MMU 以页为单位来管理系统中的页表。从虚拟内存的角度来看,页就是最小单位。
大多数 32 位体系结构支持 4KB 的页,而 64 位体系结构一般会支持 8KB 的页。
内核用 struct page 结构表示系统中的每个物理页,位于
page 结构与物理页相关,而并非与虚拟页相关。内核用这一结构来管理系统中所有的页,因为内核需要知道一个页是否空闲。一个页的拥有者可能是用户空间进程、动态分配的内核数据、静态内核代码或页高速缓存。
由于硬件的限制,有些页位于内存中的特定的物理地址上,所以不能将其用于一些特定的任务,于是内核把页划分为不同的区(zone) ,由此对具有相似特性的页进行分组。Linux 必须处理如下两种由于硬件存在缺陷而引起的内存寻址问题:
因此,Linux 划分了四种区:
Linux 把系统的页划分为分区,形成不同的内存池,这样就可以根据用途进行分配了。区是内存为了管理页而采取的一种逻辑上的分组。
内核提供了几种请求页的接口,所有接口都以页为单位分配内存,定义于
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)
该函数分配 个连续的物理页,并返回一个指针,指向第一个页的 page 结构体。出错则返回 NULL。
可以用下面这个函数把给定的页的 page 转换成它的逻辑地址:
void *page_address(struct page *page)
该函数返回一个指针,指向给定物理页当前所在的逻辑地址。
当你不在需要页时可以用下面的函数释放它们:
void __free_pages(struct page *page, unsigned int order)
void free_pages(unsigned long addr, unsigned int order)
void free_page(unsigned long addr)
kmalloc() 函数与用户空间的 malloc() 类似,不过它多了一个 flags 参数,这个函数可以获得以字节为单位的一块内核内存。其在
void *kmalloc(size_t size, gfp_t flags)
这个函数返回一个指向 size 大小的内存块指针,并且所分配的内存区在物理地址上是连续的(虚拟地址自然也是连续的)。出错时则返回 NULL。
使用 kfree() 函数可以释放由 kmalloc() 分配出来的内存块:
void kfree(const void *ptr)
如果想要释放的内存不是由 kmalloc() 分配的,或者想要释放的内存早就被释放了,调用这个函数就会有很严重的后果。
vmalloc() 函数类似于 kmalloc(),但是 vmalloc() 函数分配的内存虚拟地址是连续的,而物理地址则无需连续。malloc() 函数也一样,其在虚拟地址空间内是连续的,但是并不保证它们在物理 RAM 中也是连续的。
void *vmalloc(unsigned long size)
void vfree(const void *addr)
大多数情况下,只有硬件设备用到的内存区必须是物理上连续的块。而供软件使用的内存块就可以使用只有虚拟地址连续的内存块。
分配和释放数据结构是所有内核中最普遍的操作之一,空闲链表包含可供使用的、已分配好的数据结构块,当代码需要一个新的数据结构实例时,就可以从空闲链表中抓取一个,而不需要重新分配内存。空闲链表相当于对象高速缓存,可以快速存储频繁使用的对象类型。
但空闲链表无法全局控制,当可用内存紧缺时,内核无法通知每个空闲链表,让其释放一些内存。所以为了弥补这一缺陷,Linux 内核提供了 slab 层(slab 分配器)。slab 分配器扮演了通用数据结构缓存层的角色。
slab 分配器把不同的对象划分为不同的高速缓存组,每个高速缓存组都存放不同类型的对象。比如,一个高速缓存用于存放进程描述符(task_struct 结构的一个空闲链表),一个用于存放索引节点对象(struct inode)。
然后,这些高速缓存又被划分为多个 slab,slab由一个或多个物理上连续的页组成。每个 slab 都包含一些被缓存的数据结构,并且每个 slab 都处于三种状态之一:满、部分满或空(注意这里满的意思是所有的数据结构都被使用中,无法分配)。当内核的某一部分需要一个新的对象时,先从部分满的 slab 中进行分配,其次再从空的 slab 中进行分配,如果没有空的 slab,那么就需要创建一个新 slab了。
下面是三者之间的关系:
slab 负责内存紧缺情况下所有底层的对齐、着色、分配、释放和回收,如果要频繁的创建很多相同类型的对象,那么就应该考虑使用 slab 高速缓存。
用户空间能负担起非常大的栈,而且栈空间还能动态增长,但内核却不能这么奢侈,内核栈小而且固定。当给每个进程分配一个固定大小的栈后,不但可以减小内存的消耗,而且内核也无需负担太重的栈管理任务。