操作系统原理实验四

操作原理实验四

(1)在Windows环境下编写一个程序,模拟实现OPT,FIFO,LRU等页面淘汰算法。可以使用数组模拟内存,数组中的元素模拟为指令或数据。写不同方式的程序去访问数组来模拟 CPU 访问内存的情况。分析运算结果,在分配不同的物理块情况下, 各算法的缺页情况有什么规律?可以 srand( )和rand( )等函数定义和产生“指令”序列,然后将指令序列变换成相应的页地址流,并针对不同的算法计算出相应的命中率。例如,实验中可以产生 320 条“指令”,每个“虚拟页”存放10 条指令。进程分配的页框是4(可变,例如 32)。
(2)在Linux环境下,编写一个小程序,获取该程序中的某个变量的虚拟地址,虚拟页号,页内偏移地址,物理页框号,页内偏移地址,物理地址,并将它们打印出来。建议使用/proc/pid/pagemap 技术。
(3)在Windows 环境下,编写一个函数(特点:比较耗时,比如大型的多维数组读写),用不同的方法测试其所花费的时间。在不同环境下比较其时间是否不同,并分析其含义。测量时间的函数请 baidu。

4.1 Windows页面淘汰算法

运行效果

在这里插入图片描述

代码展示

#include<stdio.h>
#include<time.h>
#include<stdlib.h>

#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个大写字母。

生成的页面流大致如下:
在这里插入图片描述

4.2 Linux地址映射

运行效果

在这里插入图片描述

代码展示

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>

//传入虚拟地址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下如何在进程中获取虚拟地址对应的物理地址

4.3 Windows下测试运行时间

	向老师求证后得知,所谓的不同环境就是指系统的进程数数量不同,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<stdio.h>
#include<time.h>
#include<Windows.h>

//定义耗时函数
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主频)微秒。调用<windows.h>库,使用了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语言程序)

  • 9
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值