linux 逆向映射
逆向映射用于建立物理内存页和使用该页的进程的对应页表项之间的联系,在换出页时以便更新所有涉及的进程。得到物理页基址后,根据pfn_to_page可以将页框转换为page实例,page实例中的mapping成员,在映射匿名页面的时候该成员指向一个anon_vma结构,在映射文件页面的时候指向inode节点的address-space。这里简述一下匿名映射的情况:
一个物理页面可以同时被多个进程的虚拟地址内存映射,但一个虚拟页面同时只能有一个物理页面与之映射。不同虚拟页面同时映射到同一物理页面是因为子进程克隆父进程VMA,和KSM机制的存在。逆向映射实现的基础是通过struct anon_vma(简称AV)、struct anon_vma_chain(简称AVC)和sturct vm_area_struct(简称VMA)建立了联系,通过物理页面反向查找到VMA,这三个结构体的关系如下:
static void anon_vma_chain_link(struct vm_area_struct *vma, struct anon_vma_chain *avc, struct anon_vma *anon_vma) { avc->vma = vma; //建立struct anon_vma_chain和struct vm_area_struct关联 avc->anon_vma = anon_vma; //建立struct anon_vma_chain和struct anon_vma关联 list_add(&avc->same_vma, &vma->anon_vma_chain);//将AVC添加到struct vm_area_struct->anon_vma_chain链表中。 anon_vma_interval_tree_insert(avc, &anon_vma->rb_root);//将AVC添加到struct anon_vma->rb_root红黑树中。 }
说一下匿名映射情况下如何通过page得到对应的pte:匿名映射的情况下page->mapping指向anon_vma结构,AVC通过红黑树结点链接到AV中,而AVC又指向VMA,遍历anon_vma->rb_root红黑树中的AVC,从AVC可以得到相应的VMA,因此通过page指向的anon_vma结构可以得到与该进程相关的所有vma。前面说到,由于子进程复制父进程的vma,因此多个不同子进程中的虚拟页面会同时映射到同一个物理页面,另外多个不相干进程虚拟页面也可以通过KSM机制映射到同一个物理页面。这两种情况下通过逆向映射解除对一个页面的映射的实现是不一样的。函数page_referenced会先判断页面的类型,如果是KSM页面走page_referenced_ksm。如果是匿名映射页,走page_referenced_anon,如果是文件映射页,走page_referenced_file(对于文件主要就是根据page->mapping->i_mmap得到相关的vma),page_referenced_ksm和page_referenced_anon的区别在于获取page对应的虚拟地址上存在差异。
对于page_referenced_anon,对应到前面说的多个不同子进程中的虚拟页面同时映射到同一个物理页面的情况,page->index为page对应的虚拟页框在对应vma中的偏移,因为子进程复制父进程的vma,因此他们对应的vma结构都是一样的,因此page->index在不同子进程的vma中的偏移都是一致的,故对于每个关联的vma,都通过vma_address(page, vma)便可以获取page在当前vma对应的虚拟地址,且通过vma可以得到mm,进一步得到pgd。通过pgd和虚拟地址,可以继续遍历页表得到pte,然后解除映射。
对于page_referenced_ksm,由于是不同进程的虚拟页面映射到同一个物理页面,因此page对应的虚拟页框在不同进程的vma中的偏移肯定是不一致的,这里就不能对每个vma都通过vma_address(page, vma)得到page对应的虚拟地址。内核中对于ksm页面,内核维护了一个结构体rmap_item,通过stable_node->hlist将使用了ksm页面的rmap_item连接起来。rmap_item中维护了page对应的虚拟地址address及anon_vma结构,基于此得到对应的vma,虚拟地址通过rmap_item得到,然后进一步解除映射。
共享内存方式的页如何通过逆向映射解除映射:应该是通过文件的方式,内核实现的shm共享内存会分配一个文件标识符,而mmap实现的共享内存要么是在父子进程之间,要么就是文件映射。