《操作系统》实验教学大纲 (一) 课程简介 操作系统是计算机科学技术、信息管理、软件工程等专业的一门专业基础课,他的先导课程有:数据结构、计算机原理、程序设计等课程;操作系统实验课是该门课程的重要组成部分。 (二) 教学目的 通过本实验课的学习,目的使学生能加深操作系统中基本概念的理解,同时对操作系统中的常用算法的实现具有更直观的理解。 (三) 使用教材 本课程理论课的教材为:《计算机操作系统》,(修订版),汤子瀛、哲风屏、汤小丹编著,西安电子科技出版社,2002.8 (四) 考核方式 本实验的考核以所提交的上机作业为考核依据,实验课程的成绩不单独计算,它占总成绩的20%。 (五) 实验课内容 实验编号 01201001 实验名称 用汇编语言写出P、V 操作原语 实验内容 编程 实验类型 设计 实验学时 2学时 实验目的 掌握P、V操作的设计方法 实验要求 必修 实验编号 01201002 实验名称 用高级语言写出基于消息传递的进程通讯方法 实验内容 编程 实验类型 设计 实验学时 2学时 实验目的 掌握消息传递和同步 实验要求 必修 实验编号 01201003 实验名称 用高级语言写出银行家算法 实验内容 编程 实验类型 设计 实验学时 2学时 实验目的 掌握避免死锁的方法 实验要求 必修 实验编号 01201004 实验名称 用高级语言写出按最先适应算法的存储器管理程序 实验内容 编程 实验类型 设计 实验学时 2学时 实验目的 掌握分区管理的实现方法 实验要求 必修 实验编号 01201005 实验名称 用高级语言写出改进的LRU算法的程序实现 实验内容 编程 实验类型 设计 实验学时 2学时 实验目的 掌握一种淘汰算法 实验要求 必修 实验编号 01201006 实验名称 用高级语言写出事务实现的模拟程序 实验内容 编程 实验类型 设计 实验学时 2学时 实验目的 掌握REDO()和UNDO()过程的设计 实验要求 必修 说明:本课程的所有实验约需要16~18个学时,实验全部在微机上实现,所以上表中未列出实验所需的设备;上机时间从第6周到第16周 早上看了水晶龙的一个undo的例子,其中虽然有一些command模式的思想,但是用的好像不够正宗。其实用command模式实现undo和redo应该还是比较容易的,做个例子练练手吧: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <TITLE> test command </TITLE> <META NAME="Author" CONTENT="emu"> <SCRIPT LANGUAGE="JavaScript"> <!-- var actionStack = [];//操作栈 var actionIndex = 0;//操作栈中当前操作的指针 //-----------------------? command对象基类 ------------------------------ function BaseAction(){ } BaseAction.prototype.exec=function(){ ??????? actionStack[actionIndex++] = this; ??????? actionStack.length = actionIndex; } BaseAction.prototype.undo=function(){ ??????? alert("此操作未定义相应的undo操作"); } BaseAction.prototype.redo=function(){ ??????? alert("此操作未定义相应的redo操作"); } //-----------------------? move操作的command对象 ------------------------------ function MoveAction(elm){ ??? this.oldX = elm.oldX; ??? this.oldY = elm.oldY; ??? this.newX = elm.newX; ??? this.newY = elm.newY; ??? this.sourceElement = elm; ??? this.status = "done"; } MoveAction.prototype = new BaseAction(); MoveAction.prototype.undo=function(){ ??? if (this.status != "done") return; ??? this.sourceElement.style.left = this.oldX; ??? this.sourceElement.style.top = this.oldY; ??? this.status = "undone"; } MoveAction.prototype.redo=function(){ ??? if (this.status != "undone") return; ??? this.sourceElement.style.left = this.newX; ??? this.sourceElement.style.top = this.newY; ??? this.status = "done"; } //-----------------------? change操作的command对象 ------------------------------ function ChangeAction(elm){ ??? this.sourceElement = elm; ??? this.oldValue = elm.defaultValue; ??? this.newValue = elm.value; ??? elm.defaultValue = elm.value; ??? this.status = "done"; } ChangeAction.prototype = new BaseAction(); ChangeAction.prototype.undo = function(){ ??? if (this.status != "done") return; ??? this.sourceElement.value = this.sourceElement.defaultValue = this.oldValue; ??? this.status = "undone"; } ChangeAction.prototype.redo = function(){ ??? if (this.status != "undone") return; ??? this.sourceElement.value = this.newValue; ??? this.status = "done"; } //---------------------? 全局函数? ---------------------------- function undo(){ ??? if (actionIndex>0){ ??????? actionStack[--actionIndex].undo(); ??? } } function redo(){ ??? if (actionIndex<actionStack.length){ ??????? actionStack[actionIndex++].redo(); ??? } } function checkMouseMove(){ ??? if (window.activeElement){ ??????? var elm = window.activeElement; ??????? elm.style.left = event.x-elm.innerX; ??????? elm.style.top = event.y-elm.innerY; ??? } } function releaseMouse(){ ??? if (window.activeElement){ ??????? activeElement.newX = event.x-activeElement.innerX; ??????? activeElement.newY = event.y-activeElement.innerY; ??????? new MoveAction(activeElement).exec(); ??????? window.activeElement = null; ??? } } function drag(){ ??? if (event.button!=2) return; ??? var elm = event.srcElement; ??? window.activeElement = elm; ??? elm.oldX = elm.offsetLeft; ??? elm.oldY = elm.offsetTop; ??? elm.innerX = event.x - elm.oldX; ??? elm.innerY = event.y - elm.oldY; } function changeValue(){ ??? new ChangeAction(event.srcElement).exec(); } //--> </SCRIPT> </HEAD> <BODY onmousemove="checkMouseMove()" onmouseup="releaseMouse()" oncontextmenu="return false"> <input type=button onclick=undo() value=undo> <input type=button onclick=redo() value=redo> <input value="drag me" onmousedown="drag()" onchange="changeValue()" style="position:absolute;left:150;color:blue"> <input value="drag me" onmousedown="drag()" onchange="changeValue()" style="position:absolute;left:350;color:green"> <input value="drag me" onmousedown="drag()" onchange="changeValue()" style="position:absolute;left:550;color:violet"> </BODY> </HTML> 用鼠标右键拖动输入框,或者直接修改输入框的内容,可以一一 undo 再 redo 。 看来效果还不错。秋水说有bug,不过我没有重现出来呢? --??汤子瀛计算机操作系统(西电)答案--第五章 1. 可采用哪几种方式将程序装入内存?它们分别适用于何种场合? a. 首先由编译程序将用户源代码编译成若干目标模块,再由链接程序将编译后形成的目标模块和所需的 ---库函数链接在一起,组成一个装入模块,再由装入程序将装入模块装入内存; b. 装入模块的方式有: 绝对装入方式,可重定位方式和动态运行时装入方式; c. 绝对装入方式适用于单道程序环境下; d. 可重定位方式适用于多道程序环境下; e. 动态运行时装入方式也适用于多道程序环境下. 2. 何谓静态链接及装入时动态链接和运行时的动态链接? a. 静态链接是指事先进行链接形成一个完整的装入模块,以后不再拆开的链接方---式; b. 装入时动态链接是指目标模块在装入内存时,边装入边链接的链接方式; c. 运行时的动态链接是将某些目标模块的链接推迟到执行时才进行. 3. 在进行程序链接时,应完成哪些工作? a. 对相对地址进行修改; b. 变换外部调用符号. 4. 在动态分区分配方式中,可利用哪些分区分配算法? a. 首次适应算法; b. 循环首次适应算法; c. 最佳适应算法. 5. 在动态分区分配方式中,应如何将各空闲分区链接成空闲分区链? 应在每个分区的起始地址部分,设置一些用于控制分区分配的信息,以及用于链接各分区的前向指针; 在分区尾部则设置一后向指针,通过前,后向指针将所有的分区链接成一个双向链. 6. 为什么要引入动态重定位?如何实现? a. 为了在程序执行过程中,每当访问指令或数据时,将要访问的程序或数据的逻辑地址转换成物理地 ---址,引入了动态重定位. b. 可在系统中增加一个重定位寄存器,用它来装入(存放)程序在内存中的起始地址,程序在执行时,真 ---正访问的内存地址是相对地址与重定位寄存器中的地址相加而形成的,从而实现动态重定位. 7. 试用类Pascal语言来描述首次适应算法进行内存分配的过程. (略) 8. 在采用首次适应算法回收内存时,可能出现哪几种情况?应怎样处理这些情况? a. 回收区与插入点的前一个分区相邻接,此时可将回收区与插入点的前一分区合并,不再为回收分区 ---分配新表项,而只修改前邻接分区的大小; b. 回收分区与插入点的后一分区相邻接,此时合并两区,然后用回收区的首址作为新空闲区的首址,大 ---小为两者之和; c. 回收区同时与插入点的前后两个分区邻接,此时将三个分区合并,使用前邻接分区的首址,大小为 ---三区之和,取消后邻接分区的表项; d. 回收区没有邻接空闲分区,则应为回收区单独建立一个新表项,填写回收区的首址和大小,并根据 ---其首址,插入到空闲链中的适当位置. 9. 在系统中引入对换后带有哪些好处? 能将内存中暂时不运行的进程或暂时不用的程序和数据,换到外存上,以腾出足够的内存空间,把已 具备运行条件的进程或进程所需的程序和数据换入内存,从而大大地提高了内存的利用率. 10 为实现对换,系统应具备哪几方面功能? a. 对对换空间的管理; b. 进程的换出; c. 进程的换入. 11 在以进程为单位进行对换时,每次是否都将整个进程换出?为什么? a. 以进程为单位进行对换时,每次都将整个进程换出; b. 目的为了解决内存紧张的问题,提高内存的利用率. 12 为实现分页存储管理,需要哪些硬件支持?你认为以Intel 8086,MC68000, Intel 80286为芯片的微机,是否适合于实现分页管理? (有待讨论) 13 请较详细地说明,引入分页存储管理(估计印错了,是分段存储管理)是为了满足用户哪几方面的需要? a. 方便了编程; b. 实现了分段共享; c. 实现了分段保护; d. 实现了动态链接; e. 实现了动态增长. 14 在具有快表的段页式存储管理方式中,如何实现地址变换? 首先,必须配置一段表寄存器,在其中存放段表始址和段长TL. 进行地址变换时,先利用段号S,与段长TL 进行比较,若S<TL,表示未越界,(若S>=TL,表示段号太大,访问越界,产生越界中断信号)于是利用段表 始址和段号来求出该段对应的段表项在段表中的位置,从中求出该段的页表始址,并利用逻辑地址中的段 内页号P来获得对应页的页表项位置,从中读出该页所在的物理块号b,再用块号b和页内地址构成物理地址. 15 为什么说分段系统较之分页系统更易于实现信息共享和保护? a. 对于分页系统,每个页面是分散存储的,为了实现信息共享和保护,则页面之间需要一一对应起来,为此 ---需要建立大量的页表项; b. 而对于分段系统,每个段都从0开始编址,并采用一段连续的地址空间,这样在实现共享和保护时,只需 ---为所要共享和保护的程序设置一个段表项,将其中的基址与内存地址一一对应起来即可. 16 分页和分段有何区别? a. 分页和分段都采用离散分配的方式,且都要通过地址映射机构来实现地址变换,这是它们的共同点; b. 对于它们的不同点有三,第一,从功能上看,页是信息的物理单位,分页是为实现离散分配方式,以消减 ---内存的外零头,提高内存的利用率,即满足系统管理的需要,而不是用户的需要;而段是信息的逻辑单位, ---它含有一组其意义相对完整的信息,目的是为了能更好地满足用户的需要; c. 页的大小固定且由系统确定,而段的长度却不固定,决定于用户所编写的程序; d. 分页的作业地址空间是一维的,而分段的作业地址空间是二维的. 17 试全面比较连续分配和离散分配方式. a. 连续分配是指为一个用户程序分配一个连续的地址空间,包括单一连续分配方式和分区式分配方式,前者 ---将内存分为系统区和用户区,系统区供操作系统使用,用户区供用户使用,是最简单的一种存储方式, ---但只能用于单用户单任务的操作系统中;分区式分配方式分为固定分区和动态分区,固定分区是最简单的 ---多道程序的存储管理方式,由于每个分区的大小固定,必然会造成存储空间的浪费;动态分区是根据进程 ---的实际需要,动态地为之分配连续的内存空间,常用三种分配算法: 首次适应算法FF,该法容易留下许多 ---难以利用的小空闲分区,加大查找开销;循环首次适应算法,该算法能使内存中的空闲分区分布均匀,但 ---会致使缺少大的空闲分区;最佳适应算法,该算法也易留下许多难以利用的小空闲区; b. 离散分配方式基于将一个进程直接分散地分配到许多不相邻的分区中的思想,分为分页式存储管理,分段 ---存储管理和段页式存储管理. 分页式存储管理旨在提高内存利用率,满足系统管理的需要,分段式存储管 ---理则旨在满足用户(程序员)的需要,在实现共享和保护方面优于分页式存储管理,而段页式存储管理则是 ---将两者结合起来,取长补短,即具有分段系统便于实现,可共享,易于保护,可动态链接等优点,又能像 ---分页系统那样很好的解决外部碎片的问题,以及为各个分段可离散分配内存等问题,显然是一种比较有效 ---的存储管理方式; c. 综上可见,连续分配方式和离散分配方式各有各自的特点,应根据实际情况加以改进和利用. ※ 来源:考研论坛 bbs.kaoyan.   --??作者:ltply --??发布时间:2004-1-9 08:18:00 --?? 汤子瀛计算机操作系统(西电)答案--第六章 1. 在请求分页系统中,其页表项中包含那些数据项? 它们的作用是什么? a. 在请求分页系统中,其页表项中包含的数据项有页号,物理块号,状态位P,访问字段A,修改位M和 ---外存地址; b. 其中状态位P指示该页是否调入内存,供程序访问时参考; c. 访问字段A用于记录本页在一段时间内被访问的次数,或最近已有多长时间未被访问,提供给置换算法 ---选择换出页面时参考; d. 修改位M表示该页在调入内存后是否被修改过; e. 外存地址用于指出该页在外存上的地址,通常是物理块号,供调入该页时使用. 2. 一个计算机系统的虚拟存储器,其最大容量和实际容量分别由什么决定? a. 最大容量由内存和外存之和决定; b. 实际容量由内存决定. 3. 虚拟存贮器有那些特征? 其中最本质的特征是什么? a. 虚拟存储器具有离散性,多次性,对换性和虚拟性的特征; b. 其中最本质的特征是离散性,在此基础上又形成了多次性和对换性,所表现出来的最重要的特征是 ---虚拟性. 4. 实现虚拟存储器要那些硬件支持? a. 对于为实现请求分页存储管理方式的系统,除了需要一台具有一定容量的内存及外存的计算机外,还 ---需要有页表机制,缺页中断机构以及地址变换机构; b. 对于为实现请求分段存储管理方式的系统,除了需要一台具有一定容量的内存及外存的计算机外,还 ---需要有段表机制,缺段中断机构以及地址变换机构; 5. 在实现虚拟存储器时的几个关键技术是什么? (有待讨论) 6. 在请求分页系统中,页表应包括那些数据项?每项的作用是什么? (同第一题) 7. 在请求分页系统中,应从何处将所需页面调入内存? a. 在进行地址变换时,首先去检索快表,试图从中找出所要访问的页,若找到,便修改页表项中的访问 ---位,对于写指令,还须将修改位置1,然后利用页表项中给出的物理块号和页内地址,形成物理地址; b. 如果在快表中未找到该页的页表项,则应再到内存中去查找页表,再从找到的页表项中的状态位来 ---了解该页是否已调入内存,如果该页已调入内存,应将此页的页表项写入快表,当快表已满时,应先 ---调出按某种算法所确定的页的页表项,然后再写入该页的页表项; c. 如果该页尚未调入内存,这时便应产生缺页中断,请求OS从外存中把该页调入内存; d. 外存分为文件区和对换区,若系统有足够的对换区空间,可在进程运行前,将与该进程有关的文件 ---拷贝到对换区,需要时从对换区调入; e. 若系统缺少足够的对换区空间,则凡是不会被修改的文件,可直接从文件区调入,需换出时可不必 ---写入外存,但对于可能被修改的部分,在将它们换出时,便须调到对换区,以后需要时再从对换区 ---调入. 8. 在请求分页系统中,常采用哪几种页面置换算法? a. 最佳置换算法; b. 先进先出算法; c. 最近最久未使用LRU置换算法; d. Clock置换算法; e. 此外,还有最少使用置换算法和页面缓冲算法. 9. 某虚拟存储器的用户空间共有32个页面,每页1KB,主存16KB. 假定某时刻 ---为用户的第0,1,2,3页分别分配的物理块号为5,10,4,7,试将虚拟地址 ---0A5C和093C变换为物理地址. a. 将0A5C变换为2进制为: 0000,1010,0101,1100,由于页面大小为1KB约为2的10次方,所以0A5C的页号 ---为2,对应的物理块号为:4,所以虚拟地址0A5C的物理地址为125C; b. 将093C变换为2进制为: 0000,1001,0011,1100,页号也为2,对应的物理块号也为4,此时虚拟地址 ---093C的物理地址为113C. 10 在请求分页系统中,通常采用那种页面分配方式?为什么? a. 在请求分页系统中,有固定和可变分配两种分配方式; b. 采用固定分配方式是基于进程的类型(交互型)或根据程序员,系统管理员的建议,为每个进程分配 ---一固定页数的内存空间,在整个运行期间不再改变; c. 采用可变分配方式有全局置换和局部置换两种,前者易于实现,后者效率高. 11 在一个请求分页系统中,采用LRU页面置换算法时,假如一个作业的页面走向 ---为4,3,2,1,4,3,5,4,3,2,1,5,当分配给该作业的物理块数M分别 ---为3和4时,试计算访问过程中所发生的缺页次数和缺页率?比较所得结果? a. 当分配给该作业的物理块数M为3时,所发生的缺页率为7,缺页率为: 7/12=0.583; b. 当分配给该作业的物理块数M为4时,所发生的缺页率为4,缺页率为: 4/12=0.333. 12 在置换算法中,LRU和LFU哪个更常用?为什么? a. LRU与LFU置换算法的页面的访问图完全相同,即使用的硬件是相同的; b. 但是LFU并不能真正访问反映出页面的使用情况. 13 实现LRU算法所需的硬件支持是什么? a. 寄存器,用于记录某进程在内存中各页的使用情况; b. 栈,用于保存当前使用的各个页面的页面号. 14 试说明改进型Clock置换算法的基本原理. a. 因为对于修改过的页面在换出时所付出的开销将比未被修改过的页面的开销大,所以在改进型Clock ---算法中,出了须考虑到页面的使用情况外,还须再增加一个置换代价这一因素; b. 在选择页面作为淘汰页面时,把同时满足未使用过和未被修改作为首选淘汰页面. 15 什么是抖动? 产生抖动的原因是什么? a. 抖动(Thrashing)就是指当内存中已无空闲空间而又发生缺页中断时,需要从内存中调出一页程序或 ---数据送磁盘的对换区中,如果算法不适当,刚被换出的页很快被访问,需重新调入,因此需再选一页 ---调出,而此时被换出的页很快又要被访问,因而又需将它调入,如此频繁更换页面,以致花费大量的 ---时间,我们称这种现象为"抖动"; b. 产生抖动的原因是由于CPU的利用率和多道程序度的对立统一矛盾关系引起的,为了提高CPU利用率, ---可提高多道程序度,但单纯提高多道程序度又会造成缺页率的急剧上升,导致CPU的利用率下降,而 ---系统的调度程序又会为了提高CPU利用率而继续提高多道程序度,形成恶性循环,我们称这时的进程 ---是处于"抖动"状态. 16 试说明请求分段系统中的缺页中断处理过程? (见P185图6-12) 17 如何实现分段共享? a. 可在每个进程的段表中,用相应的表项来指向共享段在内存中起始地址; b. 配置相应的数据结构作为共享段表,可在段表项中设置共享进程计数Count,每调用一次该共享段, ---Count指增1,每当一个进程释放一个共享段时,Count执行减1操作,若减为0,则由系统回收该共享 ---段的物理内存,以及取消在共享段表中该段所对应的表项; c. 对于一个共享段,应给不同的进程以不同的存取权限; d. 不同的进程可以使用不同的段号去共享该段. 18 Intel 80386芯片可支持哪几种方式的存储管理? a. 不分段也不分页的存储管理方式; b. 分页不分段的存储管理方式; c. 分段不分页的存储管理方式; d. 分段分页存储管理方式. 19 试说明80386的分段地址变换机构的工作原理. a. 采用段寄存器和虚地址结构; b. 在分段部件中,地址变换是将逻辑地址变换为线性地址,然后送分页部件中.(具体见P191) 20 试说明80386的两级分页地址变换机构的原理. (见P193) 21 可通过哪些途径来提高内存利用率? (有待讨论,该题可以看成是对本章的本质内容的全面概括和总结) ※ 来源:考研论坛 bbs.kaoyan.com   --??作者:ltply --??发布时间:2004-1-9 08:20:00 --?? 汤子瀛计算机操作系统(西电)答案--第十三章 1. UNIX系统有哪些基本特征? a. 开放性; b. 多用户,多任务环境; c. 功能强大,实现高效; d. 提供了丰富的网络功能. 2. UNIX系统核心分成哪两大部分?各包含哪些功能? a. UNIX系统核心分为进程控制子系统部分和文件子系统部分; b. 进程控制子系统包含进程控制,进程通信,存贮器管理和进程调度功能; ---文件子系统包含文件管理,高速缓冲机制和设备驱动程序的功能. 3. UNIX系统中的PCB含哪几部分?并用图来说明它们之间的关系. a. UNIX系统中的PCB含四部分:进程表项,U区,进程区表和系统区表项; b. 图见P396. 4. 进程映象含哪几部分?其中系统级上下文的动态部分的作用是什么? a. 进程映象(Process Image)包含三部分:用户级上下文,寄存器上下文和系统级上下文; b. 系统级上下文的动态部分包含核心栈和若干层寄存器上下文,它的作用是当因中断或系统调用而进入 ---核心态时,核心把一个寄存器上下文压入核心栈,退出系统调用时,核心又将弹出一个寄存器上下 ---文,在进行上下文切换时,核心将压入老进程的上下文层,而弹出新进程的上下文层. 5. 在UNIX系统中,用于进程控制的系统调用有哪些(主要的)?它们的主要功能是什么? a. fork,用于创建一个新进程; b. exec,改变进程的原有代码; c. exit,实现进程的自我终止; d. wait,将调用进程挂起,等待子进程终止; e. getpid,获取进程标志符; f. nice,改变进程的优先级. 6. 为创建一个新进程,需做哪些工作? a. 为新进程分配一进程表项和进程标志符; b. 检查同时运行的进程数目; c. 拷贝进程表项中的数据; d. 子进程继承父进程的所有文件; e. 为子进程创建进程上下文; f. 子进程执行. 7. 为何要采取进程自我终止方式?如何实现exit? a. 为了及时回收进程所占用的资源,并减少父进程的干预,UNIX系统利用exit来实现进程的自我终止; b. 实现exit,核心应该做的工作是: ---关闭软中断; ---回收资源; ---写记帐信息; ---置进程为"僵死状态". 8. UNIX系统采用什么样的进程调度算法?其优先级是如何计算的? a. UNIX系统采用的是多级反馈队列轮转调度算法; b. 每隔1秒,核心按如下公式重新计算用户优先数: ---优先数=(最近使用CPU的时间/2)+基本用户优先数. 9. 试说明信号与中断两种机制间的异同处? a. 相似处: ---信号和中断都采用了相同的异步通信方式; ---当检测出有信号或中断请求时,都是暂停正在执行的程序而转去执行相应的处理程序; ---两者都是在处理完毕后返回到原来的断点; ---对信号或中断都可进行屏蔽; b. 差异处: ---中断有优先级,而信号没有优先级,即所有信号都是平等的; ---信号处理程序是在用户态下运行的,而中断处理程序则是在核心态下运行的; ---中断响应是及时的,而信号响应通常都有较大的时间延迟. 10 扼要说明信号机制中信号的发送和对信号的处理功能? a. 信号的发送是指由发送进程把信号送到指定进程的信号域的某一位上; b. 对于对信号的处理功能: 首先, ---利用系统调用signal(sig,func)预置对信号的处理方式,func=1时,该类信号被屏蔽; ---func=0时,进程收到信号后终止自己; ---func为非0,非1类整数时,func的值即作为信号处理程序的指针. 然后, ---如果进程收到的软中断是一个已决定要忽略的信号(func=1),进程不作任何处理返回; ---进程收到软中断后便退出(func=0); ---执行用于设置的软中断处理程序. 11 什么是管道?无名管道和有名管道的主要差别是什么? a. 管道是指能够连接一个写进程和一个读进程的,并允许它们以生产者-消费者方式进行通信的一个 ---共享文件,又称为pipe文件; b. 无名管道是一个临时文件,是利用系统调用pipe()建立起来的无名文件,没有路径名,只有 ---调用pipe的进程及其子孙进程才能识别此文件描述符而利用该文件(管道)进行通信; ---有名管道是利用mknod系统调用建立的,是可以在文件系统中长期存在的,既有路径名的文件, ---其它进程可以知道其存在,并利用该路径名来访问该文件. 12 读,写管道时应遵循哪些规则? a. 对pipe文件大小的限制; b. 进程互斥; c. 进程写管道时,检查是否有足够的空间存放要写的数据,若有,则写入,若无,则由核心对该索引 ---结点做出标志,然后让写进程睡眠等待,直到读进程读走数据后,再将写等待进程唤醒; d. 进程读管道时,检查是否有足够的要读的数据,若有,则进程从读指针的初始值处去读数据,每读出 ---一块后,便增加地址项的大小,读结束后由核心修改索引结点中的读指针,并唤醒所有等待的写进程, ---若无,则在读完后,进程暂时进入睡眠等待,直到写进程又将数据写入管道后,再将读进程唤醒. 13 在消息机制中,有哪些系统调用?并说明它们的用途. 在UNIX中,消息机制向用户提供了四个系统调用: a. msgget(),用来建立一消息队列,或者获取一消息队列的描述符; b. msgsnd(),用于向指定的消息队列发送一个消息,并将该消息链接到该消息队列的尾部; c. msgrcv(),用于从指定的消息队列中接收指定类型的消息; d. msgctl(),用来读取消息队列的状态信息并进行修改. 14 在共享存储区机制中,有哪些系统调用?并扼要说明它们的用途. a. shmget(),建立一共享存储区; b. shmat(),将共享存储区附接到进程的虚地址空间上; c. shmdt(),把共享存储区与新进程断开; d. shmct(),对共享存储区的状态信息进行读取和修改,也可以断开进程与共享存储区的连接. 15 核心在执行shmget系统调用时,需完成哪些工作? a. 首先检查共享存储区表,若找到指定key的表项,表明该共享区已经建立,此时返回该表项的描述符 ---shmid; b. 若未找到指定的key表项,而flag标志又为IPC_CREAT,且参数size值在系统限制值内,则分配一系统 ---空闲区作为共享区的页表区,分配响应的内存块,再将这些块号填入页表中; c. 核心在共享存储区和系统区表中,为新建立的共享区分配一空表项,并在共享存储区表填上存储区的 ---关键字及其大小,共享区页表的始址,指向系统区表项的指针等,最后返回共享存储区的描述符---shmid. 16 在信号量集机制中,有哪些系统调用?并说明它们的用途. a. semget(),建立信号量集; b. semop(),对信号量进行操作. 17 核心是如何对信号量进行操纵的? a. 核心根据sem_op来改变信号量的值,可分为3种情况; b. sem_op的值为正,则将其值加到信号量的值上,它相当于通常的V操作; c. sem_op的值为负,相当于P操作,若信号量的值大于操作值的绝对值,则核心将一个负整数加到信号 ---量值上,否则,核心将已经操作了的信号量,恢复到系统调用开始时的值; d. 若(sem_flg&IPC_NOWAIT)为真,便立即返回,否则,让进程睡眠等待. 18 为实现请求调页管理,在UNIX系统中,配置了哪些数据结构? a. 页表; b. 磁盘块描述表; c. 页框数据表; d. 对换使用表. 19 在UNIX系统中,如何改变有效页的年龄?并用实例说明之. a. 一个页可计数的最大年龄,取决于它的硬件设施; b. 对于只设置两位作为年龄域时,其有效页的年龄只能取值为0,1,2,3,当该页的年龄为0,1,2时, ---该页处于不可换出状态,而当其年龄达到3时,则可为换出状态,每当内存中的空闲页面数低于某 ---规定的低限时,核心便唤醒换页进程,又换页进程取检查内存中的每一个活动的,非上锁的区,对 ---所有有效区的年龄字段加1,对于那些年龄已增至3的页便不再加1,而是将它们换出,如果这种页已 ---被进程访问过,便将年龄域中的年龄降为0. 20 当需访问的缺页是在可执行文件上或在对换设备上时,应如何将它调入内存? 核心先为缺页分配一内存页,修改该页表项,使之指向内存页,并将页面数据表项放入相应的散列队列 中,然后把该页从对换设备上调入内存,当I/O操作完成时,核心把请求调入该页的进程唤醒. 21 在将一页换出时,可分为哪几种情况?应如何处理这些情况? a. 若在对换设备上已有被换出页的拷贝,且被换出页的内容未被修改,则此时核心不必将该页重写回 ---对换设备上,而只需将该页的页表项中的有效位清零,并将页框数据表项中的引用计数减1,最后 ---将该页表项放入空闲页链表中; b. 若在对换设备上没有被换出的拷贝,则换出进程应将该页写到对换设备上,可采用页面链集中写入; c. 在对换设备上已有换出页的副本,但该页内容已被修改过,此时核心将该页在对换设备上的原有空间 ---释放,再重新将该页拷贝到对换设备上,使在对换设备上的拷贝内容总是最新  一、概述 Command(命令)模式可用于将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,即允许用户指定对何种对象执行何种操作;或者,对请求排队或记录请求日志,以及支持可撤消的操作。 二、结构 Command模式的结构如下图所示:  图1、Command模式类图示意 上图中包括如下角色: 客户(Client)角色:创建了一个具体命令(ConcreteCommand)对象并确定其接收者。 命令(Command)角色:声明了一个给所有具体命令类的抽象接口。这是一个抽象角色。 具体命令(ConcreteCommand)角色:定义一个接受者和行为之间的弱耦合;实现Execute()方法,负责调用接收考的相应操作。Execute()方法通常叫做执方法。 请求者(Invoker)角色:负责调用命令对象执行请求,相关的方法叫做行动方法。 接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。 从Command模式的结构似乎可以看出几分Adapter模式的影子,确实如此,Invoker通过Command对Receiver的Adptee来Execute?Receiver::Action,但Command模式的意义不在于此,Command模式的目的在于通过将需要操作的对象及所执行的操作封装成一个独立的对象,而不在于简单的接口的转换;此外,二者还有一个显著的区别在于Adaptee对client往往是不可见的,而Receiver对Client往往是可见的。Client只知道Adapter::Request(),而不知(或无需知道)其内部其实调的是Adaptee::SpecialRequest(),而在Command模式中Client往往需要显式地把一个Receiver对象传给一个ConcreteCommand对象,以使其能调用Receiver::Action()。 三、应用 在下面的情况下应当考虑使用命令模式: 1、使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。 2、需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。 3、系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。 4、如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。 5、一个系统需要支持交易(Transaction)。一个交易结构封装了一组数据更新命令,使用命令模式来实现交易结构可以使系统增加新的交易类型。 四、优缺点 Command模式允许请求的一方和接收请求的一方能够独立演化,从而且有以下的优点: Command模式使新的命令很容易地被加入到系统里。 允许接收请求的一方决定是否要否决(Veto)请求。 能较容易地设计一个命令队列。 可以容易地实现对请求的Undo和Redo。 在需要的情况下,可以较容易地将命令记入日志。 Command模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。 Command类与其他任何别的类一样,可以修改和推广。 你可以把Command对象聚合在一起,合成为Composite?Command。比如宏Command便是Composite?Command的例子。 由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。 Command模式的缺点如下: 使用Command模式会导致某些系统有过多的具体Command类。某些系统可能需要几十个,几百个甚至几千个具体Command类,这会使Command模式在这样的系统里变得不实际。 五、举例 1、JDK的AbstractUndoableEdit为我们提供了基本的Undo/Redo支持,当我们需要为Java应用引入Undo/Redo操作时,只需简单地从AbstractUndoableEdit类派生子类将操作封装成类即可。下面是一个简单的封装Adjust操作的例子(代码取自XModeler,Java?Code): public?class?SelectTool ????extends?AbstractTool?{ ????//... ????public?void?mouseDragged(MouseEvent?e)?{ ????????//... ????????if?(!isDragRegistered)?{ ????????????desk.addUndoableEdit(new?AdjustUndoableEdit(desk,?(ModelObject)?oldC)); ????????????isDragRegistered?=?true; ????????} ????} } public?class?MainFrame?extends?JFrame?implements?PropertyChangeListener????//?Invoker,?the?Main?Window { ????//... ????protected?void?commandRedo()?{ ????????//?Find?active?child?window ????????JInternalFrame?internalframe?=?this.getActiveChild(); ????????//?notify?the?active?view?to?execute?the?command ????????if?(internalframe?!=?null?&&?internalframe?instanceof?ChildFrame)?{ ????????????ChildFrame?child?=?(ChildFrame)?internalframe; ????????????ModelView?view?=?child.getView(); ????????????view.redo(); ????????} ????} } public?class?AdjustUndoableEdit?extends?AbstractUndoableEdit?{ ????ModelObject?com;????//?state ????Desktop?desk;????????????//?Receiver,?the?Active?View ????Rectangle?oldRec; ????public?AdjustUndoableEdit(Desktop?desk,?ModelObject?com)?{ ????????this.com?=?com; ????????this.desk?=?desk; ????????oldRec?=?getBounds(); ????} ????Rectangle?getBounds()?{ ????????Rectangle?rec?=?(?(Component)?com).getBounds(); ????????return?rec; ????} ????public?String?getUndoPresentationName()?{ ????????return?"Undo_Adjust"; ????} ????public?String?getRedoPresentationName()?{ ????????return?"Redo_Adjust"; ????} ????public?void?undo()?throws?CannotUndoException?{????//?Execute?1 ????????super.undo(); ????????Rectangle?newRec?=?getBounds(); ????????((Component)com).setBounds(oldRec); ????????oldRec?=?newRec; ????????desk.fireAssociatorChanged(); ????} ????public?void?redo()?throws?CannotRedoException?{????//?Execute?2 ????????super.redo(); ????????Rectangle?newRec?=?getBounds(); ????????((Component)com).setBounds(oldRec); ????????oldRec?=?newRec; ????????desk.fireAssociatorChanged(); ????} } 上面的代码片断,虽然不是一个完整的Command模式的应用,但其中已经可以十分清晰地看到各个Role,以及他们之间的协作关系。 六、更进一步 在如何将执行请求封装为Command方面,STL的functor(或称function?objects)给了我们很多启示(当然functor并非STL首创,但STL使functor为更多人所熟知,因为在使用STL算法的过程中几乎不可避免要用到functor),STL的functor通过将操作封装成struct/class来实现操作的对象化,但STL设计functor不是为了支持所谓的Command模式,而是作为完整的模板库体系的一部分,与STL算法结合(另一个与STL算法结合使之发挥巨大功效的STL元素是iterator),因为,普通的函数指针很难向上提供统一的接口,而functor通过实现统一的operator?()为算法提供了统一的接口。由于主要为STL算法服务,STL只提供了两种简单的functor类型:unary_function和binary_function,你要自己实现用于STL算法的functor往往也需要从这两种functor类型派生,以遵循既定的约定,虽然,在一般的情况下,不这么做并不会出错(只有在使用binder1st/binder2nd等时才会报错)。而对于在普通应用中使用functor的情况,并不需要遵循上面的原则。 STL中unary_function和binary_function的定义如下(在functional中): template?<class?Arg,?class?Result> struct?unary_function?{ ????typedef?Arg?argument_type; ????typedef?Result?result_type; }; template?<class?Arg1,?class?Arg2,?class?Result> struct?binary_function?{ ????typedef?Arg1?first_argument_type; ????typedef?Arg2?second_argument_type; ????typedef?Result?result_type; }; 以下是一个典型的functor的定义(在functional中): template?<class?_Ret,?class?_Tp> class?mem_fun_t?:?public?unary_function<_Tp*,_Ret>?{ public: ??explicit?mem_fun_t(_Ret?(_Tp::*__pf)())?:?_M_f(__pf)?{} ??_Ret?operator()(_Tp*?__p)?const?{?return?(__p->*_M_f)();?} private: ??_Ret?(_Tp::*_M_f)(); }; 该模板类是模板类mem_fun的adaptee类之一,负责对成员函数进行封装,有了mem_fun,我们就可以方便地写出下面的代码: #include?<vector> #include?<iostream> #include?<functional> #include?<algorithm> using?namespace?std; struct?CBase { ????virtual?int?func()?=?0; }; struct?CDerived1?:?public?CBase { ????int?func()?{cout?<<?this?<<?"\tCDerived1::func"?<<?endl;?return?0;} }; struct?CDerived2?:?public?CBase { ????int?func()?{cout?<<?this?<<?"\tCDerived2::func"?<<?endl;?return?0;} }; int?main() { ????vector<CBase*>?v_a; ????CDerived1?d1; ????CDerived2?d2; ????v_a.push_back(&d1); ????v_a.push_back(&d2); ????for_each(v_a.begin(),?v_a.end(),?mem_fun(&CBase::func)); ????return?0; } 但STL的mem_func有个明显的问题,它是个unary_function,也就是说,只能用它来封装不带参数(并且返回类型不是void,这是由mem_fun的operator?()的定义决定的)的成员函数。 与STL?functor专属于STL算法不同,Loki库实现了一个通用的Functor模板类,该模板库可以支持最多15个参数,而与之类似的C++社区的明星boost库的function则可以支持最多50个参数。由于Loki::Functor的内部实现涉及TYPELIST等相关知识,不可能在此通过三言两语解释清楚,感兴趣的朋友可以参考Loki库的实现者Andrei.?A所著Modern?C++?Design一书,或Loki库源码;boost::function的实现原理可以参考<...>。 下面提供一个运用Loki::Functor实现Command模式的例子(你可以很容易地将其改成使用boost::function或普通functor,个人认为,与使用普通functor类相比,使用Loki::Functor或boost::function的好处仅在于可以方便灵活地使用functor,而无需将类改造成functor类,即实现operator?(),而且,可以方便地指定在对象上执行某个函数;当然,Loki::Functor和boost::function作为模板类,有其独特的优势--复用实现,但在下面的例子中这并不是一个需要考虑的问题): #include?<iostream> #include?<vector> #include?<Functor.h> #include?<SmallObj.cpp> using?namespace?std; using?namespace?Loki; struct?Receiver { ????virtual?bool?Action(int,?char*)?=?0; }; struct?ConcreteReceiver1?:?public?Receiver { ????bool?Action(int,?char*) ????{ ????????cout?<<?"ConcreteReceiver1::Action"?<<?endl; ????????return?true; ????} }; struct?ConcreteReceiver2?:?public?Receiver { ????bool?Action(int,?char*) ????{ ????????cout?<<?"ConcreteReceiver2::Action"?<<?endl; ????????return?true; ????} }; struct?Invoker { ????vector<Functor<bool,?TYPELIST_2(int,?char*)>*>?v_functor; ????void?AddCommand(Functor<bool,?TYPELIST_2(int,?char*)>*?p_command) ????{ ????????v_functor.push_back(p_command); ????} }; typedef?Invoker?CommandManager; int?main() { ????CommandManager?cm; ????ConcreteReceiver1?rcv1; ????ConcreteReceiver2?rcv2; ????Functor<bool,?TYPELIST_2(int,?char*)> ????????cmd1(&rcv1,?&Receiver::Action), ????????cmd2(&rcv2,?&Receiver::Action); ????cm.AddCommand(&cmd1); ????cm.AddCommand(&cmd2); ????//?Some?procedure... ????(*cm.v_functor[0])(1,?"A"); ????(*cm.v_functor[1])(2,?"B"); ????cmd1(1,?"A"); ????cmd2(2,?"B"); ????return?0; } 由于上述例子中只需要简单地对调用请求进行转发,其中没有了确切的Command及其子类ConcreteCommand,所有Command类被完全封装在各Functor对象中,你可以对其进行二次封装,将Functor对象改成成员变量,从而更好地控制执行过程。 对于上面的例子,不论采用何种封装Command的方式,对于可变参数都显得有点力不从心,因为对于程序设计来讲,可变参数始终是件令人头疼的事情,虽然有va_list/va_start/va_arg/va_end等辅助,但是参数类型检查呢?这里Reflect(反射)机制可以在一定程度上解决我们的问题。COM中IDispatch::Invoke和Java的Method::invoke就是Reflect的典型应用,借助反射机制,我们可以将参数封装成数组的形式,使得我们的Command可以对上层保持统一的接口形式(一个参数数组?+?一个返回值)。在某些情况下,boost::any也可以用于参数传递,但由于boost::any会在传递数据时丢失参数的类型信息,所以,如果要使用boost::any实现类似Reflect的效果,需要自己处理和保存类型信息(像IDispatch::Invoke使用DISPPARAMS一样)。 附注: 1.Java?Reflect机制示例 import?java.util.*; import?java.lang.reflect.*; public?class?Command ???{ ???private?Object?receiver; ???private?Method?command; ???private?Object[]?arguments; ???public?Command(Object?receiver,?Method?command, ???????????????????????????Object[]?arguments?) ??????{ ??????this.receiver?=?receiver; ??????this.command?=?command; ??????this.arguments?=?arguments; ??????} ???public?void?execute()?throws?InvocationTargetException, ?????????????????????????????????IllegalAccessException ??????{ ??????command.invoke(?receiver,?arguments?); ??????} ???} public?class?Test?{ ???public?static?void?main(String[]?args)?throws?Exception ??????{ ??????Vector?sample?=?new?Vector(); ??????Class[]?argumentTypes?=?{?Object.class?}; ??????Method?add?= ?????????Vector.class.getMethod(?"addElement",?argumentTypes); ??????Object[]?arguments?=?{?"cat"?}; ??????Command?test?=?new?Command(sample,?add,?arguments?); ??????test.execute(); ??????System.out.println(?sample.elementAt(?0)); ??????} ???} 2.COM?IDispatch::Invoke示例 ::CoInitialize(NULL); HRESULT?hr; IDispatch?*pDispatch=NULL; try { ????CLSID?clsid; ????hr=::CLSIDFromProgID(L"DispDll.Fun",&clsid); ????if(FAILED(hr))????throw(0); ????hr=::CoCreateInstance(clsid,NULL,CLSCTX_SERVER, ????????IID_IDispatch,(LPVOID?*)&pDispatch); ????if(FAILED(hr))????throw(0); ????OLECHAR?*arrFunName[]={L"Add"}; ????DISPID?dispID; ????hr=pDispatch->GetIDsOfNames(IID_NULL,arrFunName,1,LOCALE_SYSTEM_DEFAULT,&dispID); ????if(FAILED(hr))????throw(0); ????VARIANT?v[2]; ????v[0].vt=VT_I4;????v[0].lVal=3;????//?parameter?2 ????v[1].vt=VT_I4;????v[1].lVal=2;????//?parameter?1,?if?we?use?CComDispatchDriver::Invoke,?we?need?not?put?parameters?in?reverse. ????DISPPARAMS?params={v,NULL,2,0}; ????//?equals?to?the?following?4?lines /*????params.rgvarg=v; ????params.rgdispidNamedArgs=NULL; ????params.cArgs=2; ????params.cNamedArgs=0; */ ????VARIANT?vResult; ????hr=pDispatch->Invoke(dispID,IID_NULL,LOCALE_SYSTEM_DEFAULT,DISPATCH_METHOD, ????????????&params,&vResult,NULL,NULL); ????if(FAILED(hr))????throw(0); ????CString?s;????s.Format("%d",vResult.lVal); ????AfxMessageBox(s); ????pDispatch->Release(); } catch(...) { ????if(pDispatch)????pDispatch->Release(); } ::CoUninitialize(); 以下是一个典型的Invoke实现(如果你使用ATL的IDispatchImpl,则无需作以下处理),该函数的主要工作是对参数逐一进行解析,并根据传入的DISPID(即Method的编号)将参数填入对应的函数进行处理,当然,还包含一些必要的出错处理: STDMETHODIMP?CDispatchSink::Invoke(DISPID?dispidMember,?REFIID?riid, ???????????????????????????????????LCID?lcid,?WORD?wFlags, ???????????????????????????????????DISPPARAMS*?pdispparams,?VARIANT* ???????????????????????????????????pvarResult,?EXCEPINFO*?pexcepinfo, ???????????????????????????????????UINT*?puArgErr) { ???HRESULT?hr?=?S_OK; ???if?(pdispparams) ???{ ??????switch?(dispidMember) ??????{ ?????????case?2: ?????????{ ????????????if?(pdispparams->cArgs?==?1) ????????????{ ???????????????if?(pdispparams->rgvarg[0].vt?==?VT_I2) ??????????????????Event2(pdispparams->rgvarg[0].iVal); ???????????????else ??????????????????hr?=?DISP_E_TYPEMISMATCH;????//?parameter?type?is?not?desired. ????????????} ????????????else ???????????????hr?=?DISP_E_BADPARAMCOUNT;????//?parameter?count?is?wrong ????????????break; ?????????} //?Other?desired?case?statements ?????????default: ?????????{ ????????????hr?=?DISP_E_MEMBERNOTFOUND;????//?dispidMember?is?not?desired ????????????break; ?????????} ??????} ???} ???else ??????hr?=?DISP_E_PARAMNOTFOUND; ???return?hr; }