内存的基本概念
页
内核将物理页作为内存管理的基本单位
一般而言 32位体系结构支持页大小为4KB
64位体系结构支持页大小为8KB
内核中的<linux/mm_types.h>
struct page
结构体来描述系统中的每个物理页
仅描述物理页属性,与里面保存的数据无关,所以在数据交换过程中,数据可能会更换page
区
由于硬件的限制
-
一些只能被直接访问的内存
-
由于某些体系物理寻址范围大于虚拟寻址范围,故而有一些内存无法永久的映射在内核空间
故而,linux分了四个区
- ZONE_DMA 可以用来执行DMA(直接内存访问)操作
- ZONE_DMA32 只能被32位设备访问的DMA页
- ZONE_NORMAL 正常映射页
- ZONE_HIGHEM 高端内存,其中页无法永久映射到内核空间
linux 分区意义仅在于内存管理逻辑-按用途分配,对硬件无实际意义
不同的体系结构中,也会按实际意义调整不同的分区大小
其中在分配内存时,不能跨区分配
比如分配普通内存时,可以从NORMAL / DMA 二者选其一,但不可以一部分从NORMAL,一部分从DMA分配
内存概念图
其中在分页的基础上,对其分区,其中 HIGHMEM 区域出现的原因
-
内核需要的内存远远比实际的少,整个内核空间就1G,其中还要留有其他的区域
-
内核作为操作系统的核心,需要有管理到所有内存的方式
其中 DMA 和 NORMAL 决定了物理页面直接映射区的大小(二者之和),直接映射区的虚拟地址和物理地址映射为线性关系,即
物理地址 = 虚拟地址-3G
Linux虚拟空间划分时,将内核空间定义为3G-4G,用户空间为0G-3G,每个进程拥有独立的3G空间,共享1G内核空间
所以内核的虚拟起始地址 PAGE_OFFSET 为3G
在x86-32体系中,内核直接映射其896MB,超过896MB的即为HIGHMEN
在x86-64体系中,寻址范围2^64,略过地址处理细节,内核虚拟空间可以划到128T,其中直接映射物理内存为64T,故而内核已经可以直接管理到所有的内存了,其 HIGHMEN 空间为0
内存的分配
其中对页的分配跳过不再叙述,主要探讨内存块的分配
kmalloc
其中*zalloc
只是*malloc
基础上对其内存清理操作,不再单独探讨
void *kmalloc(unsigned size, gfp_t gfp)
//在kmalloc基础上对分配的内存清零
void *kzalloc(unsigned size, gfp_t gfp)
kmalloc 分配内存区在物理上连续,虚拟上也连续,失败返回NULL,成功返回内存地址
释放函数:
void kfree(void *p)
kfree kfree(NULL)安全,避免重复释放和释放非kamlloc分配的内存
vmalloc
void *vmalloc(unsigned long size)
//在vmalloc基础上对分配的内存清零
void *vzalloc(unsigned long size)
vmalloc 分配内存区在物理上不连续,虚拟上连续,失败返回NULL,成功返回内存地址
函数会睡眠,不可用于中断上下文
效率低下,由于物理上不连续,所以为了映射连续的虚拟地址,必须将分段将不连续的物理地址分段映射
释放函数:
void vfree(const void *addr)
分配器标志
typedef enum {
GFP_KERNEL,
GFP_ATOMIC,
__GFP_HIGHMEM,
__GFP_HIGH
} gfp_t;
一般而言只需要了解
GFP_KERNEL
允许睡眠/阻塞 ,因为在内存不够时它会去交换、刷新一些页,释放一些可用内存来给你,提高分配成功率
GFP_ATOMIC
不允许睡眠阻塞,常用中断上下文
总结
kmalloc()、vmalloc() 的共同特点是:
-
用于申请内核空间的内存
-
内存以字节为单位进行分配
-
所分配的内存虚拟地址上连续
kmalloc()、kzalloc()、vmalloc() 的区别是:
-
kzalloc 是强制清零的 kmalloc 操作;(以下描述不区分 kmalloc 和 kzalloc)
-
kmalloc 分配的内存大小有限制(128KB),而 vmalloc 没有限制
-
kmalloc 可以保证分配的内存物理地址是连续的,但是 vmalloc 不能保证
-
kmalloc 分配内存的过程可以是原子过程(使用 GFP_ATOMIC),而 vmalloc 分配内存时则可能产生阻塞
-
kmalloc 分配内存的开销小,因此 kmalloc 比 vmalloc 要快
内存管理算法
伙伴算法
随着系统的运行,不断的申请、释放内存,原本一页的内存可能会被分割成不等份的内存块被使用,当其中的一些被单独释放时,也就出现了所谓的内存碎片
当我需要一个连续的大内存时,因为内存碎片化,可能出现内存足够,但是分配失败的结果
故而引入伙伴算法buddy system
,将所有的空闲页分为11个链表,其包含的size为1、2、4、8、16、32、64、128、256、512、1024
例子说明:
假设要请求一个256 个页框的块(即1MB)。
算法先在256 个页框的链表中检查是否有一个空闲块。如果没有这样的块,算法会查找下一个更大的页块,也就是,在512 个页框的链表中找一个空闲块。
如果存在这样的块,内核就把256 的页框分成两等份,一半用作满足请求,另一半插入到256 个页框的链表中。
如果在512 个页框的块链表中也没找到空闲块,就继续找更大的块 —— 1024个页框的块。如果这样的块存在,内核把1024个页框块的256 个页框用作请求,然后从剩余的768 个页框中拿512个插入到512个页框的链表中,再把最后的256个插入到256个页框的链表中
如果1024个页框的链表还是空的,算法就放弃并发出错信号
以上过程的逆过程就是页框块的释放过程,也是该算法名字的由来。内核试图把大小为b的一对空闲伙伴块合并为一个大小为2b的单独块。满足以下条件的两个块称为伙伴:
- 两个块具有相同的大小,记作b。
- 它们的物理地址是连续的。
- 第一块的第一个页框的物理地址是2×b×212的倍数。
slub算法
频繁的分配和释放内存是内存管理的基本日常,但是分配需要映射内存,非常的低效,为了高效的使用内存,故而使用空闲链表
空闲链表包括了已经分配好的内存,当用户需要时,直接提供,当不需要时,无需释放,放回链表即可
但是当内存紧缩时,内核无法通知每个空闲链表释放出需要的内存,故而提出slub算法,由内核控制的空闲链表,一个或者多个slab构成了类似高速缓存的结构
kmalloc_cache
/ \
slab slab
/ \ / \
obj obj obj obj
总结
伙伴算法以页为单位管理内存,但在大多数情况下,程序需要的并不是一整页,而是几个、几十个字节的小内存。于是需要另外一套系统来完成对小内存的管理,这就是slub系统。slub系统运行在伙伴系统之上,为内核提供小内存管理的功能
slub就相当于零售商,它向伙伴系统“批发购买”内存,然后在零售出去