反向映射的作用是根据物理页,找到全部相关进程的vma。
主要有两个结构,anon_vma_chain链表,和 anon_vma->rb_root红黑树
打个不恰当的比喻:可以简单认为,红黑树是用来读的(遍历找全部映射的vm_area),链表是用来写的(修改红黑树中节点的依据)
当通过mmap创建一个匿名的vm_area后(get_unmapped_area+mmap_region),访问这个area中的页触发缺页异常处理(do_anonymous_page->anon_vma_prepare)进程时为vm_area创建一个anon_vma,并生成一个绑定anon_vma和vm_area的anon_vma_chain节点,放在vma_area->anon_vma_chain链表上和anon_area->rb_root红黑树上(anon_vma_chain_link)。
红黑树中存的是所有映射同一anon_vma的anon_vma_chain节点,链表存的是所有映射相同一vm_area的anon_vma_chain节点。
fork子进程时(fork->kernel_clone->copy_process->copy_mm->dup_mm->dup_mmap->anon_vma_fork),会调anon_vma_clone通过遍历anon_vma_chain来拷贝所有映射同一vm_area节点的anon_vma_chain到子进程的vm_area->anon_vma_chain上,然后分配一个自己的anon_vma节点(anon_vma_fork,这里如果父anon_vma节点已经没人引用了,也可以复用过来,不用自己创建),anon_vma->root指向最初的anon_vma,anon_vma->parent指向父进程的anon_vma。同时将绑定这个新anon_vma和自己vm_area的anon_vma_chain节点放在子进程的vm_area->anon_vma_chain上。
子进程的这个anon_vma_chain节点要加在父进程的anon_area->rb_root红黑树上,因为它映射anon_area与父进程的anon_area相同,如果要unmap父进程的anon_area中的物理页,需要通过红黑树找到子节点,并对子节点做unmap。同理,如果后面再fork孙进程的话,它引用的anon_area页可能是父进程anon_area原本的页,也可能是子进程后面通过copy on write创建的新anon_area页,但如果子进程vm_area发生过变化,则一定只出现在子红黑树上。所以既可能同时出现在子与父进程的红黑树上,又可能只出现在子红黑树上(取决于子进程的vm_area有没有发生过变化)。
子进程的这个anon_vma_chain节点还要加在子进程的anon_area->anon_vma_chain上,因为现在的vm_area与原来的vm_area还是一样的,当要改子进程的vm_area时,需要通过anon_vma_chain找到所有映射过这个vm_area的父vm_area节点,将自己的anon_vma_chain从父anon_area的红黑树中移除。同理,如果后面再fork孙进程的话,它引用的vm_area既可能与父子均相同,又可能只与子相同,但只需要拷贝子进程的anon_vma_chain上节点即可,因为子进程的anon_vma_chain上包含了父进程的anon_vma_chain(如果父子进程的vm_area内容一样的话)。
当子进程访问写保护页触发unshare类型的缺页异常时,处理函数中会重新分配物理页(handle_pte_fault->do_wp_page->wp_page_copy),并将物理页的mapping指向子进程的anon_area(folio_add_new_anon_rmap),并将vm_area对应的pte换掉。但不会改父进程的红黑树,虽然父进程的anon_area上的页不再与子进程的anon_area页相同了(为什么不改父进程的anon_vma_chain和红黑树呢?原因不清楚,但没有关系,在遍历父进程红黑树时(rmap_walk)会跳过这个已经COW的vm_area,因为它们的物理页范围没有交集(check_pte(pvmw)))。
当改变vm_area时,需要找到所有引用这个vm_area的父vm_area和anon_area,将anon_vma_chain从它们的链表与红黑树中删除,后面fork的子进程将不再继承子进程之上的vm_area和anon_area。比如move_vma、copy_vma、free_pgtables、anon_vma_merge、vma_merge、vma_shrink、vma_expand、do_brk、split_vma。
当需要修改anon_area对应的页表项(改物理地址,或改flag)时,需要找到所有anon_area引用过的子进程的vma,同时做同一件事。比如页迁移修改 pte,页回收清除pte,标记页为 accessed / young,查看一个页框folio_referenced等。