<code>Read the fucking source code!</code> --By 鲁迅
<code>A picture is worth a thousand words.</code> --By 高尔基
说明:
Kernel版本:4.14
ARM64处理器,Contex-A53,双核
使用工具:Source Insight 3.5, Visio
在之前的系列文章中,分析到了<code>Buddy System</code>的页框分配,<code>Slub分配器</code>的小块内存对象分配,这些分配的地址都是物理内存连续的。当内存碎片后,连续物理内存的分配就会变得困难,可以使用<code>vmap</code>机制,将不连续的物理内存页框映射到连续的虚拟地址空间中。<code>vmalloc</code>的分配就是基于这个机制来实现的。
还记得下边这张图吗?

<code>vmap/vmalloc</code>的区域就是在<code>VMALLOC_START ~ VMALLOC_END</code>之间。
开启探索之旅吧。
这两个数据结构比较简单,直接上代码:
<code>struct vmap_area</code>用于描述一段虚拟地址的区域,从结构体中<code>va_start/va_end</code>也能看出来。同时该结构体会通过<code>rb_node</code>挂在红黑树上,通过<code>list</code>挂在链表上。
<code>struct vmap_area</code>中<code>vm</code>字段是<code>struct vm_struct</code>结构,用于管理虚拟地址和物理页之间的映射关系,可以将<code>struct vm_struct</code>构成一个链表,维护多段映射。
关系如下图:
红黑树,本质上是一种二叉查找树,它在二叉查找树的基础上增加了着色相关的性质,提升了红黑树在查找,插入,删除时的效率。在红黑树中,节点已经进行排序,对于每个节点,左侧的的元素都在节点之前,右侧的元素都在节点之后。
红黑树必须满足以下四条规则:
每个节点不是红就是黑;
红黑树的根必须是黑;
红节点的子节点必须为黑;
从节点到子节点的每个路径都包含相同数量的黑节点,统计黑节点个数时,空指针也算黑节点;
定义如下:
由于内核会频繁的进行<code>vmap_area</code>的查找,红黑树的引入就是为了解决当查找数量非常多时效率低下的问题,在红黑树中,搜索元素,插入,删除等操作,都会变得非常高效。至于红黑树的算法操作,本文就不再深入分析,知道它的用途即可。
<code>vmap</code>函数,完成的工作是,在<code>vmalloc</code>虚拟地址空间中找到一个空闲区域,然后将<code>page页面数组</code>对应的物理内存映射到该区域,最终返回映射的虚拟起始地址。
整体流程如下:
操作流程比较简单,来一个样例分析,就清晰明了了:
<code>vmap</code>调用中,关键函数为<code>alloc_vmap_area</code>,它先通过<code>vmap_area_root</code>二叉树来查找第一个区域<code>first vm_area</code>,然后根据这个<code>first vm_area</code>去查找<code>vmap_area_list</code>链表中满足大小的空间区域。
在<code>alloc_vmap_area</code>函数中,有几个全局的变量:
用于缓存上一次分配成功的<code>vmap_area</code>,其中<code>cached_hole_size</code>用于记录缓存<code>vmap_area</code>对应区域之前的空洞的大小。缓存机制当然也是为了提高分配的效率。
<code>vunmap</code>执行的是跟<code>vmap</code>相反的过程:从<code>vmap_area_root/vmap_area_list</code>中查找<code>vmap_area</code>区域,取消页表映射,再从<code>vmap_area_root/vmap_area_list</code>中删除掉<code>vmap_area</code>,页面返还给伙伴系统等。由于映射关系有改动,因此还需要进行TLB的刷新,频繁的TLB刷新会降低性能,因此将其延迟进行处理,因此称为<code>lazy tlb</code>。
来看看逆过程的流程:
<code>vmalloc</code>用于分配一个大的连续虚拟地址空间,该空间在物理上不连续的,因此也就不能用作DMA缓冲区。<code>vmalloc</code>分配的线性地址区域,在文章开头的图片中也描述了:<code>VMALLOC_START ~ VMALLOC_END</code>。
直接分析调用流程:
从过程中可以看出,<code>vmalloc</code>和<code>vmap</code>的操作,大部分的逻辑操作是一样的,比如从<code>VMALLOC_START ~ VMALLOC_END</code>区域之间查找并分配<code>vmap_area</code>, 比如对虚拟地址和物理页框进行映射关系的建立。不同之处,在于<code>vmap</code>建立映射时,<code>page</code>是函数传入进来的,而<code>vmalloc</code>是通过调用<code>alloc_page</code>接口向Buddy System申请分配的。
<code>vmalloc VS kmalloc</code>
到现在,我们应该能清楚<code>vmalloc</code>和<code>kmalloc</code>的差异了吧,<code>kmalloc</code>会根据申请的大小来选择基于<code>slub分配器</code>或者基于<code>Buddy System</code>来申请连续的物理内存。而<code>vmalloc</code>则是通过<code>alloc_page</code>申请<code>order = 0</code>的页面,再映射到连续的虚拟空间中,物理地址不连续,此外<code>vmalloc</code>可以休眠,不应在中断处理程序中使用。
与<code>vmalloc</code>相比,<code>kmalloc</code>使用<code>ZONE_DMA和ZONE_NORMAL</code>空间,性能更快,缺点是连续物理内存空间的分配容易带来碎片问题,让碎片的管理变得困难。
直接上代码:
如果在中断上下文中,则推迟释放,否则直接调用<code>__vunmap</code>,所以它的逻辑基本和<code>vunmap</code>一致,不再赘述了。
作者:LoyenWang
出处:https://www.cnblogs.com/LoyenWang/
公众号:<b>LoyenWang</b>
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任