(1)在Windows环境下编写一个程序,模拟实现OPT,FIFO,LRU等页面淘汰算法。可以使用数组模拟内存,数组中的元素模拟为指令或数据。写不同方式的程序去访问数组来模拟 CPU 访问内存的情况。分析运算结果,在分配不同的物理块情况下, 各算法的缺页情况有什么规律?可以 srand( )和rand( )等函数定义和产生“指令”序列,然后将指令序列变换成相应的页地址流,并针对不同的算法计算出相应的命中率。例如,实验中可以产生 320 条“指令”,每个“虚拟页”存放10 条指令。进程分配的页框是4(可变,例如 32)。
(2)在Linux环境下,编写一个小程序,获取该程序中的某个变量的虚拟地址,虚拟页号,页内偏移地址,物理页框号,页内偏移地址,物理地址,并将它们打印出来。建议使用/proc/pid/pagemap 技术。
(3)在Windows 环境下,编写一个函数(特点:比较耗时,比如大型的多维数组读写),用不同的方法测试其所花费的时间。在不同环境下比较其时间是否不同,并分析其含义。测量时间的函数请 baidu。
#include
#include
#include
#define num_of_command 320 //指令个数
#define max_of_RAM 32
char RAM[max_of_RAM];//内存,设:最大为32个页框
//查找某个字符,在数组str[begin]到str[end-1]中最先出现的索引,
//如果没有返回end(即比最大索引还大,以后不会用到)
int findChar(char str[],char a,int begin,int end)
{
for (int i = begin; i < end; i++)
{
if (str[i] == a)
return i;
}
return end;
}
/*
OPT:最佳算法
思想:淘汰以后不需要,或者最远的将来才用到的页面
输入参数:pages[] 命令页面流;pagesNum 命令页面流长度(默认是320的);can_use 分配的页面数
*/
int OPT(char pages[],int pagesNum,int can_use)
{
//先清空出can_use个页框,不妨设前can_use个页框为系统分配给该程序的
for (int i = 0; i < can_use; i++)
{
RAM[i] = NULL;
}
//记录缺页数
int count = 0;
//开始执行指令(遍历页面流)
for (int i = 0; i < pagesNum; i++)
{
bool flag=true;//记录是否缺页(true:缺页,false:不缺页)
for (int j = 0; j < can_use; j++)
{
if (RAM[j] == pages[i])
{
flag = false;
break;
}
}
//缺页淘汰
if (flag == true)
{
count++;//缺页数+1
int x=0;//记录需要被淘汰的页面的索引
int distance=0;//记录还有多长的距离才会再次使用这个页面
for (int j = 0; j < can_use; j++)
{
//如果内存中某个被分配的页面为空
if (RAM[j] == NULL)
{
x = j;
break;
}
int len = findChar(pages, RAM[j], i + 1, pagesNum) - i;
if (len > distance)
{
distance = len;
x = j;
}
}
//找到了需要被淘汰的页面
RAM[x] = pages[i];
}
}
//printf("OPT:缺页次数为%d,缺页率为%.2f%%\n", count, (float)(100*((float)count / (float)pagesNum)));
return count;
}
/*
FIFO:先进先出算法
思想:淘汰在内存中停留时间最长的页面
输入参数:pages[] 命令页面流;pagesNum 命令页面流长度(默认是320的);can_use 分配的页面数
*/
int FIFO(char pages[], int pagesNum, int can_use)
{
//先清空出can_use个页框,不妨设前can_use个页框为系统分配给该程序的
for (int i = 0; i < can_use; i++)
{
RAM[i] = NULL;
}
//记录缺页数
int count = 0;
//记录本次页面应填充的位置
int location = 0;
//开始执行指令(遍历页面流)
for (int i = 0; i < pagesNum; i++)
{
bool flag = true;//记录是否缺页(true:缺页,false:不缺页)
for (int j = 0; j < can_use; j++)
{
if (RAM[j] == pages[i])
{
flag = false;
break;
}
}
//缺页淘汰
if (flag == true)
{
count++;//缺页数+1
//找到了需要被淘汰的页面
RAM[location] = pages[i];
location = (location + 1) % can_use;
}
}
//printf("FIFO:缺页次数为%d,缺页率为%.2f%%\n", count, (float)(100 * ((float)count / (float)pagesNum)));
return count;
}
/*
LRU:最久未使用算法
思想:淘汰最长时间未被使用的界面
输入参数:pages[] 命令页面流;pagesNum 命令页面流长度(默认是320的);can_use 分配的页面数
*/
int LRU(char pages[], int pagesNum, int can_use)
{
//先清空出can_use个页框,不妨设前can_use个页框为系统分配给该程序的
for (int i = 0; i < can_use; i++)
{
RAM[i] = NULL;
}
//记录上次使用页是什么时候
int timer[max_of_RAM] = {
0 };
//记录缺页数
int count = 0;
//开始执行指令(遍历页面流)
for (int i = 0; i < pagesNum; i++)
{
//未使用时长+1
for (int t = 0; t < can_use; t++)
{
timer[t]++;
}
bool flag = true;//记录是否缺页(true:缺页,false:不缺页)
for (int j = 0; j < can_use; j++)
{
if (RAM[j] == pages[i])
{
flag = false;
timer[j] = 0;//未使用时长清零
break;
}
}
//缺页淘汰
if (flag == true)
{
count++;//缺页数+1
int x = 0;//记录需要被淘汰的页面的索引
int distance=0;
for (int j = 0; j < can_use; j++)
{
if (RAM[j] == NULL)
{
x = j;
break;
}
if (timer[j] > distance)
{
distance = timer[j];
x = j;
}
}
//找到了需要被淘汰的页面
RAM[x] = pages[i];
}
}
//printf("LRU:缺页次数为%d,缺页率为%.2f%%\n", count, (float)(100 * ((float)count / (float)pagesNum)));
return count;
}
int command[num_of_command];
char pages[num_of_command];
void initialize()
{
/*
个人规定:
指令有260种,分别对应0-259数字
每个虚拟页存放10条指令,虚拟页A存放0-9,B存放10-19,依此类推
因此虚拟页有26种,对应26个大写字母
*/
//生成指令序列(随机生成0-259的数字),
//并转换成页地址流(A-Z)
for (int i = 0; i < num_of_command; i++)
{
command[i] = rand() % 260;
pages[i] = command[i] / 10 + 'A';
/* printf("%c", pages[i]);
if ((i + 1) % 20 == 0)
printf("\n");
else
printf("-");*/
}
}
int main()
{
//洒下时间的种子
srand((unsigned)time(NULL));
/*
页框数:设最大为32
暂定为4
不能设为常量,因为我们需要将页框数作为自变量
*/
int num_of_box = 4;
printf("\t\t\t OPT\t\t\t FIFO\t\t\t LRU\n");
printf("分配页框数 | 平均缺页次数\t平均缺页率 | 平均缺页次数\t平均缺页率 | 平均缺页次数\t平均缺页率\n");
for (; num_of_box <= max_of_RAM; num_of_box++)
{
float sum[3] = {
0};
//五次取平均值
for (int i = 0; i < 5; i++)
{
initialize();
sum[0]+=OPT(pages, num_of_command, num_of_box);
sum[1]+=FIFO(pages, num_of_command, num_of_box);
sum[2]+=LRU(pages, num_of_command, num_of_box);
}
sum[0] /= 5;
sum[1] /= 5;
sum[2] /= 5;
printf(" %d\t |\t %.2f\t %.2f%%\t |\t %.2f\t %.2f%%\t |\t %.2f\t %.2f%%\t |\t\n",
num_of_box,sum[0], (float)(100 * ((float)sum[0] / (float)num_of_command)), sum[1], (float)(100 * ((float)sum[1] / (float)num_of_command)), sum[2], (float)(100 * ((float)sum[2] / (float)num_of_command)));
}
}
其实代码上都有注释,这里不再重复,只写一下个人定义的规则吧:
个人规定:
指令有260种,分别对应0-259数字
每个虚拟页存放10条指令,虚拟页A存放0-9,B存放10-19,依此类推
因此虚拟页有26种,对应26个大写字母。
#include
#include
#include
#include
#include
#include
#include
//传入虚拟地址vaddr
void mem_addr(unsigned long vaddr)
{
printf("虚拟地址:%lx\n",vaddr);
//获取系统设定的页面大小
int pageSize = getpagesize();
printf("页面大小:%x\n",pageSize);
//计算此虚拟地址相对于0x0的经过页面数
unsigned long v_pageIndex = vaddr / pageSize;
printf("虚拟页号:%lx\n",v_pageIndex);
unsigned long v_offset = v_pageIndex * sizeof(uint64_t);
//页内偏移地址
unsigned long page_offset = vaddr % pageSize;
printf("页内偏移地址:%lx\n",page_offset);
uint64_t item = 0;//存储对应项的值
int fd = open("/proc/self/pagemap",O_RDONLY);
if(fd == -1)//判断是否打开失败
{
printf("open /proc/self/pagemap error\n");
return;
}
//将游标移动到相应位置,即对应项的起始地址且判断是否移动失败
if(lseek(fd,v_offset,SEEK_SET) == -1)
{
printf("sleek errer\n");
return;
}
//读取对应项的值,并存入item中,且判断读取数据位数是否正确
if(read(fd,&item,sizeof(uint64_t)) != sizeof(uint64_t))
{
printf("read item error!\n");
return;
}
//判断当前物理页是否在内存中,
if((((uint64_t)1 <<63 )& item) == 0)
{
printf("page present is 0\n");
return;
}
//获得物理页号,即取item的bit(0~54)
uint64_t phy_pageIndex = (((uint64_t)1 << 55) - 1) & item;
printf("物理页框号:%lx\n",phy_pageIndex);
//获取物理地址
unsigned long paddr = (phy_pageIndex * pageSize) + page_offset;
printf("物理地址:%lx\n",paddr);
}
const int a = 100;//全局变量a
int main()
{
int b =100;//局部变量b
static int c =100;//局部静态变量c
const int d =100;//局部常量d
printf("声明:以下数字均为16进制数字\n\n");
printf("全局常量a:\n");
mem_addr((unsigned long)&a);
printf("\n局部变量b:\n");
mem_addr((unsigned long)&b);
printf("\n全局静态变量c:\n");
mem_addr((unsigned long)&c);
printf("\n局部常量d:\n");
mem_addr((unsigned long)&d);
}
1)getpageSize()获取系统设定的页面大小
(2)unsigned long v_pageIndex = vaddr / pageSize; //计算此虚拟地址相对于0x0的经过页面数
(3)unsigned long page_offset = vaddr % pageSize; 获取页内偏移地址
(4)uint64_t phy_pageIndex = (((uint64_t)1 << 55) -1) & item; //获得物理页号,即取item的bit(0~54)
(5)unsigned long paddr = (phy_pageIndex * pageSize) + page_offset;获取物理地址
参考博客:Linux下如何在进程中获取虚拟地址对应的物理地址
向老师求证后得知,所谓的不同环境就是指系统的进程数数量不同,CPU占有率不同。
故本实验使用X个cmd终端挤占CPU,控制X数量得到最终结果。
无其它进程挤占CPU时:
5个cmd终端挤占CPU时:
10个cmd终端挤占CPU时:
批处理程序代码:(用于占用CPU,观察其他进程对函数的影响)
@echo off
@chcp 65001
for /l %%i in (1,1,5) do start cmd /k "@echo off&&echo 这是第%%i个终端窗口&&for /l %%m in (1,1,10000) do echo %%m"
C语言代码:
#include
#include
#include
//定义耗时函数
void consume()
{
long long int i=0;
while (i<10000000000)
{
i++;
}
}
//计时函数1--用clock()函数,精度10毫秒.
//毫秒级时间,然后除以CLOCKS_PER_SEC,就可以换成“秒”
void method_1()
{
clock_t start = clock();
//测试函数
consume();
clock_t end = clock();
double runtime = (double)(end - start) / CLOCKS_PER_SEC;
printf("方法一:%lf 秒\n\n", runtime);
}
//计时函数2--基于windows的时间测试.
//转换后单位为秒,精度为1000 000/(cpu主频)微秒
void method_2()
{
_LARGE_INTEGER time_start; /*开始时间*/
_LARGE_INTEGER time_over; /*结束时间*/
double dqFreq; /*计时器频率*/
LARGE_INTEGER f; /*计时器频率*/
QueryPerformanceFrequency(&f);
dqFreq = (double)f.QuadPart;
QueryPerformanceCounter(&time_start);
//测试函数
consume();
QueryPerformanceCounter(&time_over);
printf("方法二:%lf 秒\n",(double)(time_over.QuadPart - time_start.QuadPart) / dqFreq);
}
int main()
{
method_1();
method_2();
}
0.为什么选择使用cmd终端挤占CPU?
在选择用于挤占的进程时,先是采用了Google Chrome,但是这个软件内部可能存在某种优化导致:当我打开5个网页时,在任务管理器显示打开了11个Chrome,当我打开40个网页时,在任务管理器显示打开了18个Chrome。显然这个软件不适合作为挤占进程,于是我后来采用了用批处理打开X个cmd终端,并让终端不断的输出数字(仅仅打开终端,但是不给他提供任务是不会占用CPU的),最终达到了想要的结果。(而且每个cmd终端都在运行相同的程序,占用CPU几乎相同,因此可以看作是相同的进程)
1.consume()是一个消耗时间的函数。
2.method_1()函数:
使用clock()函数,精度10毫秒.这个方法在测量较小的时间时误差比较大,但是在本次实验中由于消耗时间较长,并没有明显误差。
3.method_2()函数:
基于windows的时间测试.单位为秒,精度为1000 000/(cpu主频)微秒。调用库,使用了QueryPerformanceCounter()函数,误差较小。
(4)批处理程序
for /l %%i in (1,1,5) do start cmd是打开5个cmd终端程序。(1,1,5)对应(start,step,end)
/K:是让命令执行完仍然显示cmd窗口。
后面引号内语句是要求新打开的cmd终端执行的,多条命令用&&连接。
@echo off的意思是关闭回显,不显示正在执行的批处理命令。
echo 这是第%%i个终端窗口:输出这是第i个终端窗口。
for /l %%m in (1,1,10000) do echo %%m:在终端窗口依次输出1到10000。(终端只是打开是不会占用CPU的,因此给他找点事情做。10000这个数字可以自由决定,但是你必须保证输出晚1-10000所消耗的时间比程序运行时间长长长,因为我们需要在终端运行中,打开我们的C语言程序运行,此过程需要批处理不停的挤占CPU,另外cmd终端新打开时CPU占有率特别高,你需要等他稳定一下(我的电脑大概输出到1000左右就稳定了),然后再运行C语言程序)