vmalloc原理与实现
在 Linux 系统中的每个进程都有独立 4GB 内存空间,而 Linux 把这 4GB 内存空间划分为用户内存空间(0 ~ 3GB)和内核内存空间(3GB ~ 4GB),而内核内存空间由划分为直接内存映射区和动态内存映射区(vmalloc区)。
直接内存映射区从 3GB 开始到 3GB+896MB 处结束,直接内存映射区的特点就是物理地址与虚拟地址的关系为:虚拟地址 = 物理地址 + 3GB。而动态内存映射区不能通过这种简单的关系关联,而是需要访问动态内存映射区时,由内核动态申请物理内存并且映射到动态内存映射区中。下图是动态内存映射区在内存空间的位置:

为什么需要vmalloc区
由于直接内存映射区(3GB ~ 3GB+896MB)是直接映射到物理地址(0 ~ 896MB)的,所以内核不能通过直接内存映射区使用到超过 896MB 之外的物理内存。这时候就需要提供一个机制能够让内核使用 896MB 之外的物理内存,所以 Linux 就实现了一个 vmalloc 机制。vmalloc 机制的目的是在内核内存空间提供一个内存区,能够让这个内存区映射到 896MB 之外的物理内存。如下图:

那么什么时候使用 vmalloc 呢?一般来说,如果要申请大块的内存就可以用vmalloc。
【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)


vmalloc实现
可以通过 vmalloc() 函数向内核申请一块内存,其原型如下:
__vmalloc() 函数主要工作有两点:
调用 get_vm_area() 函数申请一个合法的虚拟内存地址。
调用 vmalloc_area_pages() 函数把虚拟内存地址映射到物理内存地址。
接下来,我们看看 get_vm_area() 函数的实现,代码如下:
get_vm_area() 函数比较简单,首先申请一个类型为 vm_struct 的结构 area 用于保存申请到的虚拟内存地址。然后查找可用的虚拟内存地址,如果找到,就把虚拟内存到虚拟内存地址保存到 area 变量中。最后把 area 连接到 vmalloc 虚拟内存地址管理链表 vmlist 中。vmlist 链表最终结果如下图:

申请到虚拟内存地址后,__vmalloc() 函数会调用 vmalloc_area_pages() 函数来对虚拟内存地址与物理内存地址进行映射。
我们知道,映射过程就是对进程的 页表 进行映射。但每个进程都有一个独立 页表(内核线程除外),并且我们知道内核空间是所有进程共享的,那么就有个问题:如果只映射当前进程 页表 的内核空间,那么怎么同步到其他进程的内核空间呢?
为了解决内核空间同步问题,Linux 并不是直接对当前进程的内核空间映射的,而是对 init 进程的内核空间(init_mm)进行映射,我们来看看 vmalloc_area_pages() 函数的实现:
从上面代码可以看出,vmalloc_area_pages() 函数映射的主体是 init 进程的内存空间。因为映射的 init 进程的内存空间,所以当前进程访问 vmalloc() 函数申请的内存时,由于没有对虚拟内存进行映射,所以会发生 缺页异常 而触发内核调用 do_page_fault() 函数来修复。我们看看 do_page_fault() 函数对 vmalloc() 申请的内存异常处理:
上面的代码就是当进程访问 vmalloc() 函数申请到的内存时,发生 缺页异常 而进行的异常修复,主要的修复过程就是把 init 进程的 页表项 复制到当前进程的 页表项 中,这样就可以实现所有进程的内核内存地址空间同步。
