Linux内存管理:(八)页面迁移

文章说明:

  • Linux内核版本:5.0

  • 架构:ARM64

  • 参考资料及图片来源:《奔跑吧Linux内核》

  • Linux 5.0内核源码注释仓库地址:

    zhangzihengya/LinuxSourceCode_v5.0_study (github.com)

1. 可迁移页面

页面迁移机制支持两大类内存页面:

  • 传统LRU页面,如匿名页面和文件映射页面
  • 非LRU页面,如zsmalloc或者virtio-balloon页面,以virtio-balloon页面为例,它也有页面迁移的需求,之前的做法是在virtio-balloon驱动中进行迁移操作和相应的逻辑。如果其他的驱动也想做类似的页面迁移,那么它们就不能复用与virtio-balloon驱动相关的代码,必须重新写一套代码,这样会造成很多代码的重复和冗余。为了解决这个问题,内存管理的页面迁移机制提供相应的接口来支持这些非LRU页面的迁移。

2. 页面迁移流程

页面迁移的本质是将页面的内容迁移到新的页面。这个过程中会分配新页面,将旧页面的内容复制到新页面,断开旧页面的映射关系,并把映射关系映射到新页面,最后释放旧页面。页面迁移的整个流程图如下所示:

在这里插入图片描述

为了使读者有更真切的理解,下文将根据流程图围绕源代码进行讲解这个过程。

页面迁移(page migration)在Linux内核的主函数是migrate_pages()函数:

// 页面迁移的主函数
// from: 将要迁移页面的链表
// get_new_page: 申请新内存的页面的函数指针
// put_new_page: 迁移失败时释放目标页面的函数指针
// private: 传递给 get_new_page 的参数
// mode:迁移模式
// reason: 迁移的原因
int migrate_pages(struct list_head *from, new_page_t get_new_page,free_page_t put_new_page, unsigned long private,enum migrate_mode mode, int reason)
{...rc = unmap_and_move(get_new_page, put_new_page,private, page, pass > 2, mode,reason);....
}

migrate_pages()->unmap_and_move()

static ICE_noinline int unmap_and_move(new_page_t get_new_page,free_page_t put_new_page,unsigned long private, struct page *page,int force, enum migrate_mode mode,enum migrate_reason reason)
{...// 分配一个新的页面newpage = get_new_page(page, private);if (!newpage)return -ENOMEM;if (page_count(page) == 1) {...// 刚分配的页面需要调用 put_new_page() 回调函数if (put_new_page)put_new_page(newpage, private);...}// 尝试迁移页面到新分配的页面中rc = __unmap_and_move(page, newpage, force, mode);if (rc == MIGRATEPAGE_SUCCESS)set_page_owner_migrate_reason(newpage, reason);out:// 若返回值不等于 -EAGAIN,说明可能迁移没成功if (rc != -EAGAIN) {...}// 若返回值等于 MIGRATEPAGE_SUCCESS,说明迁移成功,释放页面if (rc == MIGRATEPAGE_SUCCESS) {...// 处理迁移没成功的情况,把页面重新添加到可移动的页面里。释放刚才新分配的页面} else {....}return rc;
}

migrate_pages()->unmap_and_move()->__unmap_and_move()

// page:被迁移的页面
// newpage: 迁移页面的目的地
// force: 表示是否强制迁移。在 migrate_pages() 中,当尝试次数大于 2 时,会设置为 1(0 表示强制迁移)
// mode: 迁移模式
static int __unmap_and_move(struct page *page, struct page *newpage,int force, enum migrate_mode mode)
{...// __PageMovable() 函数用于判断这个页面是否属于非 LRU 页面,它是通过 page 数据结构中的 mapping 成员// 是否设置了 PAGE_MAPPING_MOVABLE 标志位来判断的bool is_lru = !__PageMovable(page);// trylock_page() 尝试给页面加锁,返回 true 则表示当前进程已经成功获取锁if (!trylock_page(page)) {// 如果尝试获取也锁不成功// 若满足 !force || mode == MIGRATE_ASYNC,则直接忽略这个页面,因为这种情况下没有必要睡眠等待页面释放锁if (!force || mode == MIGRATE_ASYNC)goto out;// 如过当前进程设置了 PF_MEMALLOC 标志位,表示当前进程可能处于直接内存压缩的内核路径上,通过睡眠等待页锁是// 是不安全的,所以直接忽略该页面if (current->flags & PF_MEMALLOC)goto out;// 其他情况下只能等待页锁的释放lock_page(page);}// 处理正在回写的页面,即设置了 PG_writeback 标志位的页面if (PageWriteback(page)) {// 只有当页面迁移的模式为 MIGRATE_ASYNC 或者 MIGRAIE_SYNC_LIGHT 且设置强制迁移(force=1)// 时,才会等待这个页面回写完成,否则直接忽略该页面,该页面不会被迁移switch (mode) {case MIGRATE_SYNC:case MIGRATE_SYNC_NO_COPY:break;default:rc = -EBUSY;goto out_unlock;}if (!force)goto out_unlock;// 等待页面回写完成wait_on_page_writeback(page);}// 处理匿名页面的 anon_vma 可能被释放的特殊情况,因为接下来 try_to_unmap() 函数运行完成时,// page->_mapcount 会变成 0。在页面迁移的过程中,我们无法知道 anon_vma 数据结构是否被释放了if (PageAnon(page) && !PageKsm(page))// page_get_anon_vma() 增加 anon_vma->refcount 引用计数防止其被其他进程释放anon_vma = page_get_anon_vma(page);// 尝试给 newpage 申请锁if (unlikely(!trylock_page(newpage)))goto out_unlock;// 若这个页面属于非 LRU 页面if (unlikely(!is_lru)) {// move_to_new_page() 函数中会通过驱动程序注册 migratepage() 函数来进行页面迁移rc = move_to_new_page(newpage, page, mode);goto out_unlock_both;}// 接下来的代码用于处理传统的 LRU 页面if (!page->mapping) {// 处理一个特殊情况。当一个交换缓存页面从交换分区被读取之后,它会被添加到 LRU 链表里,我们把它当作// 一个交换缓存页面。但是它还没有设置 RMAP,因此 page->mapping 为空。若调用 try_to_unmap() 可能// 会触发内核岩机,因此这里做特殊处理,并跳转到 out_unlock_both 标签处。VM_BUG_ON_PAGE(PageAnon(page), page);if (page_has_private(page)) {try_to_free_buffers(page);goto out_unlock_both;}// page_mapped() 判断该页面的 _mapcount 是否大于或等于 0,若大于或等于 0,说明有用户 PTE 映射该页面} else if (page_mapped(page)) {VM_BUG_ON_PAGE(PageAnon(page) && !PageKsm(page) && !anon_vma,page);// 对于有用户态进程地址空间映射的页面,调用 try_to_unmap() 解除页面所有映射的用户 PTEtry_to_unmap(page,TTU_MIGRATION|TTU_IGNORE_MLOCK|TTU_IGNORE_ACCESS);page_was_mapped = 1;}// 对于已经解除完所有用户 PTE 映射的页面,调用 move_to_new_page() 把它们迁移到新分配的页面if (!page_mapped(page))rc = move_to_new_page(newpage, page, mode);if (page_was_mapped)// 迁移页表// 对于迁移页面失败的情况,调用 remove_migration_ptes() 删除迁移的 PTEremove_migration_ptes(page,rc == MIGRATEPAGE_SUCCESS ? newpage : page, false);out_unlock_both:unlock_page(newpage);
out_unlock:/* Drop an anon_vma reference if we took one */if (anon_vma)put_anon_vma(anon_vma);unlock_page(page);
// 处理退出情况
out:if (rc == MIGRATEPAGE_SUCCESS) {// 对于非 LRU 页面,调用 put_page() 把 newpage 的 _refcount 减 1if (unlikely(!is_lru))put_page(newpage);// 对于传统 LRU 页面,把 newpage 添加到 LRU 链表中elseputback_lru_page(newpage);}return rc;
}

migrate_pages()->unmap_and_move()->__unmap_and_move()->move_to_new_page()

// 用于迁移旧页面到新页面中
static int move_to_new_page(struct page *newpage, struct page *page,enum migrate_mode mode)
{...// 判断页面是否属于传统的的 LRU 页面。通过 page 数据结构中的 mapping 成员// 是否设置了 PAGE_MAPPING_MOVABLE 标志位来判断的bool is_lru = !__PageMovable(page);...// 返回页面的 mappingmapping = page_mapping(page);if (likely(is_lru)) {// 若页面属于传统的 LRU 链表的页面,按以下几种情况处理// 若 mapping 为空,说明该页面是匿名页面但是没有分配交换缓存,那么调用 migrate_page() 函数来迁移页面if (!mapping)rc = migrate_page(mapping, newpage, page, mode);// 该页面实现了 migratepage(),那么直接调用 mapping->a_ops->migratepage() 来迁移页面else if (mapping->a_ops->migratepage)rc = mapping->a_ops->migratepage(mapping, newpage,page, mode);// 其他情况下elserc = fallback_migrate_page(mapping, newpage,page, mode);// 对于页面属于非 LRU 页面的情况,直接调用驱动程序为这个页面注册的 migratepage() 来迁移页面} else {...rc = mapping->a_ops->migratepage(mapping, newpage,page, mode);WARN_ON_ONCE(rc == MIGRATEPAGE_SUCCESS &&!PageIsolated(page));}// 处理迁移成功的情况if (rc == MIGRATEPAGE_SUCCESS) {...}
out:return rc;
}

migrate_pages()->unmap_and_move()->__unmap_and_move()->remove_migration_ptes()->remove_migration_pte()

static bool remove_migration_pte(struct page *page, struct vm_area_struct *vma,unsigned long addr, void *old)
{...// page_vma_mapped_walk() 遍历页表,通过虚拟地址找到对应的 PTEwhile (page_vma_mapped_walk(&pvmw)) {...// 根据新页面和 vma 属性来生成一个 PTEpte = pte_mkold(mk_pte(new, READ_ONCE(vma->vm_page_prot)));...if (PageHuge(new)) {...} else{// 把新生成的 PTE 的内容写回到原来映射的页面中,完成 PTE 的迁移,这样用户进程地址空间就可以// 通过原来的 PTE 访问新页面set_pte_at(vma->vm_mm, pvmw.address, pvmw.pte, pte);// 把新页面添加到 RMAP 系统中if (PageAnon(new))page_add_anon_rmap(new, vma, pvmw.address, false);elsepage_add_file_rmap(new, false);}...// 更新相应的高速缓存update_mmu_cache(vma, pvmw.address, pvmw.pte);}return true;
}

migrate_pages()->unmap_and_move()->__unmap_and_move()->move_to_new_page()->migratepage()

struct address_space_operations {...int (*migratepage) (struct address_space *,struct page *, struct page *, enum migrate_mode);...
};

migratepage()方法会迁移旧页面的内容到新页面中,并且设置page对应的成员和属性。驱动实现的migratepage()方法在完成页面迁移之后需要显式地调用__ClearPageMovable()函数清除PAGE_MAPPING_MOVABLE标志位。如果迁移页面不成功,返回-EAGAIN,那么根据页面迁移机制会重试一次。若返回其他错误值,那么根据页面迁移机制就会放弃这个页面。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/615881.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于集成学习算法XGBoost农作物产量可视化分析预测系统

文章目录 基于集成学习算法XGBoost农作物产量可视化分析预测系统一、项目简介二、开发环境三、项目技术四、功能结构五、功能实现模型构建封装类用于网格调参训练模型系统可视化数据请求接口模型评分 0.5*mse 六、系统实现七、总结 基于集成学习算法XGBoost农作物产量可视化分析…

Netty通信中的粘包半包问题(一)

前言 我们在日常开发过程中,客户端和服务端的连接大多使用的是TCP协议,因为我们要保证数据的可靠传输, 当网络中出现丢包时要求,要求数据包的发送端重传给接收端。而TCP是一种面向连接的传输层协议, 当使用TCP进行传输时&#xf…

vue前端开发自学,插槽练习第二次,name属性的使用

vue前端开发自学,插槽练习第二次,name属性的使用!可以使用name属性,来自定义一个名字,这样,就可以在一个组件内同时出现多个插槽的内容了。在子组件内接收的时候,很简答,只需要在slot标签里面加上name“mz”&#xff1…

业务向——基于多多进宝平台的CPS

业务向——基于多多进宝平台的CPS 导读小试牛刀商品活动推广商品详情获取频道推广订单获取及和用户绑定小结 导读 多多进宝是拼多多的开放平台,为广大商家和推广者提供了一个机会,通过推广拼多多的商品来实现收益。多多进宝的CPS(按效果付费…

[JAVA数据结构] 认识 Iterable、Collection、List 的常见方法签名以及含义

目录 (一)Iterable 1. 介绍 2. 常见方法 (二)Collection 1. 介绍 2. 常见方法 (三) List 1. 介绍 2. 常见方法 总结 (一) Iterable 1. 介绍 Iterable接口是Java中的一个接口,它是集合框架中的根接口之一。Iterable接口表示实现了迭代功能,即可以通过迭…

鸿蒙HarmonyOS兼容JS的类Web开发

鸿蒙HarmonyOS兼容JS的类Web开发 文章目录 鸿蒙HarmonyOS兼容JS的类Web开发文件组织目录结构文件访问规则媒体文件格式 js标签配置pageswindow示例 app.js应用生命周期应用对象6 HML语法参考页面结构数据绑定普通事件绑定冒泡事件绑定5捕获事件绑定5列表渲染条件渲染逻辑控制块…

HTML5 画布绘制海报

需求: 1、根据用户填写的联系人信息:姓名、手机号及微信二维码,生成海报,并下载保存到本地; 2、可多个海报切换供用户选择 实现:使用html5实现,为方便用户,做的手机网站的样式&am…

图形化编程:下一代的创新教育工具

在科技日新月异的今天,编程已经成为了一项必备的技能。然而,传统的编程语言对于许多人来说仍然是一项挑战,尤其是对于年轻的学习者。为了解决这个问题,图形化编程应运而生,它以其直观、易理解和易操作的特点&#xff0…

【MySQL】本地创建MySQL数据库详解

文章目录 下载MySQL安装重置密码本地连接 下载MySQL 下载网址:https://dev.mysql.com/downloads/mysql/ 安装 将下载好的压缩包解压到D盘。 在解压好的文件夹中创建my.ini文件。 将以下代码复制粘贴到创建好的my.ini文件中。注意修改文件路径。 [mysqld] #设置…

PHP留言板实现

完整教程PHP留言板 登陆界面 一个初学者的留言板(登录和注册)_php留言板登录注册-CSDN博客 留言板功能介绍 百度网盘 请输入提取码 进入百度网盘后,输入提取码:knxt,即可下载项目素材和游客访问页面的模板文件。 &…

讯飞医疗“单飞”上市,科大讯飞的“AI算盘”还灵吗?

近年来,人工智能的各种应用呈现出爆炸式的增长态势,“AI”模式在众多领域中不断展现出其多元化的潜力。就在最近,国内的人工智能技术领军企业也迈出了重要的一步。一直被誉为国内“AI之光”的科大讯飞在1月9日晚间发布了一份公告,…

jmeter--常用插件及服务器监控(14)

一.jmeter插件管理器 下载jmeter插件管理器:plugins-manager.jar 下载plugins-manager.jar并将其放入lib/ext目录,然后重启JMeter。 插件管理界面 打开选项->Plugins Manager(界面见下图),“Installed Plugns”…

Windows pip install -r requirement.txt 太慢

解决方案一: 1、在虚拟环境中切换下载的源: pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple 2、当出现有pip.txt文件写入时,再执行pip安装 pip install -r requirement.txt 解决方案二: 1、在…

使用Openssl生成Https免费证书以及Nginx配置

1 证书和私钥的生成 1.创建服务器证书密钥文件 server.key: openssl genrsa -des3 -out server.key 2048 输入密码,确认密码,自己随便定义,但是要记住,后面会用到。 2.创建服务器证书的申请文件 server.csr openssl r…

jmeter接口自动化测试如何部署jenkins

首先,保证本地安装并部署了jenkins,jmeter,xslproc 我搭建的自动化测试框架是jmeterjenkinsxslproc ---注意:原理是,jmeter自生成的报告jtl文件,通过xslproc工具,再结合jmeter自带的模板修改&…

React Native 环境安装

Notion – The all-in-one workspace for your notes, tasks, wikis, and databases. 搭建开发环境 React Native 中文网 Homebrew(包管理器) → rvm(ruby版本管理) → ruby → cocoapods 安装 Homebrew Homebrew /bin/ba…

网安入门14-文件包含(file:// )

​ 什么是文件包含漏洞——来自ChatGPT4 文件包含漏洞是指应用程序在加载文件时,允许用户控制被加载文件的名称,从而导致恶意代码的执行或敏感信息的泄露。文件包含漏洞主要分为两种: 本地文件包含漏洞(LFI) &#…

jmeter和meterSphere如何使用第三方jar包

工具引用jar包语言都是beanshell 问题起因:metersphere 接口自动化实现过程中,如何实现字符串加密且加密方法依赖第三方库; 使用语言:beanshell脚本语言,java语言 使用工具:idea jmeter metersphere 1.首…

基于K-Means聚类算法与随机森林模型评估信贷风险客户【500010101】

项目背景 本数据集来自一家德国银行,由加州大学霍夫曼教授于 2016 年收集整理,每条记录代表了一个接受银行信贷的客户,这也就说明了,这些客户都是通过了贷款申请的,通过可视化分析对数据进行初步探索,并利…

蓝桥杯基础知识3 memset()

蓝桥杯基础知识3 memset() #include <bits/stdc.h> using namespace std;int main(){int a[5]; //随机数for(int i 0;i < 5; i)cout << a[i] << \n;cout << \n;memset(a, 0, sizeof a); //0for(int i 0;i < 5; i)cout << a[i] << …