我们知道LINUX的内存管理系统中有”反向映射“这一说,目的是为了快速去查找出一个特定的物理页在哪些进程中被映射到了什么地址,这样如果我们想把这一页换出(SWAP),或是迁移(Migrate)的时候,就能相应该更改所有相关进程的页表来达到这个目的。
1、为什么要使用反向映射
物理内存的分页机制,一个PTE(Page Table Entry)对应一个物理页,但一个物理页可以由多个PTE与之相对应,当该页要被回收时,Linux2.4的做法是遍历每个进程的所有PTE判断该PTE是否与该页建立了映射,如果建立则取消该映射,最后无PTE与该相关联后才回收该页。该方法显而易见效率极低,因为其为了查找某个页的关联PTE遍历了所有的PTE,我们不禁想:如果把每个页关联的PTE保存在页结构里面,每次只需要访问那些与之相关联的PTE不很方便吗?确实,2.4之后确实采用过此方法,为每个页结构(Page)维护一个链表,这样确实节省了时间,但此链表所占用的空间及维护此链表的代价很大,在2.6中弃之不用,但反向映射机制的思想不过如此,所以还是有参考价值的,可以参考:http://blog.csdn.net/dog250/article/details/5303581。
2、Linux2.6中是如何实现反向映射
2.1 与RM(Reverse Mapping)相关的结构
page
, address_space
, vm_area_struct
, mm_struct
, anon_vma
.
以下均显示部分成员:
struct page{ struct address_space *mapping; /* address_space类型,为对齐需要,其值为4的位数,所以最低两位无用,为充分利用资源,所以此处利用此最低位。 * 最低位为1表示该页为匿名页,并且它指向anon_vma对象。 * 最低为0表映射页,此时mapping指向文件节点地址空间。 */ atomic_t _mapcount; /* 取值-1时表示没有指向该页框的引用, 取值0时表示该页框不可共享 取值大于0时表示该页框可共享表示有几个PTE引用 */ pgoff_t index;};
struct mm_struct {
pgd_t * pgd;
}
struct vm_area_struct {
struct list_head anon_vma_node; /* Serialized by anon_vma->lock */
struct anon_vma *anon_vma; /* Serialized by page_table_lock */
}
struct anon_vma {
spinlock_t lock; /* Serialize access to vma list */
struct list_head head; /* List of private "related" vmas */
};
2.2 进程地址空间
每个进程有个进程描述符task_struct,其中有mm域指向该进程的内存描述符mm_struct。
每个进程都拥有一个内存描述符,其中有PGD域,指向该进程地址空间的全局页目录;mmap域指向第一个内存区域描述符vm_area_strut1。
进程通过内存区域描述符vm_area_struct管理内存区域,每个内存区域描述符都有vm_start和vm_end域指向该内存区域的在虚拟内存中的起始位置;vm_mm域指向该进程的内存描述符;每个vm_area_struct都有一个anon_vma域指向该进程的anon_vma;
每个进程都有一个anon_vma,是用于链接所有vm_area_struct的头结点,通过vm_area_struct的anon_vma_node构成双循环链表。
最终形成了上图。
现在假设我们要回收一个页,我们要做的是访问所有与该页相关联的PTE并修改之取消二者之间的关联。与之相关联的函数为:try_to_unmap。
2.3 try_to_unmap
2.3.1 try_to_unmap函数及PageOn宏 分析
int try_to_unmap(struct page *page)
{
int ret;
BUG_ON(PageReserved(page));
BUG_ON(!PageLocked(page));
/*判断是不是匿名页,若是的话执行try_to_unmap_anon函数,否则的话执行try_to_unmap_file函数*/
if (PageAnon(page)) // PageAnon函数分析在下面
ret = try_to_unmap_anon(page);
else
ret = try_to_unmap_file(page);
if (!page_mapped(page))
ret = SWAP_SUCCESS;
return ret;
}
static inline int PageAnon(struct page *page)
{
return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
/* #define PAGE_MAPPING_ANON 1 此函数非常easy,就是判断page的mapping成员的末位是不是1,是的话返回1,不是的话返回0*/
}
2.3.2 try_to_unmap_anon函数及page_lock_anon_vma函数及list_for_each_entry宏 分析
还没开始看文件系统一节,所以try_to_unmap_file没看懂,所以此处只分析 try_to_unmap_anon函数,等看完vfs后再来补充吧。
static int try_to_unmap_anon(struct page *page)
{
struct anon_vma *anon_vma;
struct vm_area_struct *vma;
int ret = SWAP_AGAIN;
anon_vma = page_lock_anon_vma(page); /* 获取该匿名页的anon_vma结构
* page_lock_anon_vma函数分析在下面。
*/
if (!anon_vma)
return ret;
list_for_each_entry(vma, &anon_vma->head, anon_vma_node) { /* 循环遍历
* list_for_each_entry分析在下面
* anon_vma就是上图中anon_vma的指针,anon_vma->head得到其head成员(是list_head)类型,
* 其next值便对应上图中vm_area_struct1中的anon_vma_node中的head。
* vma 是vm_area_struct类型的指针,anon_vma_node为typeof(*vma)即vm_area_struct中的成员。
* 到此便可以开始双链表循环
*/
ret = try_to_unmap_one(page, vma); // 不管是调用try_to_unmap_anon还是try_to_unmap_file最终还是回到try_to_unmap_one上
if (ret == SWAP_FAIL || !page_mapped(pag