任务

一、实验目的
1)理解页面淘汰算法原理,编写程序演示页面淘汰算法。
2)验证Linux虚拟地址转化为物理地址的机制
3)理解和验证程序运行局部性的原理。
二、实验内容
1)Win/Linux编写二维数组遍历程序,理解局部性的原理。
2)Windows/Linux模拟实现OPT和LRU等淘汰算法。
3)Linux下利用/proc/pid/pagemap技术计算某个变量或函数虚拟地址对应的物理地址等信息。

任务1 Win/Linux编写二维数组遍历程序,理解局部性的原理。

1. 提示

提示1:数组尽可能开大一些,并尝试改变数组大小,改变内外重循环次序。例如从[2048] X[2048]变化到[10240] x [20480],观察它们的遍历效率。

提示2:在任务管理中观察它们的缺页次数。

2. 任务代码

#include <iostream>
#include <ctime>
#include <Windows.h>
using namespace std;
int MyArray[10240][20480];
const string str[4] = { "局部性好","局部性差","2048*2048","10240*20480" };
int main() {
DWORD pid = GetCurrentProcessId();
cout << "当前进程的PID是"<<pid<<",您可以根据进程号在任务管理器查看进程。" << endl;
cout << "模式(1. 局部性好;2. 局部性差)" << endl;
cout << "数组大小(1. 2048*2048;2. 10240*20480)" << endl << endl;
int op1 = 0, op2 = 0;
while (1)
{
cout << "******* INPUT 2 INT TO USE *******" << endl;
cin >> op1 >> op2;
cout << "您选择了组合("<<str[op1-1]<<","<<str[op2+1]<<"),运行时间为:";
int sizex=1, sizey=1;
if (op2 == 1)
{
sizex = sizey = 2048;
}
else if (op2 == 2)
{
if (op1 == 1) {
sizex = 10240;
sizey = 20480;
}
else if (op1 == 2)
{
sizey = 10240;
sizex = 20480;
}
}
clock_t start, end;
start = clock();
for (int i = 0; i < sizex; i++)
for (int j = 0; j < sizey; j++) {
if (op1 == 1) {
MyArray[i][j] = 0;
}
else if(op1==2){
MyArray[j][i] = 0;
}
}
end = clock();
double time1 = (double)(end - start) / CLOCKS_PER_SEC;
cout << time1 << "秒" << endl << endl;
}
return 0;
}

3. 结果及说明

1)任务平台:Windows 10, Visual Studio 2019。

2)如何观察缺页次数:在任务管理器-详细信息中,右键列名,“选择列”,添加“页面错误”。

3)实验变量:
①程序局部性;②页面是否被调入内存。

4)实验前猜测:
①访问数组时会将部分页面调到内存中,占用内存增加、页面错误增加。
②局部性差的情况需要消耗更多的时间。
③局部性好坏不影响缺页次数。
④页面被调入内存后,再次访问,效率会提高。

5)实验过程:

  1. 启动程序,内存占用384K,页面错误是1498个。

  2. 先试小数组,局部性好的遍历方式,耗时0.018秒。页面错误变成7672,内存占用增加为24932K。猜测①得证。

  3. 由于刚才访问时数组的内容尚未调入内存,为保证变量唯一,再次运行(小数组,局部性好)。虽然时间减少了,但是由于数组较小,随机性较大,不能证明猜测④。
    接着再运行(小数组,局部性差)。

    可见局部性差时时间增加,猜测②得证。
    并且不论局部性好坏,页面错误和活动的内存均未发生改变,因此局部性好坏不影响页面错误。猜测③得证。

  4. 由于小数组的时间的偶然性较大,使用大数组重复运行,可见调入内存后,再次访问的速度明显加快。下图1是未调入时,下图2、3是调入时,猜测④得证。


任务2 Windows/Linux模拟实现OPT和LRU淘汰算法。

1. 提示

[以下模拟过程仅供参考,不是唯一方案!百度参考其他方案!]
提示1:程序指令执行过程采用遍历数组的操作来模拟;
提示2:用1个较大数组A(例如2400个元素)模拟进程,数组里面放的是随机数,每个元素被访问时就使用printf将其打印出来,模仿指令的执行。数组A的大小必须是设定的页大小(例如10个元素或16个元素等等)的整数倍。
提示3:用3-8个小数组(例如数组B,数组C,数组D等)模拟分得的页框。小数组的大小等于页大小(例如10条指令的大小,即10的元素)。小数组里面复制的是大数组里面的相应页面的内容(自己另外构建页表,描述大数组的页与小数组序号的关系)。
提示4:利用不同的次序访问数组A,次序可以是:顺序,跳转,分支,循环,或随机,自己构建访问次序。不同的次序也一定程度反映程序局部性。
提示5:大数组的访问次序可以用 rand( )函数定义,模拟产生指令访问序列,对应到大数组A的访问次序。然后将指令序列变换成相应的页地址流,并针对不同的页面淘汰算法统计“缺页”情况。缺页即对应的“页面”没有装到小数组中(例如数组B,数组C,数组D等)。
提示6:实验中页面大小,页框数,访问次序,淘汰算法都应可调。
提示7:至少实现2个淘汰算法。
为了方便浏览提示,我做了一点颜色标记,如下。

每种淘汰模式:

  1. Start
  2. 遍历查找是否在某一页框中。
  3. 如果不在,则转5。
  4. 如果在,则访问它,然后转8。
  5. 判断页表是否已满。
  6. 如果未满,则直接添加,然后访问它,然后转8。
  7. 如果已满,则应用淘汰算法确定需要淘汰的一页,然后添加新元素,再访问它,转8。
  8. END

淘汰算法:

  1. FIFO(First Input First Output):先进先出。算法实现思路:淘汰页表中的第一页。
  2. OPT(OPTimal Replacement):其所选择的被淘汰的页面将是以后永不使用的,或是在最长(未来)时间内不再被访问的页面。算法实现思路:遍历剩下的元素,当遇到在当前页的元素时停止遍历,找到停止最晚的页用于淘汰(如果始终没有遇到则说明该页永不使用,可直接被淘汰)。
  3. LRU(Least Recently Used):根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。算法实现思路:每一页都设置一个int类型的访问记录,当被访问时清零,没访问时++。
  4. LFU(Least Frequently Used):根据数据的历史访问频率来淘汰数据,其核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。算法实现思路:每一页都设置一个int类型的访问频率记录,当被访问时++,没访问时不做处理。

2. 任务代码

很长,不贴了
#include<iostream>
#include<list>
#include<map>
#include<vector>
#pragma warning(disable:4996)
using namespace std; #define pageTotalSize 2400
#define seqLength 800
int pageSize = 10; //页面大小
int pageFrameNum = 3; //页框数
int visitOrder = 0; //访问次序
int eliminateAlgorithm = 0; //淘汰算法
string eliminateStr[4] = {"FIFO","OPT","LRU","LFU"};
string visitStr[4] = { "顺序","跳转","循环","随机"};
int page[pageTotalSize];
vector<int>pageFrame[20]; //页框数最大20 int visitSeq[seqLength];
void FIFO(int seq[seqLength], bool showDetail); /*其所选择的被淘汰的页面是最早的一页*/
void OPT(int seq[seqLength], bool showDetail); /*其所选择的被淘汰的页面将是以后永不使用的,或是在最长(未来)时间内不再被访问的页面*/
void LRU(int seq[seqLength], bool showDetail); /*根据数据的历史访问记录来进行淘汰数据*/
void LFU(int seq[seqLength], bool showDetail); /*根据数据的历史访问频率来进行淘汰数据*/
void Order(int visitOrder); /*生成指定次序的访问数组*/

3. 结果及说明


测试了很多次,数据少有时候跑出来的结果LRU比OPT还好,它真的是一个很优秀的算法。

自选模式的:

任务3 Linux下利用/proc/pid/pagemap技术计算某个变量或函数虚拟地址对应的物理地址等信息。

1. 提示

提示1:Linux的/proc/pid/pagemap文件允许用户查看当前进程虚拟页的物理地址等相关信息。

  • 每个虚拟页包含一个64位的值
  • 注意分析64位的信息

提示2:获取当前进程的pagemap文件的全名

提示3:可以输出进程中某个或多个全局变量或自定义函数的虚拟地址,所在页号,所在物理页框号,物理地址等信息。
思考:(1)如何通过扩充实验展示不同进程的同一虚拟地址对应不同的物理地址。(2)如何通过扩充实验验证不同进程的共享库具有同一的物理地址。

2. 任务代码

我觉得这篇博客的代码写得比我写的好,分析也很好,但是因为写得太好了不像我能写出来的代码,所以我主要没有借鉴它的代码。利用/proc/pid/pagemap将虚拟地址转换为物理地址

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
//注意用sudo运行!!!
char buf[200];
//计算虚拟地址对应的地址,传入虚拟地址vaddr
void mem_addr(char* str, unsigned long pid, unsigned long vaddr, unsigned long* paddr)
{
int pageSize = getpagesize();//调用此函数获取系统设定的页面大小 unsigned long v_pageIndex = vaddr / pageSize;//计算此虚拟地址相对于0x0的经过的页面数
unsigned long v_offset = v_pageIndex * sizeof(uint64_t);//计算在/proc/pid/page_map文件中的偏移量
unsigned long page_offset = vaddr % pageSize;//计算虚拟地址在页面中的偏移量
uint64_t item = 0;//存储对应项的值
sprintf(buf, "%s%lu%s", "/proc/", pid, "/pagemap");
//printf("%s\n",buf);
int fd = open(buf, O_RDONLY);//以只读方式打开/proc/pid/page_map
lseek(fd, v_offset, SEEK_SET);//将游标移动到相应位置
read(fd, &item, sizeof(uint64_t));//读取对应项的值,并存入item中
//printf("%lu\n",v_offset);
uint64_t phy_pageIndex = (((uint64_t)1 << 55) - 1) & item;//计算物理页号,即取item的bit0-54
*paddr = (phy_pageIndex * pageSize) + page_offset;//再加上页内偏移量就得到了物理地址
printf("【%s】pid = %lu, 虚拟地址 = 0x%lx, 所在页号 = %lu, 物理地址 = 0x%lx, 所在物理页框号 = %lu\n", str, pid, vaddr, v_pageIndex, *paddr, phy_pageIndex);
sleep(1);
} const int a = 100;//全局常量 int main()
{
int b = 100;//局部变量
static int c = 100;//局部静态变量
const int d = 100;//局部常量
unsigned long phy = 0;//物理地址 char *p = (char*)malloc(100);//动态内存 int pid = fork();//创建子进程
mem_addr("全局常量", getpid(), (unsigned long)&a, &phy);
mem_addr("局部变量", getpid(), (unsigned long)&b, &phy);
mem_addr("局部静态变量", getpid(), (unsigned long)&c, &phy);
mem_addr("局部常量", getpid(), (unsigned long)&d, &phy); sleep(1);
free(p);
waitpid();
return 0;
}

3. 结果及说明

注意,运行时一定要加上sudo。

如图,输出了进程中多个变量的虚拟地址、所在页号、物理地址、所在物理页框号。

(1)如何通过扩充实验展示不同进程的同一虚拟地址对应不同的物理地址。
答:通过fork创建不同的进程,如图所示,pid为5022的为父进程,pid为5023的为子进程。其中全局变量的虚拟地址和物理地址,在父子进程中一致。局部变量、局部常量、局部静态变量则均不一致。

(2)如何通过扩充实验验证不同进程的共享库具有同一的物理地址。

这篇博客里有提到共享库怎么判断,我借鉴了一下,发现子进程的会显示没有present,不过其他进程是正常的。利用/proc/pid/pagemap将虚拟地址转换为物理地址

下图中包括三个进程,进程6944创建了子进程6945(右下角),以及进程6883(左下角)。
查看它们的/proc/pid/maps可见,它们都调用了同一个动态库/usr/lib/x86_64-linux-gnu/libc-2.31.so,并可见在不同进程中这个库的虚拟地址。

基于/proc/pid/pagemap获取物理地址的思路,我们修改之前的程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
//注意用sudo运行!!!
char buf[200];
void mem_addr(unsigned long pid, unsigned long vaddr, unsigned long* paddr)
{
int pageSize = getpagesize(); unsigned long v_pageIndex = vaddr / pageSize;
unsigned long v_offset = v_pageIndex * sizeof(uint64_t);
unsigned long page_offset = vaddr % pageSize;
uint64_t item = 0;
sprintf(buf, "%s%lu%s", "/proc/", pid, "/pagemap");
int fd = open(buf, O_RDONLY);
lseek(fd, v_offset, SEEK_SET);
read(fd, &item, sizeof(uint64_t));
uint64_t phy_pageIndex = (((uint64_t)1 << 55) - 1) & item;
*paddr = (phy_pageIndex * pageSize) + page_offset;
printf("pid = %lu, 虚拟地址 = 0x%lx, 所在页号 = %lu, 物理地址 = 0x%lx, 所在物理页框号 = %lu\n", pid, vaddr, v_pageIndex, *paddr, phy_pageIndex);
sleep(1);
} int main(int argc , char* argv[])
{
unsigned long phy = 0;//物理地址
printf("pid = %lu",getpid());
mem_addr(atoi(argv[1]), (unsigned long)strtol(argv[2],NULL,16), &phy); sleep(1000);
return 0;
}

使其允许接受命令行参数,然后用它检测不同进程的虚拟地址对应的物理地址。如下图所示。

可见,不同进程的共享库具有同一的物理地址。

而子进程的共享库好像不见了(bushi)。

【HUST】网安|操作系统实验|实验三 内存管理的更多相关文章

  1. How Javascript works (Javascript工作原理) (三) 内存管理及如何处理 4 类常见的内存泄漏问题

    个人总结: 1.两种垃圾回收机制: 1)引用标记算法:如果检测到一个对象没有被引用了,就清除它. ***这种算法不能处理循环引用的情况*** 2)标记—清除算法:从根(全局变量)开始向后代变量检测,任 ...

  2. OC-手动内存管理

    一.为什么要进行内存管理 •移动设备的内存极其有限,每个app所能占用的内存是有限制的 • •下列行为都会增加一个app的内存占用 Ø创建一个OC对象 Ø定义一个变量 Ø调用一个函数或者方法 • •当 ...

  3. php 内存管理

    内存是计算机⾮常关键的部件之⼀,是暂时存储程序以及数据的空间,CPU只有有限的寄存器可以⽤于存储计算数据,⽽⼤部分的数据都是存储在内存中的,程序运⾏都是在内存中进⾏的.和CPU计算能⼒⼀样, 内存也是 ...

  4. 2万字|30张图带你领略glibc内存管理精髓(因为OOM导致了上千万损失)

    前言 大家好,我是雨乐. 5年前,在上家公司的时候,因为进程OOM造成了上千万的损失,当时用了一个月的时间来分析glibc源码,最终将问题彻底解决. 最近在逛知乎的时候,发现不少人有对malloc/f ...

  5. 操作系统实验一:进程管理(含成功运行C语言源代码)

    目录 操作系统实验一:进程管理 1.实验目的 2.实验内容 3.实验准备 3.1.1进程的含义 3.1.2进程的状态 3.1.3进程状态之间的转换 3.2 进程控制块PCB 3.2.1进程控制块的作用 ...

  6. 旧书重温:0day2【2】 实验:三种获取kernel32.dll基址的方法

    0x01 找kernel32基地址的方法一般有三种: 暴力搜索法.异常处理链表搜索法.PEB法. 0x02 基本原理 暴力搜索法是最早的动态查找kernel32基地址的方法.它的原理是几乎所有的win ...

  7. 20135231 JAVA实验报告三:敏捷开发与XP实践

    ---恢复内容开始--- JAVA实验报告三:敏捷开发与XP实践 20135231 何佳 实验内容 1. XP基础 2. XP核心实践 3. 相关工具 实验要求 1.没有Linux基础的同学建议先学习 ...

  8. 20145221 《Java程序设计》实验报告三:敏捷开发与XP实践

    20145221 <Java程序设计>实验报告三:敏捷开发与XP实践 实验要求 以结对编程的方式编写一个软件,Blog中要给出结对同学的Blog网址 记录TDD和重构的过程,测试代码不要少 ...

  9. 关于Qt半自动内存管理的思考及实验

    一时兴起,对Qt感了兴趣,决心想要研究一下. 按网上资料配好环境,Windows 7 64bit + Qt 5.3.1 + VS2010. 根据<C++ GUI Qt4 编程>这本书,写出 ...

  10. 第五周总结&实验报告三

    第五周总结&实验报告三 实验报告 1.已知字符串:"this is a test of java".按要求执行以下操作:(要求源代码.结果截图.) ① 统计该字符串中字母s ...

随机推荐

  1. DeepSeek实战:3分钟学会提取网页纯文本!(含提示词)

    DeepSeek实战:3分钟学会提取网页纯文本!(含提示词) |  原创作者/编辑:凯哥Java                      |  分类:人工智能学习系列教程 大家好,我是凯哥Java. ...

  2. flutter-TextField垂直居中

    decoration: InputDecoration( contentPadding: EdgeInsets.symmetric(vertical: 0), // border: InputBord ...

  3. 快速集成和使用 solon-flow 规则与流引擎(用 yaml 编写业务规则)

    本文参考自:https://www.cnblogs.com/studyjobs/p/18125096 规则引擎技术的主要思想是将应用程序中的业务规则分离出来,业务规则不再以程序代码的形式驻留在系统中, ...

  4. 并发编程 - 线程同步(八)之自旋锁SpinLock

    前面对互斥锁Monitor进行了详细学习,今天我们将继续学习,一种更轻量级的锁--自旋锁SpinLock. 在 C# 中,SpinLock是一个高效的自旋锁实现,用于提供一种轻量级的锁机制.SpinL ...

  5. Ansible 数百台批量操作前期准备工作

    Ansible 数百台批量操作前期准备工作 背景: 当前有100台服务器在同一个内网,需要统一部署业务程序并且对主机修改主机名,只提供了一个文档host_user.txt,内容 " IP 用 ...

  6. Codeforces Round 1006 (Div. 3) 比赛记录

    Codeforces Round 1006 (Div. 3) 比赛记录 比赛链接 这场的题目名称都很长啊~. 很简单的一场(毕竟是div3,能不简单嘛)赛时切掉了A - F,C题花的时间有点多,G题偶 ...

  7. GPU的硬件组成及运行原理

    GPU的硬件组成 GPU 是一种专门为图形处理而设计的处理器,它的设计目标是在处理大规模.高并发的图形数据时提供高效的计算能力.与 CPU 相比,GPU 的处理器数量更多,每个处理器的计算能力相对较弱 ...

  8. python代码格式风格 PEP 8

    前言 Python Enhancement Proposal #8叫做PEP 8,它是针对 Python 代码格式而编订的风格指南. 编写 Python 代码时,总是应该遵循 PEP 8 风格指南. ...

  9. 昨晚接收的俄罗斯Meteor-M2气象卫星云图,接收质量还可以!

    接收设备: 天馈:自制四臂螺旋天线 硬件:SDRsharp 跟踪:Orbitron.SDRSharpDriverDDE 频率:137.1MHZ 解码:SDRSharp.QPSK.M2_LRPT_Dec ...

  10. ajax 多次请求相同链接 相同参数,缓存问题

    经常会发现,ajax 多次调用同一个接口时(get),参数不变. 为了提升性能,浏览器就不会和服务器进行交互,获取到的数据 就不会发生变化 解决方案:添加随机参数.或者时间戳 类似在接口后面 添加 D ...