本次课程设计的目的在于通过实践进一步扎实页面置换过程的理论知识、提高实践动手能力,提高对页面置换算法的具体过程,数据的流动路线,线程的同步互斥机制的认识程度,提高软件开发动手能力,熟练掌握从需求到总体设计到详细设计再到实际运行测试的软件设计过程。
操作系统的课程设计对我们学习软件工程专业的同学来说十分重要且必要,可以让我们通过编程实验,更加深入得理解和掌握操作系统的基本理论和功能技术,通过这次课设,可以帮助我们将相对抽象的理论应用于实践,提高分析问题和解决问题的能力,提高编写和开发系统程序的能力以及交流互助的能力。
基本任务:
(1)完成访问快表、访问页表、缺页中断、页面置换等过程的模拟;
(2)完成物理地址的转换;
(3)完成自动生成逻辑页面地址,并根据页面地址获取逻辑页面号;
(4)完成页面地址范围、访问地址序列个数、快表访问时间、页表访问时间、缺页中断时间、是否访问快表等参数的设置;
(5)完成线程模拟访问页面序列的情况下线程的暂停、终止与恢复功能;
(6)完成FIFO、LRU、LFU、OPT页面置换算法的实现与模拟;
扩展功能:
(1)完成不同算法在同一页面访问序列的情况下缺页率的对比;
(2)完成同一算法在不同物理块下缺页率的对比;
(3)完成FIFO算法Belady现象的验证;
(4)完成同一算法在不同页面范围的下缺页率的对比(其他变量保持不变);
(5)完成用户手动分配物理块的模拟(对某一置换算法,暂停其该线程对页面序列的访问,由用户分配物理块来扩展该线程的驻留集);
(6)完成CLOCK、PBA页面置换算法的实现与模拟;
(7)完成创新功能(多个线程使用类似于PBA算法的方法通过获取互斥空闲物#理块资源的途径实现页面访问)的实现于模拟;
语言及程序:C#
开发平台:VS2017
运行环境:Windows10
处理器:Intel® Core™i7-7700HQ CPU @2.80GHZ
已安装的内存(RAM):8.00GB
系统类型:64位操作系统,基于x64的处理器
前端:
GDI+的双缓冲问题
动画流畅度的提升
用GDI+画大图时速度较慢
动画演示时,拖动滚动条,图像丢失的问题
用GDI+绘制表格控件的制作(采用特殊图形作为单元格)。
后端:
解决了最初访问快表、访问页表等过程单独设计并满足整体的流程的难题;将成功访问到某个页面时对一些数据结构的维护与页面置换时对一些数据结构的维护分成单独的函数,并用专门的维护与页面置换的控制函数来控制,解决了在同一个访问流程(即先页面数组越界判断、快表与页表的访问,若页表中不存在页面号则缺页中断的过程)下,支持不同算法的运行问题;解决了FIFO、LRU、LFU、OPT、CLOCK,页面置换在相应数据结构设计以及相关数据结构维护上的难题;
(1)将阻塞进程,暂时不用的程序,数据换出
(2)将具备运行条件的进程换入
(3)类型
①整体对换:进程对换,解决内存紧张
②部分对换:页面对换/分段对换:提供虚存支持
(4)对换空间的管理
外存对换区比文件区侧重于对换速度,因此,对换区一般采用连续分配,采用数据结构和分配回收类似于可变化分区分配
(5)换出
①选出被换出进程:
因素:优先级,驻留时间,进程状态
②换出过程:
对于共享段:计数减1, 是0则换出,否则不换
修改PCB和MCB(或内存分配表)
(6)换入
①选择换入进程:优先级,换出时间等
②请内存换入
基本任务:逻辑地址——物理地址的映射
(1)页号→块号:通过页表来完成
(2)页内地址→块内地址:无需转换
基本地址变换机构:
(1)越界保护
(2)每个进程对应一页表,其信息放在PCB中,执行时将其首地址装入页表寄存器
图1 无快表的地址变换机构
图2 有快表的地址变化机构
图3 有快表的地址变化过程
2.1.3多级页表
页表可能很大,将其离散存放在不同页块中。
建一“外部页表”来管理这些离散页表块。
相当于单级页表中的页表寄存器,一般应常驻内存。
每项记录页表始址,且增加存在位。
64位机器页表一般>3级,最外层页表常驻。
图4 多级页表结构
物理地址,CPU地址总线传来的地址,由硬件电路控制(现在这些硬件是可编程的了)其具体含义。物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存、BIOS等)。在没有使用虚拟存储器的机器上,虚拟地址被直接送到内存总线上,使具有相同地址的物理存储器被读写;而在使用了虚拟存储器的情况下,虚拟地址不是被直接送到内存地址总线上,而是送到存储器管理单元MMU,把虚拟地址映射为物理地址。
线性地址(Linear Address)也叫虚拟地址(virtual address)是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。是一个32位无符号整数,可以用来表示高达4GB的地址,也就是,高达4294967296个内存单元。线性地址通常用十六进制数字表示,值得范围从0x00000000到0xfffffff)程序代码会产生逻辑地址,通过逻辑地址变换就可以生成一个线性地址。如果启用了分页机制,那么线性地址可以再经过变换以产生一个物理地址。如果没有启用分页机制,那么线性地址直接就是物理地址。
逻辑地址是在有地址变换功能的计算机中,访内指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址,也就是是机器语言指令中,用来指定一个操作数或是一条指令的地址。要经过寻址方式的计算或变换才得到内存储器中的实际有效地址即物理地址。一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是个索引号,后面3位包含一些硬件细节。
缺页异常被触发通常有两种情况——
(1)程序设计的不当导致访问了非法的地址
(2)访问的地址是合法的,但是该地址还未分配物理页框
堆和栈都是虚拟地址空间上的概念
基本思想:
分页系统能有效地提高内存的利用率,而分段系统能反映程序的逻辑结构,便于段的共享与保护,将分页与分段两种存储方式结合起来,就形成了段页式存储管理方式。
在段页式存储管理系统中,作业的地址空间首先被分成若干个逻辑分段,每段都有自己的段号,然后再将每段分成若干个大小相等的页。对于主存空间也分成大小相等的页,主存的分配以页为单位。
段页式系统中,作业的地址结构包含三部分的内容:段号,页号,页内位移量。
程序员按照分段系统的地址结构将地址分为段号与段内位移量,地址变换机构将段内位移量分解为页号和页内位移量。
为实现段页式存储管理,系统应为每个进程设置一个段表,包括每段的段号,该段的页表始址和页表长度。每个段有自己的页表,记录段中的每一页的页号和存放在主存中的物理块号。
在请求分页系统中,可采取两种内存分配策略,即固定和可变分配策略。在进行置换时,也可采取两种策略,即全局置换和局部置换。
(1)固定分配局部置换
(2)可变分配全局置换
(3)可变分配局部置换
把选择换出页面的算法称为页面置换算法置换算法的好坏将直接影响到系统的性能。
本实验小组完成的页面置换算法由FIFO、LRU、LFU、OPT、改进CLOCK下面将详细介绍上述算法中涉及到的思路以及需要的设计结构等问题。
(1)FIFO算法
思路:选择在内存驻留时间最长的页面进行置换
实现:维护一个记录所有位于内存中的逻辑页面链表,链表元素按驻留内存的时间排序,链首最长,链尾最短,出现缺页时,选择链首页面进行置换,新页面加到链尾
特点:实现简单;性能较差,调出的页面可能是经常访问的
该算法通过一个先进先出队列实现对置换哪个驻留集中页面的选择的问题。但是该算法随着分配的物理块增大,有时会出现缺页率上升的现象,即Belady现象,绘制FIFO随着物理块数增大缺页率的变化情况以及设定好特殊的页面访问序列成功的实现了FIFO算法的Belady现象的观察。
页面置换算法的功能:当出现缺页异常,需调入新页面而内存已满时,置换算法选择被置换的物理页面。
(2)LRU算法
思路:选择最长时间没有被引用的页面进行置换,因为如果某些页面长时间未被访问,则它们在将来还可能会长时间不会访问
实现:缺页时,计算内存中每个逻辑页面的上一次访问时间,选择上一次使用到当前时间最长的页面
特点:可能达到最优的效果,维护这样的访问链表开销比较大
LRU置换算法选择最近最久未使用的页面予以淘汰。在本实验的过程中,小组将LRU算法分成了缺页时页面置换以及访问到页面时对数据结构的维护两部分。其中数据结构采用与FIFO相同的队列,页面置换部分也保持与FIFO相同,但是在访问到某一页面时,通过把队列中该页取出放置到队尾来实现最近最久未使用的页面永远放在队首的方法,好比在模拟一个栈,来实现LRU数据结构的维护。
(3)LFU算法
思路:缺页时,置换访问次数最少的页面
实现:每个页面设置一个访问计数,访问页面时,访问计数加1,缺页时,置换计数最小的页面
特点:算法开销大,开始时频繁使用,但以后不使用的页面很难置换
在采用LFU算法时,应为在内存中的每个页面设置一个移位寄存器,用来记录该页面被访问的频率。该置换算法选择在最近时期使用最少的页面作为淘汰页。
将LFU实现简化为统计之前访问页面的次数,访问次数最少的页面将会被新访问的页面置换出去。
(4)OPT算法
其所选择的被淘汰页面将是以后永不使用的,或许是在最长(未来)时间内不再被访问的页面。采用最佳置换算法通常可保证获得最低的缺页率。
OPT算法在实际中不可能实现,但是在模拟的过程中,知道以后会访问什么页面是已知的,所以让OPT算法的模拟实现成为可能。
在OPT算法中并没有用到特定的数据结构,仅通过获取接下来的访问页面的序列便可以实现对将来最久未使用的页面的确定。
(5)CLOCK算法
图5 简单的clock算法
进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。
为模拟虚拟存储器中的不断访问页面的进程,实验过程中使用多个线程的并发执行来模拟虚拟存储系统当中的进程。在之后的描述中也会用进程来描述小组模拟的实际过程,但请明确这些“进程”实际上是通过线程来实现的。
进程的执行过程是线状的, 尽管中间会发生中断或暂停,但该进程所拥有的资源只为该线状执行过程服务。一旦发生进程上下文切换,这些资源都是要被保护起来的。这是进程宏观上的执行过 程。而进程又可有单线程进程与多线程进程两种。我们知道,进程有 一个进程控制块 PCB ,相关程序段 和 该程序段对其进行操作的数据结构集 这三部分,单线程进程的执行过程在宏观上是线性的,微观上也只有单一的执行过程;而多线程进程在宏观上的执行过程同样为线性的,但微观上却可以有多个执行 操作(线程),如不同代码片段以及相关的数据结构集。线程的改变只代表了 CPU 执行过程的改变,而没有发生进程所拥有的资源变化。除了 CPU 之外,计算机内的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。与进程控制表和 PCB 相似,每个线程也有自己的线程控制表 TCB ,而这个 TCB 中所保存的线程状态信息则要比 PCB 表少得多,这些信息主要是相关指针用堆栈(系统栈和用户栈),寄存器中的状态数据。进程拥有一个完整的虚拟地址空间,不依赖于线程而独立存在;反之,线程是进程的一部分,没有自己的地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。
资源互斥
在使用GDI+进行画图时,因为要同时运行多个线程,而所有的线程都会公用一个画图的缓冲区,所以要进行资源互斥的设计,同时对每个方法中使用的公共变量可以采用生成对应的局部变量,或者是使用mutex进行限制。
本次课设的前端(展示)及后端(算法处理、接口等)的基本技术路线均是先面向过程写方法,然后采用面向对象的方法进行封装。
(1)采用快速原型的方法,先实现前端界面的主要功能的大致显示。
(2)然后对后台进行开发。
(3)前后台接口设计。
(4)前后台交互。
(5)前端界面优化
(6)创新点开发
本次课设项目共分为三大部分
(1)前端设计
(2)后台设计
(3)接口设计
主要分为三个大模块
(1)不同的页面置换算法的速度和缺页率的比较如下图
图6 多个页面置换算法的比较(运行截图)
图7 动画的界面
(3)知识迁移,利用(2)中的原理来模拟一个寻仙问路游戏(不完善,不感兴趣建议跳过)下图
图8 用来模拟外存的场景
图9 用来模拟内存的场景
图10 用来模拟处理器的场景
(1)采用快速原型的方法,简单的建立了一个前端的面向过程的界面,从而弄清项目中的难点
(2)针对项目中的难点进行后台开发
(3)采用面向对象的方法进行封装
(4)进行接口的设计
(5)美化前端
(1)动画运行线程,主要负责动画的动态运行
(2)加速线程,对动画的速度进行调整
(3)每个页面置换都独立为一个线程
(4)一个线程运行所有的页面置换算法。
(5)更新数据线程,主要负责跟随动画的运行实时更新数据。
(6)一个线程负责准备GDI+绘制图片
(7)各种监视其他线程执行情况的线程
图11 保存的文件
图12 运行截图1
图13 运行截图2
数据展示:
(1)直观的以表格的形式来并发或者单个执行页面置换算法。
(2)采用动画的形式直观的对数据在CPU,内存,外存之间的数据流动(如下图)
图14 设计展示
为方便前端后端的交互、传输信息的确定是一个非常重要的工作。页面参数既应该满足在后端实现的过程中可以通过输入参数来进行算法的执行,也要考虑到前端是否能传输这样的数据。
根据题目要求,添加前端能够设置页面逻辑地址的随机生成、快表访问时间、页表访问时间、缺页中断时间等信息。
根据创先内容及后台需要,还需要对页面访问模拟过程中分配的物理块以及需要页面访问的最大值等信息的设计。
根据上述基本需求,可以设定需求的基本参数信息,为了方便对其管理,单独将其设置为一个页面请求类Request,其存储的基本信息参数如下所示:
1.class Request //全局变量设置
2. {
3. public static ArrayList test_FIFO = new ArrayList();
4. public static ArrayList test_LRU = new ArrayList();
5. public static ArrayList test_OPT = new ArrayList();
6. public static ArrayList test_SNRU = new ArrayList();
7. public static bool kuaibiao = true;//快表的有无
8. public static int NumsOfwulikuai = 3; //页内物理块个数 --相当于对快表大小的限制
9. public static int TimeOfneicun = 100; //内存的存取时间
10. public static int TimeOfkuaibiao = 10; //访问快表时间
11. public static int TimeOfqueye = 500; //缺页中断时间
12. // public static int NumsOfneicun = 20; //内存中的数量--页表的大小
13. public static int NumsOfYebiao = 20; //内存中的数量--页表的大小
14. public static int NumsOfKuaibiao = 20; //内存中的数量--页表的大小
15. // public static string xulie = "1323H,3516H,16A7H,6B23H,3D21H,16FCH,7121H,36FCH,9121H";
16. public static string xulie = "70000H, 00000H, 1AAA0H, 2AAA0H, 0AAA0H, 3AAA0H, 0AAA0H, 4AAA0H,2AAA0H,3AAA0H,0EEE0H, 3AAA0H, 2AAA0H, 1AAA0H, 2AAA0H, 0AAA0H, 1AAA0H, 7AAA0H,0AAA0H,1AAA0H";
17. public static string[] physicalArray = { "70000H", "00000H", "1AAA0H", "2AAA0H", "0AAA0H", "3AAA0H", "0AAA0H", "4AAA0H", "2AAA0H", "3AAA0H", "0EEE0H", "3AAA0H", "2AAA0H", "1AAA0H", "2AAA0H", "0AAA0H", "1AAA0H", "7AAAH","0AAAH","1AAAH" };
18. public static string[] logicArray = { "7", "0", "1", "2", "0", "3", "0", "4", "2", "3","0","3","2","1","2","0","1","7","0","1"};
19. }
将其设置为全局变量,实现多个实例的request访问的唯一性。
人物移动的数据结构设计实现展示如下:
1.
public static class player
2. {
3. public static bool loading = true;
4. public static PictureBox pic = new PictureBox();
5. public static bool kuaibiao;
6. public static Point location=new Point(0,0);
7. static List<List<Image>> image_move = new List<List<Image>>(); //人物八个方向走路的图片集
8. static List< List<Image> > image_stand = new List<List<Image> > (); //人物站立的图片集
9. static int[] current_pic=new int[16];
10. static int sleepTime = 50;
11. public static int cpu_action = 0;
12. public static int neicun_action = 0;
13. public static int waicun_action = 0;
14. // public static int current_num = 0;
15.} ,
人物的移动部分图片如下(共八个方向的移动与站立,图 为向左上方移动的图片):
图 15 左上方人物移动图
处理设计
访问快表、访问页表以及缺页中断等操作进行分开封装,方便进行后期有无页表,并方便多个页面置换算法的实现。以为代码较长,这里只展示一个创新性的改进的SNRU算法。
1.public void SNRU()
2. {
3. init();
4. Dictionary<int, string> map = new Dictionary<int, string>();//存储每一个内存页框所存的内容
5. string outline = "";
6. int totalVisitTime = 0;
7. int sleepTime = 0;
8. int unFindCount = 0;
9. //初始化
10. for (int i = 0; i < Request.NumsOfwulikuai; i++)
11. {
12. Q[i].pageId = -1;
13. Q[i].A = 0;
14. Q[i].M = 0;
15. }
16. stack1CurrentX = stack1InitialX + 70;//Stack1的初始坐标X+70
17. for (int i = 0; i < xulieLength; i++) //xulieLength表示有多少个要访问的块
18. {
19. outline = "";
20. sleepTime = 0;
21. stack1CurrentY = stack1InitialY;//Stack1的初始坐标Y
22. string currentPage = Request.logicArray[i].Trim();//待处理元素
23. int changedLocation = -1;
24. if (!inblock(currentPage))//缺页
25. {
26. unFindCount++;//缺页次数加1
27. if (map.Count() < Request.NumsOfwulikuai)
28. { //物理块未装满 不用置换,直接添加
29. int loaction = map.Count();//页面的Key,入队的位置
30. map.Add(loaction, currentPage);
31. for (int k = 0; k < Request.NumsOfwulikuai; k++)
32. {
33. if (Q[k].pageId == -1)
34. {
35. Q[k].pageId = str16_int10(currentPage);
36. break;
37. }
38. }
39. }
40. else
41. { //物理块已经装满,进行页面置换
42. int loaction = Search();
43. Q[loaction].pageId = str16_int10(currentPage);
44. Q[loaction].A = 1;
45. changedLocation = loaction;
46. map.Remove(loaction);
47. map.Add(loaction, currentPage);
48. }
49.
50. if (kuaibiao_exist)
51. {
52. totalVisitTime += Request.TimeOfkuaibiao + Request.TimeOfneicun + Request.TimeOfqueye + Request.TimeOfkuaibiao + Request.TimeOfneicun;
53. sleepTime = Request.TimeOfkuaibiao + Request.TimeOfneicun + Request.TimeOfqueye + Request.TimeOfkuaibiao + Request.TimeOfneicun;
54.
55. }
56. else
57. {
58. totalVisitTime += (Request.TimeOfneicun + Request.TimeOfqueye + Request.TimeOfneicun);//访问内存时间+缺页中断时间
59. sleepTime = (Request.TimeOfneicun + Request.TimeOfqueye + Request.TimeOfneicun);
60. }
61.
62.
63. mutexForGraphic.WaitOne();
64. #region 动态演示,通过图形更新
65. //绘制当前访问页
66. g.DrawRectangle(pen, stack1CurrentX, stack1CurrentY, dx, dy);
67. g.DrawString(currentPage, font, brush, stack1CurrentX + 4, stack1CurrentY + 4);
68.
69. //绘制内存情况
70. foreach (KeyValuePair<int, string> pair in map)
71. {
72. stack1CurrentY += dy;
73. g.DrawRectangle(pen, stack1CurrentX, stack1CurrentY, dx, dy);
74. if (pair.Key == changedLocation)
75. {
76. g.DrawString(pair.Value, font, brushred, stack1CurrentX + 4, stack1CurrentY + 4);
77. }
78. else
79. {
80. g.DrawString(pair.Value, font, brush, stack1CurrentX + 4, stack1CurrentY + 4);
81. }
82. }
83. for (int j = 0; j < (Request.NumsOfwulikuai - map.Count()); j++)
84. {
85. stack1CurrentY += dy;
86. g.DrawRectangle(pen, stack1CurrentX, stack1CurrentY, dx, dy);
87. g.DrawString("", font, brush, stack1CurrentX + 4, stack1CurrentY + 4);
88. }
89.
90. //是否缺页
91. stack1CurrentY += dy;
92. g.DrawRectangle(pen, stack1CurrentX, stack1CurrentY, dx, dy);
93. g.DrawString("√", font, brush, stack1CurrentX + 4, stack1CurrentY + 4);
94.
95. //缺页次数
96. stack1CurrentY += dy;
97. g.DrawRectangle(pen, stack1CurrentX, stack1CurrentY, dx, dy);
98. g.DrawString(unFindCount.ToString(), font, brush, stack1CurrentX + 4, stack1CurrentY + 4);
99.
100. stack1CurrentX += dx;
101. #endregion
102. mutexForGraphic.ReleaseMutex(); //画布缓冲区
103. }
104. else
105. {
106.
107. if (kuaibiao_exist) //物理块中有表示快表中也有
108. {
109. totalVisitTime += Request.TimeOfkuaibiao + Request.TimeOfneicun;
110. sleepTime = Request.TimeOfkuaibiao + Request.TimeOfneicun;
111. }
112. else
113. {
114. totalVisitTime += 2 * Request.TimeOfneicun;
115. sleepTime = 2 * Request.TimeOfneicun;
116. }
117.
118. mutexForGraphic.WaitOne();
119. #region 动态演示,通过图形更新
120. //绘制当前访问页
121. g.DrawRectangle(pen, stack1CurrentX, stack1CurrentY, dx, dy);
122. g.DrawString(currentPage, font, brush, stack1CurrentX + 4, stack1CurrentY + 4);
123.
124. //绘制内存情况
125. for (int j = 0; j < Request.NumsOfwulikuai; j++)
126. {
127. stack1CurrentY += dy;
128. g.DrawRectangle(pen, stack1CurrentX, stack1CurrentY, dx, dy);
129. g.DrawString(" ", font, brush, stack1CurrentX + 4, stack1CurrentY + 4);
130. }
131. //缺页率情况
132. stack1CurrentY += dy;
133. g.DrawRectangle(pen, stack1CurrentX, stack1CurrentY, dx, dy);
134. g.DrawString(" ", font, brush, stack1CurrentX + 4, stack1CurrentY + 4);
135.
136. //缺页次数
137. stack1CurrentY += dy;
138. g.DrawRectangle(pen, stack1CurrentX, stack1CurrentY, dx, dy);
139. g.DrawString(unFindCount.ToString(), font, brush, stack1CurrentX + 4, stack1CurrentY + 4);
140.
141. stack1CurrentX += dx;
142. #endregion
143. mutexForGraphic.ReleaseMutex();
144. }
145. //实时显示当前最新界面的物理地址
146. MyForm.BeginInvoke(new Action(() =>
147. {
148. string text;
149. int tmp = -1;
150. foreach (KeyValuePair<int, string> kvp in map)
151. {
152. if (kvp.Value == currentPage)
153. {
154. tmp = kvp.Key;
155. break;
156. }
157.
158. }
159. if (tmp == -1)
160. {
161. text = "程序未运行";
162. }
163. string address = Request.physicalArray[i];
164. address = get_physical_address(tmp, address);
165. text = "当前的最新逻辑页的物理地址为:" + address;
166. physical_address.Text = text;
167.
168. }));
169. //实时统计缺页率和累计用时
170. MyForm.BeginInvoke(new Action(() =>
171. {
172. double current_queyelv = ((unFindCount * 1.0 / (i + 1)) * 100);
173. Request.test_SNRU.Add(current_queyelv);
174. quyelv.Text = "改进的CLOCK算法当前缺页率:" + unFindCount + "/" + (i + 1) + "=" + ((unFindCount * 1.0 / (i + 1)) * 100).ToString("F2") + "%";
175. }));
176.
177. MyForm.BeginInvoke(new Action(() =>
178. {
179. totaltime.Text = "改进的CLOCK算法当前累计用时:" + totalVisitTime + "nm";
180. }));
181.
182.
183. System.Threading.Thread.Sleep(sleepTime);
184. }
185. mutexForGraphic.WaitOne();
186. #region 统计信息
187. stack1CurrentY = stack1InitialY;
188. g.DrawString("统计信息", font, brush, stack1CurrentX + 4, stack1CurrentY + 4);
189. stack1CurrentY += dy;
190. g.DrawString("缺页率:" + unFindCount + "/" + xulieLength + "=" + (unFindCount * 1.0 / xulieLength) * 100 + "%", font, brush, stack1CurrentX + 4, stack1CurrentY + 4);
191. stack1CurrentY += dy;
192. g.DrawString("累计用时:" + totalVisitTime + "ns", font, brush, stack1CurrentX + 4, stack1CurrentY + 4);
193. #endregion
194. mutexForGraphic.ReleaseMutex();
195. }
对应的流程图如下
图16 流程图
LRU采用的数据结构与FIFO数据结构相同,均为一个简单的Python列表形式。这里我们维护这个列表,保证列表的首部为最久未使用的页面,列表的尾部为刚访问的页面。所以在页面置换的过程中,我们依旧只需调换队首元素完成调页,所以可以发现LRU的实现过程与FIFO是相同的,所以在FIFO中FIFO的调页函数也适用于LRU。
既然LRU调页与FIFO一样,但是显然二者的算法流程是不一样的,而且LRU应当是访问到页面以后再将页面放入队尾更加合适,而不是简单的置换页面的时候就把页面号放入队尾,因为缺页调换时并没有成功获取到页面号对应的物理块号。
为解决上述问题,专门声明定义了LRU专用数据结构(即self.__access_history列表)的维护函数(当然在其他的算法中也会存在这样的维护操作):
上述代码中仅进行了两个操作,将访问到的页面从队列中弹出来以及将访问到的页面放入队尾。该函数在访问快表或页表并成功找到对应页面号时执行,也就意味着LRU不同于FIFO的地方便是如果页面号存在于快表或页表中,则将该页面号放入队尾表示该页面刚刚访问过。如此便实现了将最近最久未使用的页面保持在队列首的位置。
LFU页面置算法实际上是统计存在于驻留集物理块中页面的访问次数,每次调页时调换访问次数最小的页面调换,如果调换次数最小的页面有多个页,则根据一定的逻辑,调换最久未访问的页面,设计维护LFU的页面访问记录用的数据结构如下:
语言及程序:C#
开发平台:VS2017
运行环境:Windows10
处理器:Intel® Core™i7-7700HQ CPU @2.80GHZ
已安装的内存(RAM):8.00GB
系统类型:64位操作系统,基于x64的处理器
(1)采用面向对象的方法进行开发
(2)先采用面向过程的方法进行简易程序的制作
(3)页面的布局很重要
本程序都是用VS2017采用C#进行开发,并自己编写了碰撞模型。
人物移动,碰撞,对话模型的设计与编码
1.采用一个线程进行监听(类似TS锁),物体的
相对位置,满足条件触发相应的事件。适用于频繁互动的情况
2.不频繁的互动,直接在程序中写好
线程监听
基于TS锁的原理实现,每经过一段时间查看一下被监听线程的状态
LFU算法的实现过程中,对于数据结构的设计以及维护非常存在困难,另外LFU表维护的过程中为了保证访问次数按顺序排序,需要有序的插入元素,然而每一个元素并不是简单的数据结构,在排序过程中存在很多困难。
对创新功能的设计存在一系列的问题:在最初的过程中设定完全按照PBA算法的描述设计,但是由于模拟的空闲物理块有限,导致如果保留物理块内数据然后放入空闲物理块链尾,会存在空闲物理块链尾的物理块被别的进程获取而导致即使当前进程在第二个时钟周期有需要该页面,但存储该页面的物理块已分配给别的进程而导致缺页率依旧很高的现象,当然经过讨论后已经解决了此问题(参考详细设计)。
遇到的问题:画面闪烁问题
解决方法:双缓冲技术
绘图窗口内容或大小每改变一次,都要调用Paint事件进行重绘操作,该操作会使画面重新刷新一次以维持窗口正常显示。刷新过程中会导致所有图元重新绘制,
而各个图元的重绘操作并不会导致Paint事件发生,因此窗口的每一次刷新只会调用Paint事件一次。窗口刷新一次的过程中,每一个图元的重绘都会立即显示到窗口,
因此整个窗口中,只要是图元所在的位置,都在刷新,而刷新的时间是有差别的,闪烁现象自然会出现。
所以说,此时导致窗口闪烁现象的关键因素并不在于Paint事件调用的次数多少,而在于各个图元的重绘。
根据以上分析可知,当图数目不多时,窗口刷新的位置也不多,窗口闪烁效果并不严重;当图元数目较多时,绘图窗口进行重绘的图元数量增加,绘图窗口每一次刷新
都会导致较多的图元重新绘制,窗口的较多位置都在刷新,闪烁现象自然就会越来越严重。特别是图元比较大绘制时间比较长时,闪烁问题会更加严重,因为时间延迟会更长。
解决上述问题的关键在于:窗口刷新一次的过程中,让所有图元同时显示到窗口。
当进行鼠标跟踪绘制操作或者对图元进行变形操作时,Paint事件会频繁发生,这会使窗口的刷新次数大大增加。虽然窗口刷新一次的过程中所有图元同时显示到窗口,但也会有时间延迟,因为此时窗口刷新的时间间隔远小于图元每一次显示到窗口所用的时间。因此闪烁现象并不能完全消除!
所以说,此时导致窗口闪烁现象的关键因素在于Paint事件发生的次数多少。
解决此问题的关键在于:设置窗体或控件的几个关键属性。
在编程当中,或多或少会接触到图像编程,对于图像编程来说窗口闪烁是个常见的问题,当窗口有大量的复杂的图元数据需要重绘,或者拥有自定义控件中的窗口闪烁问题更是显而易见的。出现闪烁的原因有很多种,大部分原因主要是因为触发WM_PAINT消息时窗体进行了重绘操作,此过程先是用窗体的背景色擦除窗口表面,再把窗体的图像绘制上去,但是如果这2个操作不在同一时间段完成的话,就会先看到背景色(大部分为白色)接着才看到图像,这样就会出现我们所说的窗体闪烁现象。那么如何解决这个问题呢,解决方法有很多,其中有个比较好的方法(个人认为)就是采用双缓冲机制来绘图,基本上可以解决大部分的问题。
双缓冲的原理:尽量快的输出图像,使输出在一个刷新周期内完成,如果输出内容很多比较慢,那么采用内存缓冲的方法,先把要输出的内容在内存准备好,然后一次性输出到窗体上,简单的说来就是在窗口刷新一次的过程中,让所有图元同时显示到窗口中。
在C#中 .Net Framework为编程人员提供了很好的操作双缓冲的方法,为采用双缓冲机制绘制比较复杂的图像数据带来便捷。下面简单的介绍在C#中实现双缓冲的几种方法。
利用默认的双缓冲
(1)在应用程序中使用双缓冲的最简便的方法是使用 .NET Framework 为窗体和控件提供的默认双缓冲。通过将 DoubleBuffered 属性设置为 true。
1.this.DoubleBuffered=true;
(2)使用 SetStyle 方法可以为 Windows 窗体和所创作的 Windows 控件启用默认双缓冲,在窗体或者控件的构造函数中添加如下代码即可:
1. SetStyle(ControlStyles.ResizeRedraw,true);
2. SetStyle(ControlStyles.OptimizedDoubleBuffer,true);
3. SetStyle(ControlStyles.AllPaintingInWmPaint,true);
4.
或者:
1. this.SetStyle(ControlStyles.ResizeRedraw |
2. ControlStyles.OptimizedDoubleBuffer |
3. ControlStyles.AllPaintingInWmPaint, true);
4. this.UpdateStyles();
在C# 中手动管理缓冲图像有2中方法,一种是利用单独开辟内存实现双缓冲这种传统的方法,还有一种是利用 .Net Framework 中独有的BufferedGraphicsContext类实现。
方法一: 自己开辟一个缓冲区(如一个不显示的Bitmap对象),在其中绘制完成后,再一次性显示,代码如下:
1. //1、在内存中建立一块“虚拟画布”
2. Bitmap bmp = new Bitmap(200,200);
3.
4. //2、获取这块内存画布的Graphics引用
5. Graphics bufferGraphics = Graphics.FromImage(bmp);
6.
7. //3、在这块内存画布上绘图
8. bufferGraphics.Clear(this.BackColor);
9. bufferGraphics.DrawRectangle(Pens.Black,0,0,bmp.Width -1,bmp.Height -1);
10. bufferGraphics.DrawEllipse(Pens.Red,10,10,100,50);
11. bufferGraphics.DrawLine(Pens.Green,10,100,100,200);
12.
13. //4、将内存画布画到窗口中
14. using(Graphics g = e.Graphics)
15. {
16. g.DrawImage(bmp, 10, 10);
17. }
18.
19. //5. 释放资源
20. bmp.Dispose();
21. bufferGraphics.Dispose();
(1)基本功能
①多个页面置换算法的比较
②动画的演示
(2)需要运行的环境 windows10
(3)安装 exe文件
(4)运行 windows10
(5)操作 直接界面即可
(1)完成的部分
动画游戏模型搭建完毕,正打算应用于一个自己开发的小游戏。
(2)未完成的部分
小游戏实现了人物的移动,以及自动寻路的过程,正在完善打怪升级的程序。
(3)创新功能
①对CLOCK算法进行了改进,对PBA算法进行挖掘,同时对LFU算法的不同实现过程进行了对比。
②允许多个线程并发执行,对比鲜明。
③制作了较为直观的动画展示过程
(4)收获、经验、教训和感受等
收获:
①自己写了简易的碰撞,跟随,对话模型,对于一些集成开发工具的原理有了一定的简单了解
②掌握了一定的多线程编程策略
③对数据在cpu,内存,外存之间的运转有了更深的理解
(5)经验教训:
①界面布局很重要
②明确用户的需求
③项目的开发要及时的进行审查。
(6)感受:
最后,衷心感谢老师的悉心指导,感谢这次课程设计的机会,使我们得到了很好的锻炼,学无止境,我们现在了解的东西还很少,还不能很好地掌握自己的专业知识,我们要谦虚的积极认真学习,不断的增强自身的能力,提高个人素质,向一个真正的IT人士发展。
[1]博客boobo 页面置换算法及例题
原文链接:https://www.cnblogs.com/RB26DETT/p/10035804.html
[2]博客IuStar 操作系统(5)页面置换算法
原文链接:https://www.cnblogs.com/lustar/p/7875705.html
[3]博客 奄奄不息 页面置换算法详解
原文链接:https://blog.csdn.net/qq_41209741/article/details/99586257