linux内存管理(八)- 反向映射RMAP
这里有一篇博客讲的不错。linux内存管理笔记(三十八)----反向映射_linux 反向映射-CSDN博客
页表是把虚拟地址映射到物理页面,但是如何根据一个物理页找到所有映射它的pte呢?答案是用反向映射Reverse Mapping(RMAP)。这在页面回收中很有用。回收页面需要将到物理页的映射断开(改一下pte),前提是找到所有映射的pte,那就必须在page结构中留下线索,这就是mapping字段。
在讲page结构时我们提到过mapping可以指向匿名页的anon_vma结构,这个结构是反向映射的关键之一。
struct anon_vma { struct anon_vma *root; /* Root of this anon_vma tree */ struct rw_semaphore rwsem; /* W: modification, R: walking the list */ atomic_t refcount; unsigned long num_children; /* Count of VMAs whose ->anon_vma pointer points to this object. */ unsigned long num_active_vmas; struct anon_vma *parent; /* Parent of this anon_vma */ struct rb_root_cached rb_root; //avc结构会链接到这棵rbtree上 };
除了anon_vma(AV)还有一个AVC(anon_vma_chain)结构。
/* * The copy-on-write semantics of fork mean that an anon_vma * can become associated with multiple processes. Furthermore, * each child process will have its own anon_vma, where new * pages for that process are instantiated. * * This structure allows us to find the anon_vmas associated * with a VMA, or the VMAs associated with an anon_vma. * The "same_vma" list contains the anon_vma_chains linking * all the anon_vmas associated with this VMA. * The "rb" field indexes on an interval tree the anon_vma_chains * which link all the VMAs associated with this anon_vma. */ struct anon_vma_chain { struct vm_area_struct *vma; struct anon_vma *anon_vma; struct list_head same_vma; /* locked by mmap_lock & page_table_lock */ struct rb_node rb; /* locked by anon_vma->rwsem */ unsigned long rb_subtree_last; #ifdef CONFIG_DEBUG_VM_RB unsigned long cached_vma_start, cached_vma_last; #endif };
看注释可知AVC可以通过rb_node链接到anon_vma的rbtree上,这个rbtree会链接所有与AV相关的VMA。也可以通过same_vma链表链接所有与该VMA相关的AV。AVC是anon_vma和vma的枢纽,可以让三者相互找到另外两方。
上图是一个简单的情形,描述三者之间的关系,这对理解后面反向映射的应用很重要。
反向映射的应用。
使用反向映射最常见的是在回收页面,try_to_unmap是其重要函数。
void try_to_unmap(struct folio *folio, enum ttu_flags flags) { struct rmap_walk_control rwc = { .rmap_one = try_to_unmap_one, .arg = (void *)flags, .done = folio_not_mapped, .anon_lock = folio_lock_anon_vma_read, }; if (flags & TTU_RMAP_LOCKED) rmap_walk_locked(folio, &rwc); else rmap_walk(folio, &rwc); }
rmap_walk
void rmap_walk(struct folio *folio, struct rmap_walk_control *rwc) { if (unlikely(folio_test_ksm(folio))) rmap_walk_ksm(folio, rwc); else if (folio_test_anon(folio)) rmap_walk_anon(folio, rwc, false); else rmap_walk_file(folio, rwc, false); }
只看rmap_walk_anon
static void rmap_walk_anon(struct folio *folio, struct rmap_walk_control *rwc, bool locked) { struct anon_vma *anon_vma; pgoff_t pgoff_start, pgoff_end; struct anon_vma_chain *avc; if (locked) { anon_vma = folio_anon_vma(folio); /* anon_vma disappear under us? */ VM_BUG_ON_FOLIO(!anon_vma, folio); } else {
//从folio中获取anon_vma anon_vma = rmap_walk_anon_lock(folio, rwc); } if (!anon_vma) return; //page->index是page在file或内存区域中的偏移,单位是page pgoff_start = folio_pgoff(folio);
//得到复合页的末尾index pgoff_end = pgoff_start + folio_nr_pages(folio) - 1;
//遍历avc anon_vma_interval_tree_foreach(avc, &anon_vma->rb_root, pgoff_start, pgoff_end) { struct vm_area_struct *vma = avc->vma;
//得到page的虚拟地址 unsigned long address = vma_address(&folio->page, vma); VM_BUG_ON_VMA(address == -EFAULT, vma); cond_resched(); if (rwc->invalid_vma && rwc->invalid_vma(vma, rwc->arg)) continue; //使用回调函数rmap_one断开虚拟地址对应的pte if (!rwc->rmap_one(folio, vma, address, rwc->arg)) break; if (rwc->done && rwc->done(folio)) break; } if (!locked) anon_vma_unlock_read(anon_vma); }
rmap_walk_anon遍历anon_vma对应的rbtree,找到所有映射该页的vma,然后断开物理页对应虚拟地址的pte。
这里要理解page->index和vma->pgoff和vma->vm_start的关系。
由上图可以看到page->index指的是在整个文件或者进程地址空间(不一定是vma)上的偏移,要想得到它在当前vma内的偏移公式是vma->vm_start + (page->index - vma->vm_pgoff) << PAGE_SHIFT.