页
1、内核把物理页作为内存管理的基本单位(处理器的最小可寻址单位一般为字),内存管理单元(MMU,管理内存并把虚拟地址转换为物理地址的硬件)通常以页为单位进行处理
2、不同体系结构支持的页大小不尽相同,有些还支持多种,大多数32体系结构支持4KB的页,64位体系结构一般支持8KB页
3、内核用struct page结构表示系统中的每个物理页,该结构对页的描述是这暂时的(被交换、临时映射),内核仅仅用这个结构描述当前时刻在相关的物理页中存放的东西,这个结构目的用于描述物理内存本身,并不包含其中的数据,系统中的每个物理页都要分配一个这样的结构体
区
由于页位于内存中特定物理地址上而不能用于一些特定任务,内核把页划分为不同的区,使用区对具有相似特性的页进行分组
- 一些硬件只能用某些特定的内存地址来执行DMA(直接内存访问)
- 一些体系结构的内存的物理寻址范围比虚拟寻址范围大得多,就有一些内存不能永久地映射到内核空间上
Linux主要使用四种区
- ZONE_DMA 包含页用于执行DMA操作
- ZONE_DMA32 同上,但只能被32位设备访问
- ZONE_NORMAL 包含的页都能正常映射
- ZONE_HIGHMEM 高端内存,包含页不能永久地映射到内核地址空间
区的实际使用和分布与体系结构相关,可按特定的体系结构规定进行处理。x86-32上ZONE_NORMAL的范围16MB~896MB,x86-64没有ZONE_HIGHMEM区
Linux把系统的页划分为区,形成不同的内存池,就可以根据用途进行分配了,区的划分没有任何物理意义,只不过是内核为了管理页而采取得一种逻辑分组,每个区都用struct zone表示
内核分配页内存
以下只写了函数名
alloc_pages(); //分配连续的物理页
page_address(); //把给定页转为逻辑地址
__get_free_pages(); //返回第一页的逻辑地址
alloc_page(); //分配一页
__get_free_page(); //返回页逻辑地址
get_zeroed_page(); //返回填充0的页
_free_pages(); //释放页需要谨慎,用错可能导致系统崩溃
free_pages();
free_page();
内核分配可能失败,因此程序开始时先进行内存分配是很有意义的
内核分配字节内存
kmalloc(); //获取以字节为单位的一块内核内存,所分配内存区在物理上是连续的
kfree();
vmalloc();//分配的物理地址无须连续,只确保页在虚拟地址空间内连续的,性能上的考虑,只有在不得以的时候才会使用,比如获取大块内存
vfree();//这两个函数都可睡眠,小心使用
一般只有硬件设备需要用到物理地址连续的内存,软件使用的内存块就可以使用只有虚拟地址连续的内存块
gfp_mask标志(分配器标志)
可分为三类
- 行为修饰符 内核应当如何分配所需的内存
- 区修饰符 从哪儿分配内存
- 类型 组合了行为修饰符和区修饰符,将各种可能组合归纳为不同类型
一般使用类型标志
- 内核中最常用的类型标志是GFP_KERNEL 这种分配可能会引起睡眠,使用普通优先级,由于对内核如何请求的内存没有任何约束,分配成功可能性很高
- 类型标志GFP_ATOMIC表示不能睡眠的内存分配,满足调用者获取内存的请求将会受到很严格的限制,分配成功的机会较小,在中断处理程序、软中断、tasklet等不能睡眠时,只能选择它
slab层(slab分配器)
为了便于数据的频繁分配和回收,常会用到空闲链表,它包含可供使用的,已经分配好的数据结构块,空闲链表相当于对象高速缓存
slab分配器的几个基本原则:
- 频繁使用的数据结构应当缓存它们
- 频繁分配和收回会有内存碎片问题,空间链表的缓存会连续地存放
- 回收的对象可以立即投入下一次分配
- 分配器可以根据对象大小、页大小、总的高速缓存大小等概念做出决策
- 让部分缓存专属某个处理,可以不加SMP锁进行处理
- 分配器与NUMA相关,可以从相同的内存节点为请求者进行分配
- 对存放的对象进行着色,防止多个对象映射到相同的高速缓存行
slab层把不同的对象划分为高速缓存组,每个高速缓存组都存放不同类型的对象,每个对象类型对应一个高速缓存
slab由一个或多个物理上连续的页组成,每个高速缓存可以由多个slab组成,每个slab处于三种状态之一:,满、部分满、空,每个高速缓存使用kmem_cache结构表示,每个slab使用struct slab描述
当创建了一个高速缓存后,slab层就是一个专用的分配器,可以为具体的对象类型进行分配,slab层负责内存紧缺情况下所有底层的对齐、着色、分配、释放、回收等,如果需要频繁创建很多相同类型的对象,可以使用slab高速缓存实现
在栈上的静态分配
内核栈小而且固定,1页大小或2页大小,与体系结构和配置选项有关,内核没有在管理内核栈上做足工作,当栈溢出时,是很危险的,进行动态分配是更好的选择
高端内存映射
在高端内存页不能永久地映射到内核地址空间上
kmap(); //在高端内存或低端内存上都能用,如果页位于高端内存,会建立一个永久映射,函数可睡眠,只能用于进程上下文中
kunmap(); //解除映射
kmap_atomic(); //当必须创建一个映射到当前上下文又不能睡眠时,需要使用临时映射(原子映射)
kunmap_atomic(); //不会阻塞
每个CPU数据
支持SMP的现代操作系统使用每个CPU上的数据,对于给定的处理器其数据是唯一的,需要确保本地处理器只会访问它的唯一数据,系统本身不保证
内核抢占会引起代码被其它处理器抢占并调用,另一个任务抢占了代码,发生并发访问等问题,这时如果通过获取当前处理器号,对内核抢占进行禁止,重新激活当前处理器号,使用这种方法就可以保护数据安全
使用每个CPU数据的好处:(需要禁止内核抢占)
- 减少数据的锁定
- 大大减少缓存失效
如何选择内存分配方式
- 需要连续的物理页 使用kmalloc();
- 高端内存进行分配 使用alloc_pages();,返回页结构指针,而不是逻辑地址的指针,因为高端内存可能还未映射
- 不需要物理连续,仅需要虚拟地址上连续的页,使用vmalloc();
- 创建和撤销很多大的数据结构,使用slab高速缓存