文章目录
1. 页
- 内核把物理页作为内存管理的基本单位。
- 大多数32为体系结构支持4KB的页,64位支持8KB的页。
- 内核用struct page结构表示系统中的物理页:
flags域用来存放页的状态,是否页是不是脏的,是否被锁定在内存中等
_count域存页的引用计数器,为-1时,表明没有引用这一页。
virtual域是页的虚拟地址。
2. 区
- 内核使用区对具有相似特性的页进行分组。
区名 | 描述 |
---|---|
ZONE_DMA | 这个区的页能用来执行DMA |
ZONE_DNA32 | 和ZONE_DMA类似,但只能被32位设备访问 |
ZONE_NORMAL | 这个区包含的都是能正常映射的页 |
ZONE_HIGHEM | 这个区包含“高端内存”,其中的页不能永久地映射到内核地址空间 |
- lock域是一个自旋锁,防止该结构被并发访问。
- watermark数组持有该区的最小值、最低和最高水位值。
- name域是一个以NULL结束的字符串表示这个区的名字,“DMA”、“Normal”和“HighMem”。
3. 获得页
所有接口都是以页为单位分配内存。
struct page * alloc_pages(gfp_t gfp_mask, unsigned int order) //分配2^order(1<<order)个连续的物理页,返回第一个页的page结构体,出错返回NULL。
把给定的页转换成它的逻辑地址:
void * page_address(struct page *page) //返回给定物理页当前所在的逻辑地址。
与alloc_pages()类似的函数:
unsigned long __get_free_pages(gfp_t gfp_mask, ungigned int order) //直接放回第一个页的逻辑地址。
只需要一页:
struct page * alloc_page(gfp_t gfp_mask)
unsigned long __get_free_page(gfp_t gfp_mask)
3.1 获得填充为0的页
unsigned long get_zeroed_page(unsigned int gfp_mask)
3.2 释放页
void __free_pages(struct page *page, unsigned int order)
void free_pages(unsigned long addr, unsigned int order)
void __free_pages(unsigned long addr)
#define GFP_KERNEL(__GFP_WAIT | __GFP_IO | __GFP_FS)
__GFP_WAIT : 缺内存页的时候可以睡眠;
__GFP_IO : 允许启动磁盘IO;
__GFP_FS : 允许启动文件系统IO。
4. kmalloc():物理地址和虚拟地址都连续
- kmalloc()可以获得以字节为单位的一块内核内存。分配的内存在物理上连续的。
void * kmalloc(size_t size, gfp_t flags) //返回至少有size大小的内存块指针。flags可以为GFP_KERNEL
4.1 gfp_mask标志
三类:行为修饰符、区修饰符及类型。
行为修饰符:表示内核应当如何分配所需的内存。
区修饰符:表示内存区应当从何处分配。
不能给__get_free_pages()或kalloc()指定ZONE_HIGHMEM,因为返回逻辑地址,而不是page。只有alloc_pages()才可以分配高端内存。
类型标志:指定所需的行为和区描述符。
4.2 kfree()
void kfree(const void *ptr)
kfree(NULL)是安全的。
5. vmalloc():虚拟地址连续,物理地址地址无须连续
- vmalloc()分配的内存虚拟地址是连续的,物理地址则无须连续。
- kmalloc()分配的物理地址和虚拟地址都是连续的。
- 用户空间malloc()返回的页在进程的虚拟地址空间内是连续的,但是不保证物理RAM中也是连续的。
void * vmalloc(unsigned long size) //返回大小至少为size的逻辑连续的内存区。
````void vfree(const void *addr) //释放通过vmalloc()所获得到的内存。```
6. slab层
slab分配器是基于对象进行管理的,所谓的对象就是内核中的数据结构(例如:task_struct,file_struct 等)。相同类型的对象归为一类,每当要申请这样一个对象时,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免内部碎片。slab分配器并不丢弃已经分配的对象,而是释放并把它们保存在内存中。slab分配对象时,会使用最近释放的对象的内存块,因此其驻留在cpu高速缓存中的概率会大大提高。
简要分析下这个图:kmem_cache是一个cache_chain的链表,描述了一个高速缓存,每个高速缓存包含了一个slabs的列表,这通常是一段连续的内存块。存在3种slab:slabs_full(完全分配的slab),slabs_partial(部分分配的slab),slabs_empty(空slab,或者没有对象被分配)。slab是slab分配器的最小单位,在实现上一个slab有一个货多个连续的物理页组成(通常只有一页)。单个slab可以在slab链表之间移动,例如如果一个半满slab被分配了对象后变满了,就要从slabs_partial中被删除,同时插入到slabs_full中去。
举例说明:如果有一个名叫inode_cachep的struct kmem_cache节点,它存放了一些inode对象。当内核请求分配一个新的inode对象时,slab分配器就开始工作了:
- 首先要查看inode_cachep的slabs_partial链表,如果slabs_partial非空,就从中选中一个slab,返回一个指向已分配但未使用的inode结构的指针。完事之后,如果这个slab满了,就把它从slabs_partial中删除,插入到slabs_full中去,结束;
- 如果slabs_partial为空,也就是没有半满的slab,就会到slabs_empty中寻找。如果slabs_empty非空,就选中一个slab,返回一个指向已分配但未使用的inode结构的指针,然后将这个slab从slabs_empty中删除,插入到slabs_partial(或者slab_full)中去,结束;
- 如果slabs_empty也为空,那么没办法,cache内存已经不足,只能新创建一个slab了。
https://www.cnblogs.com/wangzahngjun/p/4977425.html
slab分配器扮演了通用数据结构缓存层的角色。
6.1 slab层的设计
slab层把不同的对象划分为所谓告诉缓存组,其中每个缓存组都存放不同类型的对象,每种对象类型对应一个告诉缓存。
slab由一个或多个物理上连续的页组成。每个高速缓存由多个slab组成。
每个slab处于状态:满、部分满或空。
6.2 slab分配器的接口
一个新的高速缓存创建函数:
name:高速缓存的名字;
size:高速缓存中每个元素的大小;
align:slab内第一个对象的偏移,确保在页内进行特定的对齐,0表示标准对齐;
flags:可选设置项,用来控制高速缓存的行为,0表示没有特殊行为。
撤销高速缓存:
- 从缓存中分配:
创建高速缓存后,调用:
void * kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags) //返回一个指向对象的指针,flags的值传递给_get_free_pages()。
释放一个对象:
void kmem_cache_free(struct kmem_cache *cachep, void *objp) //将cachep中的objp对象标志空。
着色:https://www.cnblogs.com/linhaostudy/p/13184704.html
7. 在栈上静态分配
32位和64位栈一般为2页,为8KB和16KB。
内核栈,中断栈
8. 高端内存的映射
高端内存中的页不能永久映射到内核地址空间上。用alloc_pages()函数以__GFP_HIGHMEM标志获得到的页不可能有逻辑地址。
8.1 永久映射
映射一个给定的page结构到内核地址空间:
void *kmap(struct page *page) //page如果是低端内存则单纯的返回页的虚拟地址,如果是高端内存则建立永久映射地址,该函数可以睡眠,只能用于进程上下文。
解除映射:
void kunmap(struct page *page)
8.2 临时映射
当必须创建一个映射而当前的上下文又不能睡眠时,内核提供了临时映射(原子映射)。内核可以原子地把高端内存中的一个页映射到某个保留的映射中,因此临时映射可以用在不能睡眠的地方,比如中断处理程序中。
建立临时映射函数:
void * kmap_atomic(struct page *page, enum km_type type)
这个函数不会阻塞,可以用在中断上下文和其他不能重新调度的地方,也禁止内核抢占。
取消映射:
void kunmap_atomic(void *kvaddr, enum km_type type)
9. 每个CPU的分配
每个CPU的数据存放在一个数组中。
10. 新的每个CPU接口
10.1 编译是的每个CPU数据
10.2 运行时的每个CPU数据
11. 使用每个CPU的原因
- 减少数据锁定,每个处理器访问每个CPU数据的逻辑,不需要任何锁;
- 减少缓存失效。
12. 分配函数的选择
- 连续物理页,用kmalloc();
- 高端内存用alloc_pages();
- 不需要连续的物理页,vmalloc()
- 创建和撤销很多大的数据结构,用slab高速缓存,