3.3 windows,ReactOS系统中页面的换出----1

系列文章目录

文章目录

  • 系列文章目录
  • 3.3 页面的换出
  • MiBalancerThread()
  • MmTrimUserMemory()
  • MmPageOutVirtualMemory()


3.3 页面的换出

在前一节中我们看到,如果有映射的页面已经被倒换到磁盘上即倒换文件中,那么对这个页面的访问就会引起一次缺页异常,而相应的异常处理程序就会从磁盘上把这个页面倒换进来。这自然就会产生一个问题:这个页面是在什么时候又是怎样跑到磁盘上去的呢?当然,页面(的内容)不会自己跑到磁盘上,而得有个行为主体将其倒出到磁盘上,这个行为主体就是内核线程MiBalancerThread() .

MiBalancerThread()


VOID STDCALL
MiBalancerThread(PVOID Unused)
{PVOID WaitObjects[2];....WaitObjects[0] = &MiBalancerEvent;//通过Event唤醒WaitObjects[1] = &MiBalancerTimer;//通过定时器唤醒while (1){Status = KeWaitForMultipleObjects(2,WaitObjects,WaitAny,Executive,KernelMode,FALSE,NULL,NULL);if (Status == STATUS_SUCCESS){//因为事件而被(某个线程)唤醒/* MiBalancerEvent */CHECKPOINT;while (MmStats.NrFreePages < MiMinimumAvailablePages + 5){//循环,知道有一个数量的空闲物理页面for (i = 0; i < MC_MAXIMUM; i++){if (MiMemoryConsumers[i].Trim != NULL){//调用内存消费者的修剪函数NrFreedPages = 0;Status = MiMemoryConsumers[i].Trim(MiMinimumPagesPerRun, 0, &NrFreedPages);if (!NT_SUCCESS(Status)){KEBUGCHECK(0);}}}}InterlockedExchange(&MiBalancerWork, 0);CHECKPOINT;}else if (Status == STATUS_SUCCESS + 1){/* MiBalancerTimer *///因超时而被唤醒ShouldRun = MmStats.NrFreePages < MiMinimumAvailablePages + 5 ? TRUE : FALSE;for (i = 0; i < MC_MAXIMUM; i++)//对于所有的内存消费者{if (MiMemoryConsumers[i].Trim != NULL){NrPagesUsed = MiMemoryConsumers[i].PagesUsed;if (NrPagesUsed > MiMemoryConsumers[i].PagesTarget || ShouldRun){//本消费者占用页面数已超过配额,或总库存已降到危险点if (NrPagesUsed > MiMemoryConsumers[i].PagesTarget){Target = max (NrPagesUsed - MiMemoryConsumers[i].PagesTarget,MiMinimumPagesPerRun);}else{Target = MiMinimumPagesPerRun;//每次至少修剪的数量}NrFreedPages = 0;//执行本消费者的修剪函数Status = MiMemoryConsumers[i].Trim(Target, 0, &NrFreedPages);if (!NT_SUCCESS(Status)){KEBUGCHECK(0);}}}}}else{//因其他原因被唤醒,不应发生DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status);KEBUGCHECK(0);}}
}

这个线程平时都在睡眠,但是周期性地通过事件MiBalancerTimer 被定时器唤醒。此外,这个线程也可以通过事件 MiBalancerEvent 被唤醒,这发生在需要分配物理页面却发现库存不足的时候每当这个线程被唤醒的时候,它就借助若干个内存“消费者(Consumer)”的“修剪函数”对内存加以“修剪(Trim)”。所谓修剪,就是把一些判断为暂时不会被访问的页面(的内容)倒换出去,腾出所占据的物理页面另行分配,

如前所述,所谓内存“消费者”并不是指实际占用着物理页面的进程,而是指各种不同的用途例如用户空间的页面映射,内核中的可倒换物理页面池、不可倒换物理页面池,磁盘上扇区内容的高速缓存等。内核中有个结构数组MiMemoryConsumers[],其中的每个元素都代表一个“消费者”其下标可以是:

#define MC_CACHE                            (0)
#define MC_USER                             (1)
#define MC_PPOOL                            (2)
#define MC_NPPOOL                           (3)
#define MC_MAXIMUM                          (4)

显然,每一种用途即“消费者”都应该提供自己的修剪函数:但是目前ReactOS只为MC_CACHE和MC_USER提供了修剪函数。换言之,另两种用途的页面是不让修剪的。其中用户空间页面所映射物理页面的修剪函数为MmTrimUserMemory()。

MmTrimUserMemory()

[MiBalancerThread()>MmTrimUserMemory()]/* FUNCTIONS *****************************************************************/NTSTATUS
MmTrimUserMemory(ULONG Target, ULONG Priority, PULONG NrFreedPages)
{PFN_TYPE CurrentPage;PFN_TYPE NextPage;NTSTATUS Status;(*NrFreedPages) = 0;CurrentPage = MmGetLRUFirstUserPage();//运用LRU算法找到第一个可以倒出的页面while (CurrentPage != 0 && Target > 0){NextPage = MmGetLRUNextUserPage(CurrentPage);//下一个可以倒出的页面//倒出物理页面 CurrentPageStatus = MmPageOutPhysicalAddress(CurrentPage);if (NT_SUCCESS(Status)){DPRINT("Succeeded\n");Target--;(*NrFreedPages)++;}else if (Status == STATUS_PAGEFILE_QUOTA)//已超过倒换文件的容量配额{MmSetLRULastPage(CurrentPage);//下次再来}CurrentPage = NextPage;}return(STATUS_SUCCESS);
}

参数Target是要求修剪的页面数量,NrFreedPages用来返回实际修剪的数量,另一个参数Priority实际上没有被用到。
修剪的对象是“最近最少被用到”的页面,相应的算法为LRU。与MiMemoryConsumers[]平行,内核中还有个队列头数组 UsedPageListHeads[],也是以 MC_USER 等为下标,凡是被分配用于某个消费者的物理页面,其数据结构都按LRU的次序挂在其队列中。LRU是一种常用的算法,我们就不深入到这里面去了。
找到修剪对象的物理页面号之后,就通过MmPageOutPhysicalAddressO)将其倒换出去。

MmPageOutVirtualMemory()

[MiBalancerThread()> MmTrimUserMemory() > MmPageOutPhysicalAddress()>MmPageOutVirtualMemory()]NTSTATUS
NTAPI
MmPageOutVirtualMemory(PMADDRESS_SPACE AddressSpace,PMEMORY_AREA MemoryArea,PVOID Address,PMM_PAGEOP PageOp)
{PFN_TYPE Page;BOOLEAN WasDirty;SWAPENTRY SwapEntry;NTSTATUS Status;DPRINT("MmPageOutVirtualMemory(Address 0x%.8X) PID %d\n",Address, AddressSpace->Process->UniqueProcessId);/** Check for paging out from a deleted virtual memory area.*/if (MemoryArea->DeleteInProgress){PageOp->Status = STATUS_UNSUCCESSFUL;KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);MmReleasePageOp(PageOp);return(STATUS_UNSUCCESSFUL);}/** Disable the virtual mapping.//关断目标页面的映射*/MmDisableVirtualMapping(AddressSpace->Process, Address,&WasDirty, &Page);if (Page == 0){KEBUGCHECK(0);}/** Paging out non-dirty data is easy.*/if (!WasDirty){//页面是干净的,物理页面空白或其内容与倒换文件中的对应页面完全相同MmLockAddressSpace(AddressSpace);MmDeleteVirtualMapping(AddressSpace->Process, Address, FALSE, NULL, NULL);MmDeleteAllRmaps(Page, NULL, NULL);if ((SwapEntry = MmGetSavedSwapEntryPage(Page)) != 0){MmCreatePageFileMapping(AddressSpace->Process, Address, SwapEntry);MmSetSavedSwapEntryPage(Page, 0);}MmUnlockAddressSpace(AddressSpace);MmReleasePageMemoryConsumer(MC_USER, Page);PageOp->Status = STATUS_SUCCESS;KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);MmReleasePageOp(PageOp);return(STATUS_SUCCESS);}/** If necessary, allocate an entry in the paging file for this page*/SwapEntry = MmGetSavedSwapEntryPage(Page);if (SwapEntry == 0){SwapEntry = MmAllocSwapPage();if (SwapEntry == 0){MmShowOutOfSpaceMessagePagingFile();MmEnableVirtualMapping(AddressSpace->Process, Address);PageOp->Status = STATUS_UNSUCCESSFUL;KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);MmReleasePageOp(PageOp);return(STATUS_PAGEFILE_QUOTA);}}/** Write the page to the pagefile*/Status = MmWriteToSwapPage(SwapEntry, Page);if (!NT_SUCCESS(Status)){DPRINT1("MM: Failed to write to swap page (Status was 0x%.8X)\n",Status);MmEnableVirtualMapping(AddressSpace->Process, Address);PageOp->Status = STATUS_UNSUCCESSFUL;KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);MmReleasePageOp(PageOp);return(STATUS_UNSUCCESSFUL);}/** Otherwise we have succeeded, free the page*/DPRINT("MM: Swapped out virtual memory page 0x%.8X!\n", Page << PAGE_SHIFT);MmLockAddressSpace(AddressSpace);MmDeleteVirtualMapping(AddressSpace->Process, Address, FALSE, NULL, NULL);MmCreatePageFileMapping(AddressSpace->Process, Address, SwapEntry);MmUnlockAddressSpace(AddressSpace);MmDeleteAllRmaps(Page, NULL, NULL);MmSetSavedSwapEntryPage(Page, 0);MmReleasePageMemoryConsumer(MC_USER, Page);PageOp->Status = STATUS_SUCCESS;KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);MmReleasePageOp(PageOp);return(STATUS_SUCCESS);
}

需要倒换出去的页面可能是“干净”的,也可能是“脏”的。

所谓干净的页面,是指自从上一次建立通向这个物理页面的映射以来从未对其进行过写操作的面,具体有两种可能:

1,这是个刚被分配并建立映射但从未被写过的空白页面。这样的页面在倒换文件中尚无对应的倒换页面,也不需要有,因为以后需要时可以重新分配一个空白页面。所以只要删除其映射,并通过 MmDeleteAlRmaps()使其与所属的进程脱钩,最后释放这个物理页面就可以了

2,这是个从倒换文件换入的页面,但是换入之后从未被写过。这样的页面在倒换文件中已经有了对应的倒换页面,但是不需要改变倒换页面的内容。对于这样的页面,一方面要删除其映射,另一方面要使相应的页面映射表项指向倒换文件中的页面。凡是有了倒换页面的物理页面,其PHYSICAL PAGE结构中的SavedSwapEntry字段就指向这个倒换页面,现在一方面将该字段的内容转移到相应的页面映射表项中(并将其PAPRESENT标志位清0),另一方面通过 MmSetSavedSwapEntryPage()将这个字段清 0。然后再释放这个物理页面。

倒换页面是由“倒换页面项”SWAPENTRY描述的,这是个32位无符号整数,实际上是倒换文件号与文件内页面号的组合

所谓脏的页面,则是指自从上一次建立通向这个物理页面的映射以来已经对其进行过写操作的面,具体又有两种可能:

1,这本是个刚被分配并建立映射的空白页面,但是现在已经不再空白。这样的页面在倒换文件中尚无对应的倒换页面,但是需要有。所以通过MmAllocSwapPageO)分配一个倒换页面这个函数返回一个SWAPENTRY,说明是哪一个倒换文件中的哪一个页面。然后通过MmWriteToSwapPage()将页面的内容写入倒换文件,再将相应的页面映射表项改成指向倒换页面(并将其 PA_PRESENT标志位清0),最后释放该物理页面。

2,这是个从倒换文件换入的页面,但是换入之后已经被写过。这样的页面在倒换文件中已经有了对应的倒换页面,但需要改变其内容。所以也由MmWriteToSwapPageO)将页面的内容写入倒换文件,再将相应的页面映射表项改成指向倒换页面(并将其PA_PRESENT标志位清 0),最后释放该物理页面。

至于 MmWriteToSwapPage(),则已经属于文件操作的范畴,这里就不深入下去了。最后再概括一下倒换页面与物理内存页
面和页面映射表项PTE之间的关系:
1,如果(虚存)页面的内容在物理页面中,则相应的PTE指向该物理内存页面,而物理内存页面的 PHYSICAL_PAGE结构中的SavedSwapEntry 字段指向作为后备的倒换页面。
2,如果(虚存)页面的内容不在物理页面中,则相应的PTE直接指向倒换页面。

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

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

相关文章

Git - 两种方式撤销已提交到远端仓库的记录并删除提交记录

文章目录 命令行方式附 命令行方式 确定要撤销的提交记录 首先&#xff0c;使用以下命令查看提交历史&#xff1a; git log找到想撤销的提交记录的哈希值&#xff08;SHA&#xff09; &#xff0c;比如9c9c98d6f7f28c41d971f8efd51ed31f9720792c 撤销提交记录 根据需求选择以下…

vscode markdown-image 图片粘贴自动上传到本地目录设置

.vscode/settings.json文件内容 {"markdown-image.base.fileNameFormat": "${hash}-${YY}${MM}${DD}-${HH}${mm}${ss}","markdown-image.local.path": "./images","markdown-image.base.uploadMethod": "Local",…

Linux入门-基础指令和权限

1.压缩打包 1.1压缩是什么 压缩是通过特定的算法&#xff0c;使文件减小体积&#xff0c;从而达到节省空间的目的。 1.2.为什么要压缩 a.压缩将文件大小减小&#xff0c;在本地可能不太明显&#xff0c;但是在网络传输中&#xff0c;减小了网络传输的成本。 b.将多个文件压…

HTML字符实体详解

HTML 字符实体是在 HTML 文档中用来表示特定字符的特殊编码。这些字符可能因为直接输入而引发解析错误,或某些字符在 HTML 中具有特殊含义(例如,< 和 > 用于标签)。因此,使用字符实体可以确保文本的准确呈现。 1. 什么是字符实体? 字符实体由一个 & 符号开始,…

大数据新视界 -- 大数据大厂之 Impala 性能优化:解锁大数据分析的速度密码(上)(1/30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

docker pull 拉取镜像失败,使用Docker离线包

1、登录并注册Github&#xff0c;然后在Github中搜索并打开“wukongdaily/DockerTarBuilder” 项目&#xff0c;在该项目主页点击“Fork”。 然后点 “Create Fork”&#xff0c;将项目创建到自己的Github主页。 2、接着在自己创建过来的这个项目中点击“Actions” 3、然后…

HTTP和HTTPS到底啥区别?不只是差一个“S”这么简单.....

在信息时代&#xff0c;互联网已经成为人们生活中不可或缺的一部分。我们每天浏览网页、发送邮件、进行网络购物&#xff0c;都离不开网络数据的传输。 而保障这些数据在传输过程中的安全&#xff0c;就显得尤为重要。 HTTP和HTTPS作为两种应用层协议&#xff0c;在网络数据传…

tools日常使用(随时更新)

文章目录 1 桌面快捷方式小箭头处理参考文献 1 桌面快捷方式小箭头处理 参考来源已不可考&#xff0c;在此仅贴上代码段。 # 箭头消除 reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 29 /d "%systemroot%…

15.useIntersectionObserver

在现代 Web 应用中,检测元素是否进入视口是一个常见需求,尤其是在实现懒加载、无限滚动或跟踪广告曝光等场景中。useIntersectionObserver 钩子提供了一种声明式的方法来利用 Intersection Observer API,使得监测元素可见性变得简单而高效。这个自定义钩子不仅简化了 Inters…

微服务设计模式 - 发布订阅模式(Publisher Subscriber Pattern)

微服务设计模式 - 发布订阅模式&#xff08;Publisher Subscriber Pattern&#xff09; 定义 发布-订阅模式&#xff08;Publisher-Subscriber Pattern&#xff09;是一种常见的设计模式&#xff0c;被广泛用于云计算和分布式系统中&#xff0c;以实现松散耦合的组件间通信。发…

计算布尔二叉树的值

给你一棵 完整二叉树 的根节点&#xff0c;这棵树有以下特征&#xff1a; 叶子节点 要么值为 0 要么值为 1 &#xff0c;其中 0 表示 False &#xff0c;1 表示 True 。非叶子节点 要么值为 2 要么值为 3 &#xff0c;其中 2 表示逻辑或 OR &#xff0c;3 表示逻辑与 AND 。 …

找出目标值在数组中的开始和结束位置(二分查找)

给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 示例 1&#xff1a…

0xGame 2024 [Week 4] Jenkins

1.前言 由于好久没做web题了&#xff0c;所以今天来尝试来做一波web题&#xff0c;仅供刷题记录。 2.题目 这个给的提示对于小白来说实在是友好的过劲。 3.分析 上网搜到一个关于Jenkins的历史漏洞&#xff0c;下面链接可供参考 https://blog.csdn.net/2301_80127209/arti…

yolov8训练及测试(ubuntu18.04、tensorrt、ros)

1 数据集制作 1.1标注数据 Linux/Ubuntu/Mac 至少需要 Python 2.6 &#xff08;推荐使用 Python 3 或更高版本 及 PyQt5&#xff09; Ubuntu Linux (Python 3 Qt5) git clone https://gitcode.com/gh_mirrors/la/labelImg.git sudo apt-get install pyqt5-dev-tools cd lab…

Nginx 反向代理(解决跨域)

文章目录 前言一、同源策略二、跨域是什么&#xff1f;三、Nginx解决跨域1.前端示例代码2.说明 四、nginx反向代理配置五、启动nginx六、最终效果总结 前言 Nginx反向代理解决跨域 一、同源策略 定义&#xff1a;同源策略&#xff08;Same-Origin Policy&#xff09;是指浏览…

clickhouse配置用户角色与权限

首先找到user.xml文件&#xff0c;默认在/etc/clickhouse-server路径下 一、配置角色 找到标签定义 <aaaa><readonly>1</readonly><allow_dll>0</allow_dll> </aaaa>其中aaaa为角色名称&#xff0c;readonly为只读权限&#xff08;0–代表…

ssm《数据库系统原理》课程平台的设计与实现+vue

系统包含&#xff1a;源码论文 所用技术&#xff1a;SpringBootVueSSMMybatisMysql 免费提供给大家参考或者学习&#xff0c;获取源码看文章最下面 需要定制看文章最下面 目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容…

多渠道流量获取策略提升网站访问量的有效方法

内容概要 在当今数字时代&#xff0c;企业面临着越来越激烈的竞争&#xff0c;流量获取变得极为重要。多渠道流量获取不仅可以增加网站的访问量&#xff0c;还能够有效提升品牌的知名度和影响力。通过整合多种渠道&#xff0c;企业能够更好地触达目标受众&#xff0c;实现精准…

kafka实时返回浏览数据

在安装完kafka(Docker安装kafka_docker 部署kafka-CSDN博客)&#xff0c;查看容器是否启动&#xff1a; docker ps | grep -E kafka|zookeeper 再用python开启服务 from fastapi import FastAPI, Request from kafka import KafkaProducer import kafka import json import …

【MyBatis源码】BoundSql分析

基础 BoundSql是对SQL语句及参数信息的封装&#xff0c;它是SqlSource解析后的结果。Executor组件并不是直接通过StaticSqlSource对象完成数据库操作的&#xff0c;而是与BoundSql交互。BoundSql是对Executor组件执行SQL信息的封装&#xff0c;具体实现代码如下&#xff1a; …