详解kmalloc、vmalloc

IA-32环境下的Linux对用户进程分配4G的虚拟地址空间,低3G是用户独立的空间,高1G是内核空间。用户空间的动态内存分配用malloc,而在设备驱动程序或者内核模块中动态开辟内存,不是用malloc,而是kmalloc ,vmalloc。释放内存用的是kfree,vfree,或free_pages. kmalloc函数返回的是虚拟地址(线性地址).

kmalloc – 内核分配内存

void * kmalloc (size_t size, int flags);
size 要分配内存的大小. 以字节为单位.
flags 要分配内存的类型.

kmalloc特殊之处在于它分配的内存是物理上连续的,这对于要进行DMA的设备十分重要.
这里写图片描述

注意kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用了。

kmalloc() 函数本身是基于 slab 实现的。slab 是为分配小内存提供的一种高效机制。但 slab 这种分配机制又不是独立的,它本身也是在页分配器的基础上来划分更细粒度的内存供调用者使用。也就是说系统先用页分配器分配以页为最小单位的连续物理地址,然后 kmalloc() 再在这上面根据调用者的需要进行切分。

下面看看kmalloc的调用过程
kmalloc()函数的实现是在 __do_kmalloc() 中,可以看到在 __do_kmalloc()代码里通过__find_general_cachep来在malloc_size结构体数组中查找已经缓存的slab内存块,这里的查找是找到第一个大于用户传进来的大小。如果找到了就调用了 kmem_cache_alloc()来分配一个 slab,把这块内存的没有分配的slab分配出去。如果没有找到,通过kmem_cache_create()函数建立专用缓冲区(新的slab),再分配。其实 kmem_cache_alloc() 等函数的实现也是调用了_cache_alloc()这个函数来分配新的 slab。我们按照 __cache_alloc()函数的调用路径一直跟踪下去会发现在 cache_grow() 函数中使用了 kmem_getpages()函数来分配一个物理页面,kmem_getpages() 函数中调用的alloc_pages_node() 最终是使用 __alloc_pages() 来返回一个struct page 结构,而这个结构正是系统用来描述物理页面的。

需要注意的是大家都知道kmalloc都是在slab机制上实现的,但是有没有发现slab是对不同对象建立专用kmem_cache_t的,那么kmalloc是如何通过大小来确定某一个对象的,如果有的对象大小一样怎么办,不同的对象初始化也不一样。其实这样理解是不对的,Linux下的slab有两种功能,一种是建立普通的缓存,一种是建立对象的缓存。kmalloc就是建立普通的缓存。大家可以看这张图中的最后一句话。《Linux内核设计与实现》和《Linux内核情景分析》
这里写图片描述

这里写图片描述

vmalloc – 内核分配内存

说vmalloc前先来看看这张图
这里写图片描述

由图可见,内核线性地址空间部分从PAGE_OFFSET(通常定义为3G)开始,为了将内核装入内存,从PAGE_OFFSET开始8M线性地址用来映射内核所在的物理内存地址(也可以说是内核所在虚拟地址是从PAGE_OFFSET开始的);接下来是mem_map数组,mem_map的起始线性地址与体系结构相关,比如对于UMA结构,由于从PAGE_OFFSET开始16M线性地址空间对应的16M物理地址空间是DMA区,mem_map数组通常开始于PAGE_OFFSET+16M的线性地址;从PAGE_OFFSET开始到VMALLOC_START – VMALLOC_OFFSET的线性地址空间直接映射到物理内存空间(一一对应影射,物理地址<==>线性地址-PAGE_OFFSET),这段区域的大小和机器实际拥有的物理内存大小有关,这儿VMALLOC_OFFSET在X86上为8M,主要用来防止越界错误;在内存比较小的系统上,余下的线性地址空间(还要再减去空白区即VMALLOC_OFFSET)被vmalloc()函数用来把不连续的物理地址空间映射到连续的线性地址空间上,在内存比较大的系统上,vmalloc()使用从VMALLOC_START到VMALLOC_END(也即PKMAP_BASE减去2页的空白页大小PAGE_SIZE(解释VMALLOC_END))的线性地址空间,此时余下的线性地址空间(还要再减去2页的空白区即VMALLOC_OFFSET)又可以分成2部分:第一部分从PKMAP_BASE到FIXADDR_START用来由kmap()函数来建立永久映射高端内存;第二部分,从FIXADDR_START到FIXADDR_TOP,这是一个固定大小的临时映射线性地址空间,(引用:Fixed virtual addresses are needed for subsystems that need to know the virtual address at compile time such as the APIC),在X86体系结构上,FIXADDR_TOP被静态定义为0xFFFFE000,此时这个固定大小空间结束于整个线性地址空间最后4K前面,该固定大小空间大小是在编译时计算出来并存储在__FIXADDR_SIZE变量中。

正是由于vmalloc()使用区、kmap()使用区及固定大小区(kmap_atomic()使用区)的存在才使ZONE_NORMAL区大小受到限制,由于内核在运行时需要这些函数,因此在线性地址空间中至少要VMALLOC_RESERVE大小的空间。VMALLOC_RESERVE的大小与体系结构相关,在X86上,VMALLOC_RESERVE定义为128M,这就是为什么ZONE_NORMAL大小通常是16M到896M的原因。

现在来仔细说说vmalloc,内核的线性地址空间把大于 896M 的内存称为高端内存区。对于这样的内存,无法在“内核直接映射空间”进行映射。因为“内核直接映射空间”最多只能从 3G 到 4G,只能直接映射 1G 物理内存,对于大于 1G 的物理内存,无能为力。实际上,“内核直接映射空间”也达不到 1G, 还得留点线性空间给“内核动态映射空间” 呢。因此,Linux 规定“内核直接映射空间” 最多映射 896M 物理内存。  
对于高端内存,可以通过 alloc_pages() 或者其它函数获得对应的 page,但是要想访问实际物理内存,还得把 page 转为线性地址才行(为什么?想想 MMU 是如何访问物理内存的),也就是说,我们需要为高端内存对应的 page 找一个线性空间,这个过程称为高端内存映射。

高端内存映射有三种 ,其中一种就是vmalloc(非连续物理内存分配区),还有一种就是永久映射区(通过 kmap(), 可以把一个 page 映射到这个空间来)。如果这块知识不知道的,可以看我这篇博文:Linux内存管理

内核给vmalloc维护了vm_struct这个结构体来实现映射
这里写图片描述

需要注意的是不要把vm_struct和用户空间的vm_area_struct搞混了 

vmalloc的数据结构

描述非连续区的数据结构为struct vm_struct,定义于include/linux/vmalloc.h中:

struct vm_struct {

          unsigned long flags;

          void * addr;

          unsigned long size;

          struct vm_struct * next;

};

struct vm_struct * vmlist;

非连续区组成一个单链表,链表第一个元素的地址存放在变量vmlist中。Addr域是内存区的起始地址;size是内存区的大小加4096(安全区的大小)。

2.创建一个非连续区的结构

函数get_vm_area()创建一个新的非连续区结构。

这个函数比较简单,就是在单链表中插入一个元素。其中调用了kmalloc()和kfree()函数,分别用来为vm_struct结构分配内存和释放所分配的内存。只是为vm_struct这个结构来分配释放内存,而不是对vm_struct描述的内存区域来分配和释放内存。别搞混了!

vmalloc和kmalloc、malloc的区别

1、kmalloc和vmalloc是分配的是内核的内存,malloc分配的是用户的内存
2、kmalloc保证分配的内存在物理上是连续的,内存只有在要被DMA访问的时候才需要物理上连续,malloc和vmalloc保证的是在虚拟地址空间上的连续

3、kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相对较大

4、vmalloc比kmalloc要慢。 尽管在某些情况下才需要物理上连续的内存块,但是很多内核代码都用kmalloc来获得内存,而不是vmalloc。这主要是出于性能的考虑。vmalloc函数为了把物理内存上不连续的页转换为虚拟地址空间上连续的页,必须专门建立页表项。糟糕的是,通过vmalloc获得的页必须一个个地进行映射,因为它们物理上是不连续的,这就会导致比直接内存映射大得多的TLB抖动,vmalloc仅在不得已时才会用–典型的就是为了获得大块内存时。

上面几点都是网上总结的我贴过来的,大家也都知道没什么意思。kmallo和vmalloc有我觉的特别重要的一点就是,kmalloc分配的内存是物理地址和线性地址直接转换的,而vmalloc就要像用户空间一样在该进程的页目录和页表中增加映射关系来转换物理地址和线性地址。这样就造成了一个问题kmalloc分配的都是直接映射区,这个区间是不能产生页面异常的,所以也就不存在物理页面的延迟分配原则了。

下面还有几个问题我自己也没有解决,大家可以一起讨论下。
1.kmalloc真的只能分配直接映射区内存吗?不可以分配高端内存区吗?
2.kmalloc是slab层上实现,那么可不可以用vmalloc分配一块高端内存区,缓存在slab中
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值