持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21天,点击查看活动详情
任何事物都有固定的生命周期,就像一个企业有创立、成长、成熟、衰退等阶段。匿名页面也是有生命周期的,分为诞生、使用、回收、释放等阶段。我们从生命周期的角度来观察匿名页面。
匿名页面的产生
从内核的角度来看,在如下情况下会产生匿名页面。 (1)用户空间通过malloc()/mmap()接口函数来分配内存,在内核空间中发生缺页中断时,do_anonymous_page()会产生匿名页面。
(2)发生写时复制。当缺页中断出现写保护错误时,新分配的页面是匿名页面,下面又分两种情况。 调用do_wp_page()。
分配只读的特殊映射的页面,如映射到零页面的页面。 分配非单身匿名页面(有多个映射的匿名页面,即page->_mapcount > 0)。 分配只读的私有映射的内容缓存页面。 分配KSM页面。 调用do_cow_page()共享的匿名映射(Shared Anonymous Mapping,SHMM)页面。 上述这些情况在发生写时复制时会新分配匿名页面。 (3)do_swap_page(),从交换分区读回数据时会分配匿名页面。 (4)迁移页面。 以do_anonymous_page()分配一个匿名页面为例,匿名页面刚分配时的状态如下。 page->_refcount = 1。 page->_mapcount = 0。 设置PG_swapbacked标志位。 加入LRU_ACTIVE_ANON链表中,并设置PG_lru标志位。 page->mapping指向VMA中的anon_vma数据结构。
匿名页面的使用
匿名页面在缺页中断中分配完成之后,就建立了进程虚拟地址空间和物理页面的映射关系,用户进程访问虚拟地址即访问匿名页面的内容。
匿名页面的换出
假设现在系统内存紧张,需要回收一些页面来释放内存。匿名页面刚分配时会加入活跃LRU链表(LRU_ACTIVE_ANON)的头部,在活跃LRU链表移动一段时间后,该匿名页面到达活跃LRU链表的尾部,shrink_active_list()函数把该页面加入不活跃LRU链表(LRU_INACTIVE_ANON)。 shrink_inactive_list()函数扫描不活跃LRU链表。 第一次扫描不活跃LRU链表时,shrink_page_list()->add_to_swap()函数会为该页面分配交换分区。 此时匿名页面的_refcount、_mapcount和flags的状态如下。
匿名页面的换入
匿名页面被换出到交换分区后,如果应用程序需要读写这个页面,则会发生缺页中断,由于PTE中的present位显示该页面不在内存中,但PTE不为空,说明该页面在交换分区中,因此调用do_swap_page()函数重新读取该页面的内容。
匿名页面的销毁
当用户进程关闭或者退出时,会扫描这个用户进程所有的VMA,并会清除这些VMA上所有的映射。如果符合释放标准,相关页面会被释放。本例中的匿名页面只映射了父进程的VMA,所以这个页面也会被释放。图5.15所示是匿名页面的生命周期。