Read the fucking source code!
--By 鲁迅A picture is worth a thousand words.
--By 高尔基说明:node
在以前的系列文章中,分析到了Buddy System
的页框分配,Slub分配器
的小块内存对象分配,这些分配的地址都是物理内存连续的。当内存碎片后,连续物理内存的分配就会变得困难,可使用vmap
机制,将不连续的物理内存页框映射到连续的虚拟地址空间中。vmalloc
的分配就是基于这个机制来实现的。算法
还记得下边这张图吗?
数组
vmap/vmalloc
的区域就是在VMALLOC_START ~ VMALLOC_END
之间。缓存
开启探索之旅吧。数据结构
这两个数据结构比较简单,直接上代码:less
struct vm_struct { struct vm_struct *next; void *addr; unsigned long size; unsigned long flags; struct page **pages; unsigned int nr_pages; phys_addr_t phys_addr; const void *caller; }; struct vmap_area { unsigned long va_start; unsigned long va_end; unsigned long flags; struct rb_node rb_node; /* address sorted rbtree */ struct list_head list; /* address sorted list */ struct llist_node purge_list; /* "lazy purge" list */ struct vm_struct *vm; struct rcu_head rcu_head; };
struct vmap_area
用于描述一段虚拟地址的区域,从结构体中va_start/va_end
也能看出来。同时该结构体会经过rb_node
挂在红黑树上,经过list
挂在链表上。
struct vmap_area
中vm
字段是struct vm_struct
结构,用于管理虚拟地址和物理页之间的映射关系,能够将struct vm_struct
构成一个链表,维护多段映射。函数
关系以下图:
工具
红黑树,本质上是一种二叉查找树,它在二叉查找树的基础上增长了着色相关的性质,提高了红黑树在查找,插入,删除时的效率。在红黑树中,节点已经进行排序,对于每一个节点,左侧的的元素都在节点以前,右侧的元素都在节点以后。
红黑树必须知足如下四条规则:性能
定义以下:3d
struct rb_node { unsigned long __rb_parent_color; struct rb_node *rb_right; struct rb_node *rb_left; } __attribute__((aligned(sizeof(long)))); /* The alignment might seem pointless, but allegedly CRIS needs it */
因为内核会频繁的进行vmap_area
的查找,红黑树的引入就是为了解决当查找数量很是多时效率低下的问题,在红黑树中,搜索元素,插入,删除等操做,都会变得很是高效。至于红黑树的算法操做,本文就再也不深刻分析,知道它的用途便可。
vmap
函数,完成的工做是,在vmalloc
虚拟地址空间中找到一个空闲区域,而后将page页面数组
对应的物理内存映射到该区域,最终返回映射的虚拟起始地址。
总体流程以下:
操做流程比较简单,来一个样例分析,就清晰明了了:
vmap
调用中,关键函数为alloc_vmap_area
,它先经过vmap_area_root
二叉树来查找第一个区域first vm_area
,而后根据这个first vm_area
去查找vmap_area_list
链表中知足大小的空间区域。
在alloc_vmap_area
函数中,有几个全局的变量:
static struct rb_node *free_vmap_cache; static unsigned long cached_hole_size; static unsigned long cached_vstart; static unsigned long cached_align;
用于缓存上一次分配成功的vmap_area
,其中cached_hole_size
用于记录缓存vmap_area
对应区域以前的空洞的大小。缓存机制固然也是为了提升分配的效率。
vunmap
执行的是跟vmap
相反的过程:从vmap_area_root/vmap_area_list
中查找vmap_area
区域,取消页表映射,再从vmap_area_root/vmap_area_list
中删除掉vmap_area
,页面返还给伙伴系统等。因为映射关系有改动,所以还须要进行TLB的刷新,频繁的TLB刷新会下降性能,所以将其延迟进行处理,所以称为lazy tlb
。
来看看逆过程的流程:
vmalloc
用于分配一个大的连续虚拟地址空间,该空间在物理上不连续的,所以也就不能用做DMA缓冲区。vmalloc
分配的线性地址区域,在文章开头的图片中也描述了:VMALLOC_START ~ VMALLOC_END
。
直接分析调用流程:
从过程当中能够看出,vmalloc
和vmap
的操做,大部分的逻辑操做是同样的,好比从VMALLOC_START ~ VMALLOC_END
区域之间查找并分配vmap_area
, 好比对虚拟地址和物理页框进行映射关系的创建。不一样之处,在于vmap
创建映射时,page
是函数传入进来的,而vmalloc
是经过调用alloc_page
接口向Buddy System申请分配的。
vmalloc VS kmalloc
vmalloc
和kmalloc
的差别了吧,kmalloc
会根据申请的大小来选择基于slub分配器
或者基于Buddy System
来申请连续的物理内存。而vmalloc
则是经过alloc_page
申请order = 0
的页面,再映射到连续的虚拟空间中,物理地址不连续,此外vmalloc
能够休眠,不该在中断处理程序中使用。vmalloc
相比,kmalloc
使用ZONE_DMA和ZONE_NORMAL
空间,性能更快,缺点是连续物理内存空间的分配容易带来碎片问题,让碎片的管理变得困难。直接上代码:
void vfree(const void *addr) { BUG_ON(in_nmi()); kmemleak_free(addr); if (!addr) return; if (unlikely(in_interrupt())) __vfree_deferred(addr); else __vunmap(addr, 1); }
若是在中断上下文中,则推迟释放,不然直接调用__vunmap
,因此它的逻辑基本和vunmap
一致,再也不赘述了。