1. 阅读最新RMAP机制的代码,画出VMA,AVC,anon_vma和page等数据结构之间的关系图

奔跑吧Linux-内存管理思考题(7)_子进程

最新的RMAP机制新增了AVC数据结构(struct anon_vma_chain),父进程和子进程都有各自的AV数据结构并且都有一棵红黑树,此外父进程和子进程都有各自的AVC挂入进程的AV红黑树中。还有一个AVC枢纽,来联系父进程和子进程。
AVC枢纽挂入父进程的AV红黑树中,因此所有子进程都有一个AVC枢纽用于挂入都父进程的AV红黑树。
需要反向映射遍历时,只需要扫描父进程的AV红黑树即可。当子进程VMA发生COW时,新分配的匿名页面cow_page->mapping指向子进程自己的AV数据结构,而不是父进程的AV数据结构,因此反向映射遍历时不需要扫描所有的子进程。

2.当page加入lru链表,被其他线程释放了这个page,那么lru链表如何知道这个page已经被释放了?

LRU时最近最少使用的缩写,内核使用双向链表来表示LRU链表,根据页面类型分为LRU_ANON和LRU_FILE。这两种类型根据页面的活跃性质又分为活跃LRU和不活跃LRU。内核中有以下五个LRU链表。
enum lru_list {
LRU_INACTIVE_ANON = LRU_BASE,//不活跃匿名页面
LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE,//活跃匿名页面
LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE,//不活跃文件映射页面
LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE,//活跃文件映射
LRU_UNEVICTABLE,//不可回收页面
NR_LRU_LISTS
};
LRU链表之所以要这么细分,是因为当内存紧缺时总是优先换出page cache页面,而不是匿名页面。大多数情况下page cache页面不需要回写磁盘,除非页面内容被修改,而匿名页面总是要被写入swap分区才能被换出。LRU链表按照zone来配置。

经典的LRU链表算法是FIFO算法,页面总是在活跃LRU和不活跃LRU链表之间转移,
随着时间的推移,最不常用的页面将慢慢移动到不活跃LRU链表的末尾,直至被换出。当系统再次需要的时候,这些页面会重新置于LRU链表的开头。

第二次机会法在经典LRU算法基础上做了一些改进,目的是避免将经常使用的页面置换出去。相比于经典LRU算法,第二次机会法在经典LRU基础上增加了一个访问状态位(硬件控制的比特位),所以要检查页面的访问状态。如果是0,就淘汰这个页面,如果是1,就给它第二次机会。如果一个页面经常被使用,那么它的状态位则始终为1。
内核使用PG_active来表示页面是否活跃,PG_referenced表示页面被引用过。不活跃的页面将会被移到不活跃链表中,其中没有引用的页面将会被回收。

Page_check_references会扫描不活跃LRU链表,返回四种状态:
(1) 将该页面迁移到活跃链表
(2) 继续将该页面保留在不活跃链表中
(3) 最后两种状态表示可以回收该页面

如何判断页面可以保留在不活跃链表中?page_referenced函数会检查该页面的pte被访问引用的个数,testclearpagereferenced会返回该页面的PG_referenced状态位。如果有访问引用该页面的pte,则放到活跃链表中。

3.Kswapd内核线程何时会被唤醒?

Kswapd负责在内存不足的情况下回收内存,在分配内存时,如果处于低水位无法成功分配内存,就会通过wakeup_kswapdh函数来唤醒kswapd内核线程来回收页面。

4.LRU链表如何知道page的活动频繁程度?

page_referenced函数判断该page是否被访问引用过,返回的访问引用pte的个数,即访问和引用该page的用户空间虚拟页面的个数。核心思想是通过反向映射系统来统计访问引用pte的用户个数。