11 如何分配和释放虚拟内存
虚拟地址空间的分配和释放:
1、虚拟地址空间分配接口:
vma_new_vadrs 接口函数:进行参数检查,开始地址要和页面对齐,结束地址不超过整个虚拟地址空间;;接着调用虚拟地址空间分配的核心函数 vma_new_vadrs_core 函数;
vma_new_vadrs_core 核心函数:调用vma_find_kmvarsdsc 函数查找虚拟地址区间,virmemadrs_t 结构中的所有kmvarsdsc_t 结构,找出合适的虚拟地址区间;允许应用程序指定分配虚拟地址空间的开始地址,也可以由系统决定,但是应用程序指定的话,分配更容易失败,因为可能已经被占用了;
分配时查找虚拟地址区间:
vma_find_kmvarsdsc 函数:根据分配的开始地址和大小,在virmemadrs_t 结构中查找相应的 kmvarsdsc_t 结构;
该函数首先判断:分配的虚拟地址空间大小小于4KB不行;并且结束地址大于整个虚拟地址空间也不行;
然后判断当前的kmvarsdsc_t 结构是否可行;若不行,接着遍历virmemadrs_t 结构中所有的 kmvarsdsc_t 结构;这儿两步都是调用vma_find_kmvarsdsc_is_ok 函数做判断;
vma_find_kmvarsdsc_is_ok 函数:先检查当前的kmvarsdsc_t 结构是不是最后一个?
若不是最后一个,则先检查当前的kmvarsdsc_t 结构,接着获取下一个结构,判断参数START 为空 ,则由系统动态决定分配虚拟地址空间的开始地址,如果这个接结构的结束地址加上分配的大小 小于等于 下一个结构的开始地址就返回 这个结构;若参数START不为空,则比较应用指定分配的开始、结束地址是不是在当前和下一个结构之间;
若是最后一个,当START参数为空时,判断结束地址加上分配空间大小是不是小于整个虚拟地址空间;当START参数不为空时,比较应用指定的开始、结束地址是不是在当前和整个虚拟地址结束地址之间;
2、虚拟地址空间释放接口:
vma_del_vadrs 接口函数:对参数检查之后,调用核心函数 vma_del_vadrs_core ;
vma_del_vadrs_core 核心函数:首先检查虚拟地址空间的kmvarsdsc_t 结构,分四种情况:
第一种情况 要释放的虚拟地址空间正好等于查找的kmvarsdsc_t 结构,先进行脱链,然后删除kmvarsdsc_t 结构;
第二种情况 要释放的虚拟地址空间在查找的kmvarsdsc_t 结构的上半部分, 直接把查找的 kmvarsdsc_t 结构的开始地址设置为 释放虚拟地址的结束地址;
第三种情况 要释放的虚拟地址空间是在查找的kmvarsdsc_t 结构的下半部分 直接把查找到kmvarsdsc_t 结构的结束地址设置为释放虚拟地址空间的开始地址;
第四种情况 要释放的虚拟地址空间是在查找的kmvarsdsc_t 结构的中间, 要新建一个 kmvarsdsc_t 结构来处理释放虚拟地址空间之后的下半虚拟部分地址空间;让新的kmvarsdsc_t 结构指向查找的 kmvarsdsc_t 结构的后半部分为虚拟地址空间;和查找到的kmvarsdsc_t 结构保持一致;加入链表;并判断是否为最后一个kmvarsdsc_t 结构;
分配虚拟地址空间时,为了节约kmvarsdsc_t 结构占用的内存空间, 规定只要分配的虚拟地址空间上一个虚拟地址空间是连续且类型相同的,就借用上一个kmvarsdsc_t 结构,而不是重新分配一个kmvarsdsc-t 结构表示新分配的虚拟地址空间;假如每个应用每次分配一个页面的虚拟地址空间,不停的分配,每个空间对应一个kmvarsdsc_t 结构,物理内存很快将会被耗尽;
释放时查找的虚拟地址区间:
因为释放查找的要求不一样,所以不用分配虚拟地址空间时的那个查找函数;释放时仅仅需要保证,释放的虚拟地址空间的开始地址和结束地址,落在某一个kmvarsdsc_t结构表示的虚拟地址空间就可以;
vma_del_find_kmvarsdsc_t函数:参数curr 不为空,释放的虚拟地址空间落在当前 kmvarsdsc_t 结构表示的虚拟地址区间;否则,就遍历所有的kmvarsdsc_t 结构,查看是哪一个区间;
3、代码测试是否能访问:延迟内存分配技术
我们写一个测试代码,分配0x1000大小的虚拟地址空间,开始地址是0x5000;并在异常分配函数处作了打印处理;
测试发现,分配失败,发生缺页异常,因为MMU无法将这个虚拟地址转化为物理地址,CPU跳转到缺页异常的处理入口函数,开始执行代码,处理异常;我们只是分配了一个虚拟地址,就对它进行访问,所以才会缺页;我们并没有为这个虚拟地址空间分配任何物理内存页面,建立对应的MMU页表,我们可以这么做能解决问题,但是现实中往往是发生缺页异常了,才分配物理页面,建立对应的MMU页表;分配的虚拟地址空间,只有实际访问到了才分配对应的物理页面;;这种延迟内存分配技术在系统工程中非常有用;能最大限度的节约物理内存页面;
4、处理缺页异常:
缺页异常从kernel.asm文件中的 exc_page_fault 标号处开始,此处只是开始保存了CPU的上下文,然后调用了内核的通用异常分发器函数,最后由异常分发器函数调用不同的异常处理函数;
hal_fault_allocator异常分发器函数,调用异常处理接口krluserspace_accessfailed;
krluserspace_accessfailed接口获取当前进程,调用缺页异常处理接口vma_map_fairvardrs;
vma_map_fairvardrs缺页异常处理接口,对参数检查,进行缺页异常的核心处理函数 vma_map_fairvadrs_core;
5、处理缺页异常的核心:
vma_map_fairvadrs_core核心函数,查找缺页地址对应的kmvarsdsc_t 结构,由函数vma_map_find_kmvarsdsc实现,若没找到,则非法访问;
否则就查找kmvarsdsc_t 结构下对应的kvmemcbox_t 结构,用来挂载物理内存页面,由vma_map_retn_kvmemcbox函数实现;
最后分配物理内存页面并建立MMU页表映射关系,由函数vma_map_phyadrs 实现;
下面实现这三个函数
Ⅰ、缺页地址是否合法:
判断某个缺页地址是否合法,就要判断它是不是已经分配的虚拟地址,也就是看这个虚拟地址是不是会落在某个kmvarsdsc_t 结构表示的虚拟地址区间;
vma_map_find_kmvarsdsc_t 函数,先看看上一次刚刚被操作的kmvarsdsc_t 结构, 虚拟地址是否落在kmvarsdsc_t 结构表示的虚拟地址区间;
否则就遍历每个kmvarsdsc_t 结构,判断虚拟地址和 kmvarsdsc_t 结构 中的区间 做比较,大于等于开始地址,小于结束地址,
Ⅱ、建立kvmemcbox_t 结构
kvmemcbox_t 结构用来挂载物理内存页面 msadsc_t 结构,这个msadsc_t 结构由虚拟地址区间kmvarsdsc_t 结构代表的虚拟地址空间所映射的物理内存页面,一个kmvarsdsc_t结构比须有一个kvmemcbox_t 结构才能分配物理内存,除此之外,还能在内存共享时使用;
vma_map_retn_kvmemcbox函数,若kmvarsdsc_t 结构已经存在了kvmemcbox_t 结构,直接返回;
否则 新建一个kvmemcbox_t 结构,由 knl_get_kvmemcbox函数实现,调用kmsob_new 函数分配一个 kvmemcbox_t 结构大小的内存空间对象,然后实例化它;
Ⅲ、映射物理内存页面
现在给虚拟地址分配对应的物理内存页面,建立对应的MMU页表,使虚拟地址到物理地址可以转化成功,数据就可以写入;
vma_map_phyadrs接口函数:调用核心函数vma_map_msa_fault ;
vma_map_msa_fault 核心函数:会调用vma_new_usermsa函数,此函数调用之前的 内存管理接口,从而实现 分配一个物理内存页面,挂载到 kvmemcbox_t 结构中,并返回到msadsc_t 结构;
msadsc_ret_addr 函数 获取msadsc_t 结构对应的内存页面的物理地址;
hal_mmu_transform 函数 建立MMU页表完成虚拟地址到物理地址的映射;映射成功时就返回物理地址,否则就就要先释放分配的物理内存页面 ,需调用vma_del_usermsa ;
总结:内存管理是重中之重;从物理内存页面管理到内存对象管理再到虚拟内存管理;内核的其他组件,都要依赖于内存管理组件;
x86 CPU的 缺页异常号是14,保存在CPU的CR2 寄存器中;
浙公网安备 33010602011771号