当前位置: 首页 > Linux

浪潮信息工程师:带你了解设备透传到虚拟机的快速入门技术优化方案-龙力科技

时间:2023-04-06 02:52:31 Linux

当分配了比较大的内存时,虚拟机的启动时间会明显变慢,可能会从十几秒延长到几分钟,严重影响用户体验。本文编译自龙立讲堂第51期。浪潮信息操作系统研发工程师参与技术分享,介绍了设备透传虚拟机启动慢的原因及优化方法。以下为本次分享内容:技术背景:大内存虚拟机设备虚拟机设备透传存在问题,如网卡或GPU,通过本机的vfio透传给虚拟机hostmachine,并且给虚拟机分配了比较大的内存,虚拟机的启动时间会明显变慢。尤其是分配了几百G甚至TB级的内存时,会有比较明显的启动延迟,可能会延迟十几秒到几分钟。由于只会在qemu进程第一次启动时受到影响,一般来说用户可以接受这种启动延迟,但如果用户有频繁创建和销毁虚拟机的需求,用户体验会很差。设备透传给虚拟机后,会使用虚拟机内部的驱动来操作设备。这里一个重要的问题就是虚拟机内部如何进行DMA操作,因为DMA不经过CPU,所以使用的是物理地址,但是虚拟机内部看到的物理地址其实只是qemu申请的虚拟地址在宿主机上处理,直接拿来做DMA是肯定不行的。这需要借助IOMMU来实现。虚拟机中DMA访问的GPA通过iommu映射到宿主机上的物理地址HPA2。此外,虚拟机中的驱动程序还可以通过MMU/EPT将GVA映射到HPA1。最后,需要HPA1和HPA2这两个物理地址相同才能使设备正常工作。(如下图)为了实现HPA1等于HPA2,需要固定GPA到HPA的映射,建立iommu表。vfio是通过VFIO_IOMMU_MAP_DMA指令实现的。freepagereporting机制后面会用到。这里介绍一下免费页面上报机制的原理。首先,该机制提供回调函数的注册,每2秒遍历每个zone中的空闲页,调用回调函数进行处理,但只处理buddy的中高阶内存页,即pageblock_order及以上的内存。X86架构下处理9阶和10阶的内存页,即2M和4M的内存页。这样也保证可以处理2M的透明大页面。另一方面,在处理的时候,会保证每个zone的waterline比最低的waterline高64M,因为如果zone本身的waterline已经足够低,向zone申请内存,可能会触发内存回收,这会影响某些程序的性能。通过预清内存加速qemu进程的内存初始化过程在内核加载和服务运行的过程中,从火焰图中可以看出最耗时的函数是clear_subpage函数。根据函数调用关系可以看出ioctl系统调用是从qemu端调用的。这里是虚拟机内存的虚拟内存大小。执行iommu映射工作,将虚拟地址映射到指定的物理地址。其中,需要先对虚拟地址进行pagefault,固定好虚拟机地址和物理地址的关系,然后再进行iommu映射操作。当应用程序执行页面错误时,最终会执行clear_user_highpage来清除内存。这个函数耗时比较长,是虚拟机启动慢的一个重要原因。当然我们可以选择不清内存,但是这样会将宿主机上的一些敏感信息传递给虚拟机,会造成安全影响,所以一般我们都需要清内存。为了解决内存页清除时间过长的问题,我们可以基于空闲页上报机制实现内存页的预清除。这个过程相对简单。这里简单介绍一下原理。首先通过空闲页面上报接口注册自己的钩子函数,然后这个钩子函数会周期性的调用,每次调用都会清空buddy中的2M/4M空闲页面,并在pageflag的flag上设置clear。最后,当应用程序申请的内存触发页面错误,需要清除内存时,会先判断该页面是否设置了清除标志。如果设置,将跳过耗时的清除操作。还有一点,内存页的清除是一个比较耗时但并不紧急的操作。如果当前CPU有其他事情要做,会先执行其他任务,只有在CPU空闲时才会进行内存清理。通过使用大内存pin,handle_mm_fault,find_extern_vma,减少qemu的内存pin时间,从代码的角度可以优化这个功能。vfio_pin_map_dma(…){while(size){npage=vfio_pin_pages_remote(dma,start_vaddr,size,&pfn,limit)vfio_iommu_map(iommu,start_vaddr,pfn,npage,true)}执行iommumap的ioctl}IOMMU_MAP_DMA用于连续物理内存address进入内核空间会执行vfio_pin_map_dma函数。在这个函数中,会对虚拟机内存大小的虚拟内存进行iommumap操作。首先调用vfio_pin_pages_remote进行内存管脚操作。npage这个函数的返回值是N个物理地址连续的内存页,然后调用vfio_iommu_map执行iommumap操作。比如500G内存的虚拟机,这两个函数会被执行很多次。vfio_pin_pages_remote(…){for(…){pin_user_pages_remote(NULL,mm,vaddr,1,flags|FOLL_LONGTERM,page,NULL,NULL);pfn=page_to_pfn(page[0]);如果(pfn!=*pfn_base+固定)中断;}一次只pin一页,然后判断是否与上一页物理连续}vfio_pin_pages_remote这个函数会返回多个物理连续的内存页。上一页在物理上是否连续。get_user_pages(…){while(npages){if(!vma||start>=vma->vm_end)//相邻的两个虚拟内存页是否属于同一个vma{vma=find_extern_vma(…)}page=follow_page_mask(vma,开始,…);if(!page)faultin_page(…)}}前面提到的pin_user_pages_remote函数执行了很多次。这个函数主要调用get_user_pages。根据需要的pinpage个数,先判断相邻的两个虚拟内存页是否属于同一个vma,如果不属于则调用find_extern_vma函数获取vma,这个函数从flamegraph中耗时较长,如下就是执行follow_page_mask获取页表,如果还没有分配物理页就调用pagefault。后两个功能的执行是不可避免的。再来看find_extern_vma函数,因为虚拟机的物理内存是qemummap的连续虚拟内存,属于同一个vma,所以如果每次pin多个page,可以跳过频繁执行的。查找外部虚拟机。来看看内核社区的优化方案吧。5.12内核合并到主线,通过修改vfio代码实现。提交显示性能提高了8%。优化原则是pin_user_pages_remote函数中每次获取512页。当然,这些页面可能是不连续的,然后识别出512个页面中连续的部分,分别执行iommu,因为我们的系统开启了透明大页面。所以一般来说,这512页在物理上都是连续的,只需要执行一次iommu即可。测试结果测试环境为虚拟机分配了512G内存,并且对网卡做了设备透传。虚拟机启动时间的计算方法是从qemu进程启动到grub界面出现,内存被预清空。的情况下,即当前系统上的所有空闲页面都已被清除。最终测试结果:默认情况下(即开启透明大页),如果只开启largememorypin,启动时间从80秒减少到60秒,如果开启内存页预清空,启动时间将减少到12秒。看看虚拟机内存使用大页内存时的测试结果。使用2M或1G大页时启用大内存pin功能,虚拟机启动时间基本从36秒减少到18秒。内存页面预置零启动时间没有变化,因为内存预置零不适用于标准大页面。虚拟机分配512G内存。时间计算方法:timevirshstarttestvm,从qemu启动到grub界面的时间。在最理想的情况下,内存预清除是指当前系统上的所有空闲页面都已被清除。直播课件及视频播放获取方式:【PPT课件获取】:关注微信公众号(OpenAnolis)回复“龙百合课件”即可获取。如有任何问题,欢迎随时咨询龙蜥小助手——小龙(微信:openanolis_assis)。【视频播放】:可到龙蜥官网https://openanolis.cn/video查看视频播放。-超过-