Linux内核6.6 内存管理(13)匿名页面的生命周期
0.匿名页面和paga cache
维度 | 匿名页面 | page cache |
是否可丢弃 | OS不能随便丢弃 | 干净页面可以随便丢弃 |
磁盘文件关联 | 无,“凭空” 分配的内存 | 有,是文件数据的内存缓存 |
数据持久化 | 进程退出 / 断电后丢失 | 可写回磁盘,文件内容持久保存 |
内存回收策略 | 优先被换出到 swap(因无文件备份) | 优先写回磁盘,若磁盘繁忙才换出到 swap |
典型场景 | 程序运行时的临时数据(变量、栈) | 文件读写操作(如日志读取、文件修改) |
内核管理模块 | 由内存分配器(如 slab、伙伴系统)管理 | 由文件系统(如 ext4)和页缓存机制管理 |
1.匿名页面的诞生
- malloc/mmap 接口分配的内存 -> do_anonymous_page ()
- 写时复制:当缺页中断出现写保护错误时,新分配的页面是匿名页面
- (1)do_wp_page ()
- 只读的 special 映射的页,例如映射到 zero page 的页面。
- 非单身匿名页面(有多个映射的匿名页面,即 page->_mapcount > 0)
- 只读的私用映射的 page cache。
- KSM 页面。
- (2)do_cow_page ()
- 共享的匿名页面(shared anonymous mapping,shmm)
- 上述这些情况在发生写时复制时会新分配匿名页面。
- do_swap_page (),从 swap 分区读回数据时会新分配匿名页面。
- 迁移页面。
2.使用场景
2.1匿名页面的使用
匿名页面在缺页中断中分配完成之后,就建立了进程虚拟地址空间VMA和物理页面的映射关系,用户进程访问虚拟地址即访问到匿名页面的内容
2.2匿名页面的换出
https://elixir.bootlin.com/linux/v6.6/source/mm/vmscan.c#L1705
2.3匿名页面的换入
匿名页面被换出到swap分区后,如果应用程序需要读写这个页面,缺页中断发生,因为pte中的present比特位显示该页不在内存中,但是pte表项不为空,说明该页在swap分区中,可以调用do_swap_page重新读入该页的内容。
https://elixir.bootlin.com/linux/v6.6/source/mm/memory.c#L3722
在这之前先科普一下swap,swap 区域属于存储设备(通常是硬盘或固态硬盘)上的一块专门空间,从你的硬盘 / SSD 里划分出来的一块 “特殊分区” 或文件,专门用来临时存放内存中暂时不用的数据(当内存不够时)。本质上,它依托于物理存储设备存在,只是功能上模拟了部分内存的作用(尽管速度慢很多)。
struct vm_area_struct *vma = vmf->vma; struct folio *swapcache, *folio = NULL; struct page *page; struct swap_info_struct *si = NULL; rmap_t rmap_flags = RMAP_NONE; bool exclusive = false; swp_entry_t entry; pte_t pte; vm_fault_t ret = 0; void *shadow = NULL; if (!pte_unmap_same(vmf)) goto out; 初始化核心变量和关键检查pte_unmap_same(vmf):验证当前页表项(PTE)是否与函数入口时一致。由于函数运行期间可能有其他进程修改 PTE(如并发缺页),若不一致则直接跳转至出口(out),避免操作无效地址。
entry = pte_to_swp_entry(vmf->orig_pte);//提取交换页entry if (unlikely(non_swap_entry(entry))) {//处理非标准交换页,包括:到22行 if (is_migration_entry(entry)) { migration_entry_wait(vma->vm_mm, vmf->pmd, vmf->address); } else if (is_device_exclusive_entry(entry)) { vmf->page = pfn_swap_entry_to_page(entry); ret = remove_device_exclusive_entry(vmf); } else if (is_device_private_entry(entry)) { // 设备私有页迁移到内存的处理 // ...(省略细节) } else if (is_hwpoison_entry(entry)) { ret = VM_FAULT_HWPOISON; } else if (is_pte_marker_entry(entry)) { ret = handle_pte_marker(vmf); } else { print_bad_pte(vma, vmf->address, vmf->orig_pte, NULL); ret = VM_FAULT_SIGBUS; } goto out; } 迁移项(进程迁移时的临时标记):等待迁移完成(migration_entry_wait)。 设备独占 / 私有项(如 GPU 等设备管理的内存):移除独占标记或迁移到系统内存。 硬件毒页(损坏页面):返回硬件错误(VM_FAULT_HWPOISON)。 无效项:输出错误日志并返回总线错误(VM_FAULT_SIGBUS)。
si = get_swap_device(entry); //锁定交换设备,防止操作期间交换设备被卸载,保证读取安全 if (unlikely(!si)) goto out; folio = swap_cache_get_folio(entry, vma, vmf->address);//优先从交换缓存中获取页面 if (folio) page = folio_file_page(folio, swp_offset(entry)); swapcache = folio; if (!folio) { //缓存未命中 if (data_race(si->flags & SWP_SYNCHRONOUS_IO) && __swap_count(entry) == 1) { // 直接分配新页并从交换空间读取(同步IO设备) folio = vma_alloc_folio(...); // ...(锁定页面、充电、读取数据等) } else { // 普通设备:预读机制读取 page = swapin_readahead(entry, GFP_HIGHUSER_MOVABLE, vmf); folio = page ? page_folio(page) : NULL; } if (!folio) { // 读取失败:检查PTE是否仍有效,无效则释放资源,有效则返回OOM vmf->pte = pte_offset_map_lock(...); if (likely(vmf->pte && pte_same(...))) ret = VM_FAULT_OOM; goto unlock; } ret = VM_FAULT_MAJOR; // 标记为主缺页(从磁盘读取) } else if (PageHWPoison(page)) { ret = VM_FAULT_HWPOISON; goto out_release; }
ret |= folio_lock_or_retry(folio, vmf);//锁定页面,防止并发修改 if (ret & VM_FAULT_RETRY) goto out_release; if (swapcache) { if (unlikely(!folio_test_swapcache(folio) || page_swap_entry(page).val != entry.val)) goto out_page; // KSM(内核同页合并)相关处理:可能需要复制页面 page = ksm_might_need_to_copy(page, vma, vmf->address); // ...(处理KSM返回的错误) } folio_throttle_swaprate(folio, GFP_KERNEL); // 限制交换速率,避免系统过载 vmf->pte = pte_offset_map_lock(vma->vm_mm, vmf->pmd, vmf->address, &vmf->ptl); if (unlikely(!vmf->pte || !pte_same(ptep_get(vmf->pte), vmf->orig_pte))) goto out_nomap; //再次校验页面,因为并发场景下,PTE可能已被修改 if (unlikely(!folio_test_uptodate(folio))) { //检测页面是否就绪 ret = VM_FAULT_SIGBUS; goto out_nomap; }
// 检查页面状态一致性(BUG_ON用于调试,确保无逻辑错误) BUG_ON(!folio_test_anon(folio) && folio_test_mappedtodisk(folio)); BUG_ON(folio_test_anon(folio) && PageAnonExclusive(page)); // 判断页面是否独占(用于写权限处理) exclusive = pte_swp_exclusive(vmf->orig_pte); // ...(根据页面是否在缓存、是否有写回等调整exclusive) arch_swap_restore(entry, folio); // 恢复架构特定元数据(如某些CPU的页面属性) swap_free(entry); // 释放交换项(减少交换空间引用计数) if (should_try_to_free_swap(folio, vma, vmf->flags)) folio_free_swap(folio); // 若无需缓存,释放交换缓存 // 更新内存统计(匿名页+1,交换项-1) inc_mm_counter(vma->vm_mm, MM_ANONPAGES); dec_mm_counter(vma->vm_mm, MM_SWAPENTS); // 创建新PTE并设置权限 pte = mk_pte(page, vma->vm_page_prot); if (!folio_test_ksm(folio) && (exclusive || folio_ref_count(folio) == 1)) { if (vmf->flags & FAULT_FLAG_WRITE) { pte = maybe_mkwrite(pte_mkdirty(pte), vma); // 标记为可写+脏页 vmf->flags &= ~FAULT_FLAG_WRITE; } rmap_flags |= RMAP_EXCLUSIVE; } // 设置软脏位、用户fault标记 if (pte_swp_soft_dirty(vmf->orig_pte)) pte = pte_mksoft_dirty(pte); if (pte_swp_uffd_wp(vmf->orig_pte)) pte = pte_mkuffd_wp(pte); // 建立反向映射(记录虚拟地址到物理页面的映射,用于内存回收) if (unlikely(folio != swapcache && swapcache)) { page_add_new_anon_rmap(page, vma, vmf->address); } else { page_add_anon_rmap(page, vma, vmf->address, rmap_flags); } set_pte_at(vma->vm_mm, vmf->address, vmf->pte, pte); // 更新页表,完成映射 arch_do_swap_page(vma->vm_mm, vma, vmf->address, pte, vmf->orig_pte); // 架构特定处理
folio_unlock(folio); // 解锁页面,允许其他进程访问 if (folio != swapcache && swapcache) { folio_unlock(swapcache); folio_put(swapcache); // 释放交换缓存 } if (vmf->flags & FAULT_FLAG_WRITE) { ret |= do_wp_page(vmf); // 处理写时复制(若为写操作) if (ret & VM_FAULT_ERROR) ret &= VM_FAULT_ERROR; goto out; } update_mmu_cache_range(vmf, vma, vmf->address, vmf->pte, 1); // 更新MMU缓存,使映射生效 unlock: if (vmf->pte) pte_unmap_unlock(vmf->pte, vmf->ptl); // 解锁并取消PTE映射 out: if (si) put_swap_device(si); // 释放交换设备引用 return ret; // 错误处理路径(out_nomap/out_page/out_release):释放页面、PTE锁、交换设备等资源
2.4匿名页面的释放
当用户进程关闭或者退出时,会扫描这个用户进程所有的VMAs, 并会清除这些VMA上所有的的映射,如果符合释放标准,相关页面会被释放