VMA操作
应用进程申请内存空间通常使用malloc或者mmap来完成。我们知道在ARM32架构中0~3G的地址空间是分配给用户进程地址空间的。为了能在系统中(宏观上)同时运行多个进程,互不干扰操作系统想了一个办法,就是基于某一时刻只能运行一个进程的客观事实,系统为每个进程都准备了一套0~3G的虚拟地址空间,他们可以以各自特定的规则映射到物理空间。此刻哪儿个进程在运行就使用哪儿个进程的地址空间及映射关系,再加上写时分配机制的加成。这便有效的解决了进程在宏观上同时运行而绝不会互相干扰的问题。
综上所述,用户进程要申请到内存空间,首先要申请到一块合适的虚拟地址空间供物理地址映射。内核中使用VMA来表示这块虚拟地址空间单元。所以每个进程核心数据结构中都有一个管理VMA的数据结构struct vm_area_struct。
struct vm_area_struct数据结构
![6e2dc8ffe49a035c54f8567fdffe1e1f.png](https://i-blog.csdnimg.cn/blog_migrate/0ed4c8f6eefadf0c7c2ca10e3d64191f.jpeg)
struct vm_area_struct数据结构1
![583eb8fcd0156f939527ca4d248cd729.png](https://i-blog.csdnimg.cn/blog_migrate/44a2caec2ed830fcd602706dc3726f0c.jpeg)
struct vm_area_struct数据结构2
![666f38bb2a11bdaff0c1e3f4a6295ab5.png](https://i-blog.csdnimg.cn/blog_migrate/5f23eec5f54d174640c0d061dac35576.jpeg)
struct vm_area_struct数据结构3
- vm_start和vm_end:指定VMA进程地址空间的起始结束地址
- vm_next和vm_prev:VMA连成一个链表
- vm_rb:VMA作为一个节点加入红黑树,进程的struct mm_struct都有一颗红黑树mm->mm_rb管理这些节点
- vm:指向该VMA所属进程struct mm_struct
- vm_page_prot:VMA的访问权限
- vm_flags:描述VMA的标志
- anon_vma_chain和anon_vma:用于管理RMAP反向映射
- vm_ops:指向方法的集合,操作VMA
- vm_pgoff:指定文件映射的偏移量,这个变量的单位是页。对于匿名页来说它的值是0或者vm_addr/PAGE_SIZE
- vm_file:指向file的实例,描述一个被映射的文件。
find_vma函数
查找满足如下2个条件之一的VMA:
- addr在VMA空间范围内的,vma->vm_start<=addr<=vma->vm_end;
- 距离addr最近并且vma->vm_end大于addr的VMA
![7c5b278bb3bfb0877e4e4caae8828599.png](https://i-blog.csdnimg.cn/blog_migrate/52363508157c7fe00f15551c9bd78b82.jpeg)
find_vma函数
- 78行:在cache中看能否找到对应地址的vma,能找到直接返回
- 84~94行:在红黑树中查找合适的VMA
- 96行:更新cache,最后返回找到的vma
基于find_vma函数实现的还有find_vma_intersection用于查找与start_addr、end_addr重合的VMA;find_vma_prev返回查找到的VMA的前继节点
insert_vm_struct函数
![87cd76d92874c10e4717cdae93f77df2.png](https://i-blog.csdnimg.cn/blog_migrate/cb7bb4a0dcca414b49ab19d81f406d4c.jpeg)
insert_vm_struct函数
- 148行:find_vma_links查找插入位置,实现如下↓
- 155行:vma_link会调用__vma_link函数把VMA插入红黑树和链表,这个函数很简单不做说明。
find_vma_links函数
![5552f06a15dd1fbb2743af1654d489e7.png](https://i-blog.csdnimg.cn/blog_migrate/929d70fe0fea62d1eb09960b41b3216f.jpeg)
find_vma_links函数
- 165行:__rb_link:指向红黑树的根节点地址
- 167~180行:在红黑树中查找合适的位置。vma_tmp->vm_end <= addr的不符合寻找条件,继续往右子树探测,如果vma_tmp->vm_end > addr表示符合了基本条件,但是如果vma_tmp->vm_start
- 最后*rb_link指向待插入位置,*rb_parent表示待插入节点的父节点
malloc函数
![19b79d352877119bcf64077b76d1607d.png](https://i-blog.csdnimg.cn/blog_migrate/bf47e1012a16d2432a639f030551ed9d.jpeg)
用户空间内存空间布局
![f08093c30c47f47b362de8a27005c1dd.png](https://i-blog.csdnimg.cn/blog_migrate/98ca9d802684450b63ea289fe267e26a.jpeg)
malloc函数-1
![5a25717efa3aab0ad88ddf40b3778a86.png](https://i-blog.csdnimg.cn/blog_migrate/b468a6ac40c1b1fa7a20157b38512f4b.jpeg)
malloc函数-2
- 278行:基于用户空间的内存空间布局来理解该函数,min_brk表示堆的起始界限也就是数据段的结束位置。brk表示加上申请空间长度后临界线应该处在的位置,因为堆是从底部一直往上增长的,所以如果brk比堆起始位置还小肯定是不合法的。
- 284~285行:堆的临界线页对齐处理,也就是侧面说明malloc申请的空间也会是页对齐的。
- 290~294行:堆的新位置比旧位置还小那就是释放空间,调用do_munmap函数来释放空间。
- 297行:以老边界地址找是否存在重合的vma,如果存在则不需要查找了直接返回。
- 301行:do_brk函数申请分配内存,大小与页对齐
do_brk函数
![302f149de08c2cc432cd77111c8b64df.png](https://i-blog.csdnimg.cn/blog_migrate/51742bbb9624b10cb05cf79d1d4b8145.jpeg)
do_brk函数-1
![18c6bb1087b0a827942b83f01d340350.png](https://i-blog.csdnimg.cn/blog_migrate/c6422643b4e5d23dfbc3c4bd9ece80f0.jpeg)
do_brk函数-2
![bbcd80dcee85788773616c15960df521.png](https://i-blog.csdnimg.cn/blog_migrate/11cdf168f40ac64b3fc98e7fd92bbdfa.jpeg)
do_brk函数-3
- 336行:get_unmapped_area判读虚拟地址空间是否有足够的空间,返回一段没有被映射过的虚拟地址空间的起始地址,其中MAP_FIXED表示使用指定具体的虚拟地址对应空间
- 337行:mlock特性长度检测
- 347行:判断有没有可能合并附近的VMA,可以的或直接就可以跳转到out,否则只能新建VMA
- 354~366行:创建VMA,并对齐初始化
- 367行:将初始化好的VMA添加到红黑树和链表中vma_link这个函数前面介绍过。
回到malloc的系统调用brk函数中
- 306行:flags是否置位VM_LOCKED,如果置位则调用mm_populate马上分配物理内存并建立映射关系
mm_populate->__mm_populate函数
![d1a95d7c5ec497d9a785d9d446f7c79b.png](https://i-blog.csdnimg.cn/blog_migrate/a1c715a3b282cdcd28aa67068354ad8a.jpeg)
__mm_populate函数-1
![8777e09ad103039a5e8470be15a96e92.png](https://i-blog.csdnimg.cn/blog_migrate/f50fbe82a835c098bbd0932db4b75f64.jpeg)
__mm_populate函数-2
- 399行:先查找vma,没找到就直接返回
- 411行:__mlock_vma_pages_range为VMA分配物理内存,实现如下↓
__mlock_vma_pages_range函数
![b064a6e0e0396f5391ba94c13c354ed4.png](https://i-blog.csdnimg.cn/blog_migrate/4ce611d3d2ad4c3ded9e730b12e61523.jpeg)
__mlock_vma_pages_range函数
- __get_user_pages函数为进程地址空间申请物理内存并建立映射关系
__get_user_pages函数
![a07a977f52755bb5ffb9284b021f09c2.png](https://i-blog.csdnimg.cn/blog_migrate/578c405ce366dd40725b8f530aec82f2.jpeg)
__get_user_pages函数-1
![fd2f3b3ab77a56beb5c439e72e0b41cf.png](https://i-blog.csdnimg.cn/blog_migrate/8a792c46d81242ea594bf67c6b9bf783.jpeg)
__get_user_pages函数-3
![b241e80935dcd3f6494f19f2876db76a.png](https://i-blog.csdnimg.cn/blog_migrate/345a2bdc4eba4bec4b2dd50a13a56d25.jpeg)
__get_user_pages函数-4
- 参数说明:tsk进程核心数据结构;mm进程内存管理核心数据结构;start进程地址空间VMA的起始地址;nr_pages需要分配的pages;gup_flags分配掩码;pages物理页面的二级指针;vmas:进程地址空间VMA;nonblocking:IO操作是否阻塞
- find_extend_vma会调用find_vma查找VMA。如果vma_start大于start的地址会尝试扩增VMA的范围。如果前期要查找的VMA刚好在gate_vma中就使用gate_vma页面。
- cond_resched();当前进程是否需要被调度,内核经常在循环中增加这个判断优化系统延迟。
- follow_page_mask函数查看VMA中的物理页面是否已经分配了内存。函数实现细节如下↓
- 如果fllow_page_mask分配内存失败,调用faultin_page->handle_mm_fault人为触发缺页中断
follow_page_mask函数
![2d02dcd7012f64c63c633192b309cc00.png](https://i-blog.csdnimg.cn/blog_migrate/42b7fcaa393ca47a7dfca9c5703b2c08.jpeg)
follow_page_mask函数-1
![cc429bbcba71032719303b6e6b7df277.png](https://i-blog.csdnimg.cn/blog_migrate/7be0d8b9bfac8440c81e15af1ec50411.jpeg)
follow_page_mask函数-2
- 648行:通过mm和address得到当前进程页表对应的PGD目录项
- 651行:通过PGD和address得到当前进程页表对应的PUD目录项
- 658行:通过PUD和address得到当前进程页表对应的PMD目录项
- 670行:调用follow_page_pte检查PTE页面,其实现如下↓
follow_page_pte函数
![a9d1de710f56f672a09e90f9b3961838.png](https://i-blog.csdnimg.cn/blog_migrate/7454bd911e1be52c468d496adc3dfa30.jpeg)
follow_page_pte函数-1
- 684行:检查pmd合法性
- 686行:拿到对应的pte页面
- 688~700行:pte不在内存中的处理。没有定义FOLL_MIGRATION的错误返回;pte_to_swp_entry函数合并交换页面到主内存;migration_entry_wait等待合并交换页面完成,等待完毕重试。
![65c19ebdf0353dd99efbe169a2a672c6.png](https://i-blog.csdnimg.cn/blog_migrate/cee8f3e9574dc8ee643079f9a43073b1.jpeg)
follow_page_pte函数-2
- 703行:如果pte是只读页面,flag却含有可写权限,也报错返回。
- 708行:vm_normal_page函数根据pte返回normal mapping的page数据结构,实现细节如下↓
- 716~722行:对一些指定标志的page做一些相应的加工处理。对于FOLL_GET的页面会增加page的_count计数;对于FOLL_TOUCH且标记是可写的且非脏的页mark_page_accessed标记为活跃
![30308784700130d7ac147dd626a52794.png](https://i-blog.csdnimg.cn/blog_migrate/b54393c75f63816a3a85c04f9cba18c9.jpeg)
follow_page_pte函数-3
- 724~736行:对VM_LOCKED的page做一些加工处理,最后返回这个page
vm_normal_page函数
![a1b06449afdc43836fd9559d3b7915e2.png](https://i-blog.csdnimg.cn/blog_migrate/88859a93cdabbd3cbba0e8e86cd44903.jpeg)
vm_normal_page函数-1
- 755~765行:对于一些特殊映射的页面不返回page数据结构;对于定义了find_special_page函数指针,执行该指针继续检查;对于第0页和特殊映射的页面返回空;
![b672fbe62d74f7a39317d0ddc8e4aeac.png](https://i-blog.csdnimg.cn/blog_migrate/14b988c1f50b4100351203893b2090ee.jpeg)
vm_normal_page函数-2
- 768~783行:对于没有定义与前面类似HAVE_PTE_SPECIAL的情况。775行检测remap_pfn_range的情况。因为其pgoff通常指向第一个pfn;对于写时复制的页面正常返回page;对于0页不返回page结构;
综上:整个malloc的底层处理流程如下:
![b8ee434eade7ccb8754e9984e1e991df.png](https://i-blog.csdnimg.cn/blog_migrate/035b86f0d3c427d15264e734a1c10400.jpeg)
malloc底层处理流程