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上所有的的映射,如果符合释放标准,相关页面会被释放

#驱动开发##如果再来一次,你还会选择这个工作吗?##嵌入式##牛客创作赏金赛#
全部评论

相关推荐

入职阿里淘天交易业务仅1个月的客户端研发发出哀嚎∶参加26届校招招人实在是太难了!手中大把客户端/前端HC发不出去!甚至一天收不到几份简历 💔阿里淘天-交易业务-客户端/前端大量HC急招!🔥 急到裂开!---你,无需考虑!✅ 业务价值?无需考虑!🛒 负责淘宝下单、订单、购物车、物流、买家秀全链路🛡️ 守护亿级用户交易安全,⚡️ 优化亿级用户交易体验!(你的代码每分钟被全国人盘!)✅ 团队氛围?无需考虑!😄 工作轻松快乐,不PUSH不PUA🧋 周周奶茶水果畅享 → 🍖 月月聚餐团建 → ✈️ 年年outing旅游✅ 技术栈?无需考虑!📱 客户端(Android/iOS/鸿蒙) + 🌐前端HC管饱!🔄 Java?秒转安卓! | C++?直通iOS/鸿蒙!💡 全部技术栈,照单全收!✅ 学历筛选?无需考虑!🎯 有大厂实习经历?私我!看能力直开绿灯!✅ 工作地点?无需考虑!📍 杭州/北京任选!工位看西湖还是望京?您说了算!✅ 各种福利?无需考虑!🏆 阿里淘天老牌大厂,房补餐补年假全拉满!(真·满到溢出)✅ 稳定性?无需考虑!🛡️ 核心交易业务,稳过东方明珠地基!✅ 投递机会?无需考虑!📈 现在投=秋招复活甲+1!✅ 流程速度?无需考虑!⚡️ 私信我内推 → 全程人工火箭推进!光速走完全流程!✅ 各种问题?无需考虑!💻 高强度冲浪选手,25小时在线!问就是秒回!(比AI还AI)---还有80%的福利...🤫 篇幅所限不再赘述加入淘天交易业务,亲自解锁!---💥 速戳我头像 → 私信简历!(错过≈摩托变单车!)
投递阿里巴巴集团等公司10个岗位
点赞 评论 收藏
分享
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务