Linux内存管理原理及应用总结

内存的基本概念

内核将物理页作为内存管理的基本单位

一般而言 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就相当于零售商,它向伙伴系统“批发购买”内存,然后在零售出去

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值