背景分析:

在之前分析EPT violation的时候,没有太注意qemu进程页表和EPT的关系,从虚拟机运行过程分析,虚拟机访存使用自身页表和EPT完成地址转换,没有用到qemu进程页表,所以也就想当然的认为虚拟机使用的物理页面在qemu进程的页表中没有体现。但是最近才发现,自己的想法是错误的。LInux kernel作为核心管理层,具体物理页面的管理有其管理,再怎么说,虚拟机在host上表现为一个qemu进程,而内存管理器只能根据qemu进程页表管理其所拥有的物理页面,否则,linux kernel怎么知道哪些物理页面是属于qemu进程的?这是问题1;还有一个问题就是用一个实例来讲,virtio 的实现包含前后端驱动两个部分,前后端其实通过共享内存的方式实现数据的0拷贝。具体来讲,虚拟机把数据填充好以后,通知qemu,qemu得到通过把对应的GPA转化成HVA,如果两个页表不同步,怎么保证访问的是同一块内存?

带着上面的问题,我又重新看了下EPT的维护流程,终于发现了问题,事实上,KVM并不负责物理页面的分配,而是请求qemu分配后把对应的地址传递过来,然后自己的维护EPT。也就是说,在qemu进程建立页表后,EPT才会建立。下面详细描述下,整体流程大致如图所示:

handle_ept_violation是处理EPT未命中时候的处理函数,最终落到tdp_page_fault函数中。有个细节就是该函数在维护EPT之前,已经申请好了pfn即对应的物理页框号,具体见try_async_pf函数,其实之前也注意过这个函数,就是没多想!!唉……

static bool try_async_pf(struct kvm_vcpu *vcpu, bool prefault, gfn_t gfn,
gva_t gva, pfn_t *pfn, bool write, bool *writable)
{
bool async; *pfn = gfn_to_pfn_async(vcpu->kvm, gfn, &async, write, writable); if (!async)
return false; /* *pfn has correct page already */ if (!prefault && can_do_async_pf(vcpu)) {
trace_kvm_try_async_get_page(gva, gfn);
if (kvm_find_async_pf_gfn(vcpu, gfn)) {
trace_kvm_async_pf_doublefault(gva, gfn);
kvm_make_request(KVM_REQ_APF_HALT, vcpu);
return true;
} else if (kvm_arch_setup_async_pf(vcpu, gva, gfn))
return true;
} *pfn = gfn_to_pfn_prot(vcpu->kvm, gfn, write, writable); return false;
}

这里其主要作用的有两个函数和gfn_to_pfn_prot,二者均调用了static pfn_t __gfn_to_pfn(struct kvm *kvm, gfn_t gfn, bool atomic, bool *async,bool write_fault, bool *writable)函数,区别在于第四个参数bool *async,前者不为NULL,而后者为NULL。先跟着gfn_to_pfn_async函数往下走,该函数直接调用了__gfn_to_pfn(kvm, gfn, false, async, write_fault, writable);可以看到这里atomic参数被设置成false。

static pfn_t __gfn_to_pfn(struct kvm *kvm, gfn_t gfn, bool atomic, bool *async,
bool write_fault, bool *writable)
{
struct kvm_memory_slot *slot; if (async)
*async = false; slot = gfn_to_memslot(kvm, gfn); return __gfn_to_pfn_memslot(slot, gfn, atomic, async, write_fault,
writable);
}

在__gfn_to_pfn函数中,如果async不为NULL,则初始化成false,然后根据gfn获取对应的slot结构。接下来调用__gfn_to_pfn_memslot(slot, gfn, atomic, async, write_fault,writable);该函数主要做了两个事情,首先根据gfn和slot得到具体得到host的虚拟地址,然后就是调用了hva_to_pfn函数根据虚拟地址得到对应的pfn。

static pfn_t hva_to_pfn(unsigned long addr, bool atomic, bool *async,
bool write_fault, bool *writable)
{
struct vm_area_struct *vma;
pfn_t pfn = ;
int npages; /* we can do it either atomically or asynchronously, not both */
/*这里二者不能同时为真*/
BUG_ON(atomic && async);
/*主要实现逻辑*/
if (hva_to_pfn_fast(addr, atomic, async, write_fault, writable, &pfn))//查tlb缓存
return pfn; if (atomic)
return KVM_PFN_ERR_FAULT;
/*如果前面没有成功,则调用hva_to_pfn_slow*/
npages = hva_to_pfn_slow(addr, async, write_fault, writable, &pfn);//快表未命中,查内存页表
if (npages == )
return pfn; down_read(&current->mm->mmap_sem);
if (npages == -EHWPOISON ||
(!async && check_user_page_hwpoison(addr))) {
pfn = KVM_PFN_ERR_HWPOISON;
goto exit;
} vma = find_vma_intersection(current->mm, addr, addr + ); if (vma == NULL)
pfn = KVM_PFN_ERR_FAULT;
else if ((vma->vm_flags & VM_PFNMAP)) {
pfn = ((addr - vma->vm_start) >> PAGE_SHIFT) +
vma->vm_pgoff;
/*如果PFN不是MMIO*/
BUG_ON(!kvm_is_mmio_pfn(pfn));
} else {
if (async && vma_is_valid(vma, write_fault))
*async = true;
pfn = KVM_PFN_ERR_FAULT;
}
exit:
up_read(&current->mm->mmap_sem);
return pfn;
}

在本函数中涉及到两个重要函数hva_to_pfn_fast和hva_to_pfn_slow,首选是前者,在前者失败后,调用后者。hva_to_pfn_fast核心是调用了__get_user_pages_fast函数,而hva_to_pfn_slow函数的主体其实是get_user_pages_fast函数,可以看到这里两个函数就查了一个前缀,前者默认页表项已经存在,直接通过遍历页表得到对应的页框;而后者不做这种假设,如果有页表项没有建立,还需要建立页表项,物理页面没有分配就需要分配物理页面。考虑到这里是KVM,在开始EPT violation时候,虚拟地址肯定没有分配具体的物理地址,所以这里调用后者的可能性比较大。get_user_pages_fast函数的前半部分基本就是__get_user_pages_fast,所以这里我们简要分析下get_user_pages_fast函数。

int get_user_pages_fast(unsigned long start, int nr_pages, int write,
struct page **pages)
{
struct mm_struct *mm = current->mm;
unsigned long addr, len, end;
unsigned long next;
pgd_t *pgdp, pgd;
int nr = ; start &= PAGE_MASK;
addr = start;
len = (unsigned long) nr_pages << PAGE_SHIFT;
end = start + len;
if ((end < start) || (end > TASK_SIZE))
goto slow_irqon; /*
* local_irq_disable() doesn't prevent pagetable teardown, but does
* prevent the pagetables from being freed on s390.
*
* So long as we atomically load page table pointers versus teardown,
* we can follow the address down to the the page and take a ref on it.
*/
local_irq_disable();
pgdp = pgd_offset(mm, addr);
do {
pgd = *pgdp;
barrier();
next = pgd_addr_end(addr, end);
if (pgd_none(pgd))
goto slow;
if (!gup_pud_range(pgdp, pgd, addr, next, write, pages, &nr))
goto slow;
} while (pgdp++, addr = next, addr != end);
local_irq_enable(); VM_BUG_ON(nr != (end - start) >> PAGE_SHIFT);
return nr; {
int ret;
slow:
local_irq_enable();
slow_irqon:
/* Try to get the remaining pages with get_user_pages */
start += nr << PAGE_SHIFT;
pages += nr; down_read(&mm->mmap_sem);
ret = get_user_pages(current, mm, start,
(end - start) >> PAGE_SHIFT, write, , pages, NULL);
up_read(&mm->mmap_sem); /* Have to be a bit careful with return values */
if (nr > ) {
if (ret < )
ret = nr;
else
ret += nr;
} return ret;
}
}

函数开始获取虚拟页框号和结束地址,在咱们分析的情况下,一般这里就是一个页面的大小。然后调用local_irq_disable禁止本地中断,开始遍历当前进程的页表。pgdp是在页目录表中的偏移+一级页表基址。进入while循环,获取二级表的基址,next在这里基本就是end了,因为前面申请的仅仅是一个页面的长度。可以看到这里如果表项内容为空,则goto到了slow,即要为其建立表项。这里暂且略过。先假设其存在,继续调用gup_pud_range函数。在x86架构下,使用的二级页表而在64位下使用四级页表。64位暂且不考虑,所以中间两层处理其实就是走个过场。这里直接把pgdp指针转成了pudp即pud_t类型的指针,接下来还是进行同样的工作,只不过接下来调用的是gup_pmd_range函数,该函数取出表项的内容,往下一级延伸,重点看其调用的gup_pte_range函数。

static inline int gup_pte_range(pmd_t *pmdp, pmd_t pmd, unsigned long addr,
unsigned long end, int write, struct page **pages, int *nr)
{
unsigned long mask;
pte_t *ptep, pte;
struct page *page; mask = (write ? _PAGE_RO : ) | _PAGE_INVALID | _PAGE_SPECIAL; ptep = ((pte_t *) pmd_deref(pmd)) + pte_index(addr);
do {
pte = *ptep;
barrier();
if ((pte_val(pte) & mask) != )
return ;
VM_BUG_ON(!pfn_valid(pte_pfn(pte)));
page = pte_page(pte);
if (!page_cache_get_speculative(page))
return ;
if (unlikely(pte_val(pte) != pte_val(*ptep))) {
put_page(page);
return ;
}
pages[*nr] = page;
(*nr)++; } while (ptep++, addr += PAGE_SIZE, addr != end); return ;
}

这里就根据pmd和虚拟地址的二级偏移,定位到二级页表项的地址ptep,在循环中,就取出ptep的内容,不出意外就是物理页面的地址及pfn,后面调用了page = pte_page(pte);实质是把pfn转成了page结构,然后设置参数中的page数组。没有错误就返回1.上面就是整个页表遍历的过程。如果失败了,就为其维护页表并分配物理页面,注意这里如果当初申请的是多个页面,就一并处理了,而不是一个页面一个页面的处理。实现的主体是get_user_pages函数,该函数是__get_user_pages函数的封装,__get_user_pages比较长,我们这里i就不在介绍,感兴趣的朋友可以参考具体的代码或者其他资料。

参考资料:

linux内核3.10.1代码

qemu进程页表和EPT的同步问题的更多相关文章

  1. {Python之进程} 背景知识 什么是进程 进程调度 并发与并行 同步\异步\阻塞\非阻塞 进程的创建与结束 multiprocess模块 进程池和mutiprocess.Poll

    Python之进程 进程 本节目录 一 背景知识 二 什么是进程 三 进程调度 四 并发与并行 五 同步\异步\阻塞\非阻塞 六 进程的创建与结束 七 multiprocess模块 八 进程池和mut ...

  2. 进程理论 阻塞非阻塞 同步异步 I/O操作

    1.什么是进程 进程指的是一个正在运行的程序,进程是用来描述程序执行过程的虚拟概念 进程的概念起源于操作系统,进程是操作系统最核心的概念,操作系统其它所有的概念都是围绕进程来的 2.操作系统 操作系统 ...

  3. CIL锁,GIL与线程池的区别,进程池和线程池,同步与异步

    一.GIL锁 什么是GIL? 全局解释器锁,是加在解释器上的互斥锁 GC是python自带的内存管理机制,GC的工作原理:python中的内存管理使用的是应用计数,每个数会被加上一个整型的计数器,表示 ...

  4. python笔记-10(socket提升、paramiko、线程、进程、协程、同步IO、异步IO)

    一.socket提升 1.熟悉socket.socket()中的省略部分 socket.socket(AF.INET,socket.SOCK_STREAM) 2.send与recv发送大文件时对于黏包 ...

  5. python开发进程:互斥锁(同步锁)&进程其他属性&进程间通信(queue)&生产者消费者模型

    一,互斥锁,同步锁 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 竞争带来的结果就是错乱,如何控制,就是加锁处理 part1:多个进程共享同一打印终 ...

  6. Android SharePreference 在主进程和次进程间共享数据不同步出错

      SharedPreference作为android五大存储(网络,数据库,文件,SharedPreference,contentProvider)之中最方便使用的一个,从类名上来看就不是一个存储大 ...

  7. KVM地址翻译流程及EPT页表的建立过程

    本博文为原创,遵循CC3.0协议,转载请注明出处:http://blog.csdn.net/lux_veritas/article/details/9284635 ------------------ ...

  8. 一个进程间同步和通讯的 C# 框架

    转自原文 一个进程间同步和通讯的 C# 框架 threadmsg_demo.zip ~ 41KB    下载 threadmsg_src.zip ~ 65KB    下载 0.背景简介 微软在 .NE ...

  9. C#进程间通讯或同步的框架引荐

    这篇文章主要介绍了一个进程间通讯同步的C#框架,代码具有相当的稳定性和可维护性,随着.NET的开源也会被注入更多活力,推荐!需要的朋友可以参考下  0.背景简介 微软在 .NET 框架中提供了多种实用 ...

随机推荐

  1. 递归删除子目录下所有.la后缀文件

    删除当前目录及其子目录下的后缀名.la的所有文件 find ./ -name '*.la' | xargs rm -f

  2. BlueZ--内核层+应用层

    BlueZ 1.Kernel层实现: bluetooth协议栈有多层结构,最底层的硬件协议在硬件中就已经实现了.软件级别的协议实现,从HCI这一层开始实现. BlueZ对各层协议的实现是依托于Sock ...

  3. Scala中集合类型与java中集合类型转换

    对于java中的集合元素并不能在scala中拿来就用的,需要进行相应的转换. 1. 转换规则如下 从下面可以看出,有些可以相互转换的,有些只能单向转换: scala.collection.Iterab ...

  4. 微信小程序6 - 页面之间传参及通知系统封装

    1. 简单传参 wx.navigateTo({ url: '/pages/demo/index/index?id=1' }) /pages/demo/index/index.js 中 onLoad(o ...

  5. 调用半截的div

    不能引用jquery: <script src="${rootUrl }js/jquery/jquery.js" type="text/javascript&quo ...

  6. asp.net 判断session是否过期

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.We ...

  7. 如何用ChemDraw绘制化学课件

    近年来随着ChemDraw等多媒体技术的迅速发展,多媒体技术越来越多的应用在教学中.学会应用ChemDraw绘制化学分子结构.化学反应式和实验装置的方法,将在有机化学的教学中提供一定的帮助,进一步提高 ...

  8. ios开发之 -- Swap file ".Podfile.swp" already exists!

  9. iOS开发之--iOS APP打包的时候出现的四个选项

  10. UE4插件

    源代码中包含一些插件例子 C:\Program Files\Epic Games\UE_4.15\Engine\Plugins\Developer 一下截图来自官网https://docs.unrea ...