反向映射

反向映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 */
};

匿名页经常由几个进程共享。
最常见的情形:创建新进程,父进程的所有页框,包括匿名页,同时也分配给子进程。
另外(不常见),进程创建线性区时使用两个标志 MAP_ANONYMOUS 和 MAP_SHARED,表明这个区域内的也将由该进程后面的子进程共享。

将引用同一页框的所有匿名页链接起来的策略:将该页框所在的匿名线性区存放在一个双向循环链表中。
注意:即使一个匿名线性区存有不同的页,也始终只有一个反向映射链表用于该区域中的所有页框。

当为一个匿名线性区分配第一页时,内核创建一个新的 anon_vma 数据结构,它只有两个字段:

  • lock,竞争条件下保护链表的自旋锁。
  • head,线性区描述符双向循环链表的头部。

然后,内核将匿名线性区的 vm_area_struct 描述符插入 anon_vma 链表。vm_area_struct 中对应链表的两个字段:

  • anon_vma_node,存放指向链表中前一个和后一个元素的指针。(多个vm_area_struct链接到一起)
  • anon_vma,指向 anon_vma 数据结构。

最后,内核将 anon_vma 数据结构的地址存放在匿名页描述符的 mapping 字段。

当已被一个进程引用的页框插入另一个进程的页表项时(如调用 fork() 时),内核只是将第二个进程的匿名线性区插入 anon_vma 数据结构的双向循环链表,而第一个进程线性区的 anon_vma 字段指向该 anon_vma 数据结构。
因此,每个 anon_vma 链表通常包含不同进程的线性区。
借助 anon_vma 链表,内核可快速定位引用同一匿名页框的所有页表项。
每个区域描述符在 vm_mm 字段中存放内存描述符地址,而该内存描述符又有一个 pgd 字段,其中存有进程的页全局目录。
这样,页表项就可以从匿名页的起始线性地址得到,该线性地址可以由线性区描述符及页描述符的 index 字段得到。