操作系统原理实用实验教程张鸿烈编著山东大学齐鲁软件学院山东大学计算机科学与技术学院前言第I页前言在现代计算机系统中,操作系统已经成为整个计算机系统的核心.
所有计算机硬件和应用软件都必须依赖操作系统的支撑,接受操作系统的管理.
一个性能优秀的操作系统会使整个计算机系统的资源发挥到极致;同时也为用户使用计算机提供了最大可能的方便.
因此介绍计算机操作系统原理的课程也就成为了计算机科学中最主要的课程之一.
本实验教程主要是为了配合操作系统原理阶段的实验教学而编写的.
实验内容和顺序主要考虑:紧密围绕操作系统原理课的主要教学内容.
重点选择操作系统科学领域中经典的、有启发性的实验课题.
实验课题的范围能基本覆盖整个操作系统原理课所讲授的内容.
实验的顺序能基本对应操作系统原理课讲授的进度.
因为大多数操作系统原理教材都是以Unix/Linux系统为主要蓝本讲授,因此本实验教程采用Linux系统作为实验环境.
本实验教程安排了两个部分的实验内容:操作系统命令实验操作系统算法实验.
其中操作系统命令实验的目的,一方面是为了让学生体验和熟悉一下经典操作系统最基本的操作界面,另一方面是为了能使不太熟悉Linux系统编程环境的学生尽快的了解Linux系统最基本的程序开发环境,以便迅速展开操作系统原理的算法实验.
因此这一部分的内容并不是对于Linux系统应用的全面介绍,仅是围绕操作系统的算法实验可能用到的系统操作安排的实验.
操作系统算法实验是本实验教程重点安排的实验.
这一部分共安排了十个在操作系统原理中最为重要的问题作为实验课题,这十个课题基本覆盖了大多数操作系统原理教材所讲授的内容,同时也揭示Linux内核最精彩的一些系统功能的用法.
每个实验课题都给出了5个方面的指导:实验目的说明本实验所要达到的实验结果,以便学生能够明确做过这个实验后应有哪些收获.
实验说明说明本实验用到的算法的基本原理和实现中用到的详细的Linux技术文档,以便学生能够看懂示例程序和利用这些技术文档独立的编程调试.
示例实验给出一个能实现所要实验的算法的完整的程序源代码,包括程序前言第II页的开发、调试、分析的详细步骤和过程.
以便使学生能通过开发调试这个程序去亲身体验算法具体的实现过程和实现效果.
独立实验给出一个与该算法有关但不同于示例实验的并且可以借助于示例实验实现的问题.
以便训练学生独立编程调试的能力.
实验要求给出对该实验的过程和结果应做哪些分析和总结的指导.
给出的示例实验程序的源代码在关键语句处均添加了详细的中文注释,以方便学生阅读和理解编程的思路.
其中除有少数实验,例如管程,涉及到要封装对象,以及几个用对象更容易说明问题的实验采用了C++语言编写了程序,其余示例程序均为C语言编写.
这样的选择主要是考虑到目前操作系统原理教材中算法的讨论大部分采用C语言来描述,同时C语言也是Linux系统的宿主语言,可以直接的与操作系统内核接口;另外也不疏忽使用面向对象的程序设计方法来研究操作系统.
这些示例程序均在RedHat、Fedora、Ubuntu、SUSE等系列的Linux系统中进行了测试.
示例程序的内容和开发不涉及特权用户权限和图形界面环境.
以普通用户身份,文本界面进入系统就可以进行实验.
因此在单机上没有Linux系统环境时,可以采用远程终端方式开展实验.
这些示例程序的篇幅都不是很大,可以让学生在2—3个学时内调试和分析出来,从而对操作系统中抽象概念有个快速的感性认识.
独立实验的课题都是和示例实验相类似的一些问题,主要为了训练学生独立思考、编程和调试、分析程序的技能.
只要学生在真正领会了示例程序的基础上,参考示例程序再通过自己的认真思考后,完成这些实验也是不难的.
当然,这些独立实验可以根据教学要求的深度、学生的编程能力、以及实验时间的多少来选作.
由于时间和水平的限制,这本实验教材肯定存在很多缺陷和不足,其中的实验课题的安排和设计可能不是最优.
但希望它能对我们的操作系统原理课程的教学有所裨益,对于学生实践操作系统原理,熟悉Unix/Linux系统核心编程环境有所帮助.
作者2008年12月目录第I页目录前言.
I第一部分、操作系统命令实验.
1实验一、系统的注册与注销.
11.
1登录系统.
11.
2获取帮助.
21.
3虚拟终端.
21.
4退出系统.
21.
5关机.
2实验二、文件系统主要命令.
32.
1文件目录基本结构.
32.
2常用目录操作命令.
32.
3常用文件操作命令.
42.
4文件属性.
6实验三、进程管理主要命令.
73.
1进程状态查询.
73.
2进程控制.
8实验四、SHELL命令控制符84.
1通配符.
94.
2输入输出重定向.
94.
3命令控制符.
94.
4Shell变量10实验五、常用软件开发工具.
115.
1gcc和g++语言编译器.
115.
2make项目管理器.
125.
3gdb程序调试器.
13实验要求.
15第二部分操作系统算法实验.
16实验一、进程控制实验.
161.
1实验目的.
161.
2实验说明.
161.
3示例实验.
181.
4独立实验.
221.
5.
实验要求.
22实验二、线程和进/线程管道通信实验.
232.
1实验目的.
232.
2实验说明.
232.
3示例实验.
252.
4独立实验.
302.
5实验要求.
30实验三、进程调度算法实验.
31前言第II页3.
1实验目的.
313.
2实验说明.
313.
3示例实验.
323.
4独立实验.
343.
5实验要求.
34实验四、进程同步实验364.
1实验目的.
364.
2实验说明.
364.
3示例实验.
404.
4独立实验.
504.
5实验要求.
50实验五、进程互斥实验515.
1实验目的.
515.
2实验说明.
515.
3示例实验.
515.
4独立实验.
595.
5实验要求.
59实验六、死锁问题实验606.
1实验目的.
606.
2实验说明.
606.
3示例实验.
606.
4独立实验.
716.
5实验要求.
71实验七、内存页面置换算法实验727.
1实验目的.
727.
2实验说明.
727.
3示例实验.
727.
4独立实验.
797.
5实验要求.
79实验八、磁盘移臂调度算法实验808.
1实验目的.
808.
2实验说明.
808.
3示例实验.
808.
4独立实验.
858.
5实验要求.
85实验九、文件系统接口实验869.
1实验目的.
869.
2实验说明.
869.
3示例实验.
889.
4独立实验.
919.
5实验要求.
91实验十、分布式系统实验9210.
1实验目的.
9210.
2实验说明.
9210.
3示例实验.
95目录第III页10.
4独立实验.
10110.
5实验要求.
101附录A操作系统原理实验建议102附录B操作系统原理实验报告104参考文献:106第一部分、操作系统命令实验第1页第一部分、操作系统命令实验本部分实验指导仅对常用的shell命令的使用以及Liunx操作系统环境作一些简明介绍,以便使学生能够快速适应Linux操作系统的工作环境,迅速展开在Linux操作系统环境下的操作系统算法实验.
若要全面地去掌握shell的详细操作可将本节介绍的内容作为shell命令练习的提纲,再结合shell的专用教程和Linux系统联机帮助进一步的去学习.
其中介绍的shell语言为Bash-shell语言.
实验一、系统的注册与注销实验目的练习进入和退出系统的操作;学习linux联机帮助命令的使用,学会怎样利用借助联机帮助命令随时查阅系统说明文档.
1.
1登录系统linux是一个多用户多任务的操作系统.
多个用户可以同时使用一台机器,每个人又可以同时启动多个程序.
为了区分不同的用户,他们多有自己独立的用户帐号.
例如系统启动后显示:login:password:第一次登录是时,必须用系统安装时确定的root用户及口令以系统管理员身份登录,然后由系统管理员建立其他用户的账号和口令,这样其他用户才可以以他们各自的账号和口令登录系统.
登录成功后如果你使用的是文本界面系统在显示器上会显示linux命令解释程序shell准备好提示符.
如果您使用的是图形界面系统会显示linux系统桌面,需要您打开linux控制台终端窗体才能使用shell命令.
shell命令提示符的符号会根据用户的身份和使用的shell命令的种类而不同.
一般系统管理员为#,使用Bash-shell的用户为$.
此时系统输入光标已经定位在shell提示符右边您可以在该命令行上输入操作命令了.
Shell命令取分大小写字符.
命令的语法一般为:$命令动词[–选项符1–选项符2…][命令参数1命令参数2…]其中各项使用空格键分开,回车键结束.
例如,列出文件hello的详细属性信息的命令可以输入命令:$ls–lhello这里ls是命令动词(它是列表list的缩写);-l是选项符,表示要列出详细信息;hello是ls的命令参数,它是一个您想要了解的文件名.
这将列出hello文件详细的属性信息.
实验一、系统的注册与注销第2页1.
2获取帮助linux带有联机手册,可以用man命令查阅各系统命令及系统调用的语法.
例如,要查询ls命令的使用语法可以输入命令:$manlsman命令将显示ls命令的详细使用方法.
$man–asleepman命令将显示所有与sleep相关的系统文档.
1.
3虚拟终端微机上的linux系统只有一个终端,但linux提供多个虚拟终端,用户可以通过Alt+Ctrl+F1切换到默认的第一个终端,Alt+Ctrl+F2切换到第二个终端,Alt+F3切换到第三个终端等等.
1.
4退出系统要注销当前账号,或换一账号重新登录系统有多种方法,可以使用exit或logout,也可以同时键入Ctrl+D.
例如:$exitlogin:系统将注销您的当前账号,再次等待您登录系统.
1.
5关机关闭系统或重新启动系统,可以使用命令halt、reboot或shutdown命令,也可以同时使如Ctrl+Alt+Del键.
第一部分、操作系统命令实验第3页实验二、文件系统主要命令实验目的熟悉linux文件系统的基本结构和主要的使用方法.
2.
1文件目录基本结构linux文件系统是多级树形结构.
典型linux文件系统大致的结构如下图:Linux系统的根目录表示符号为―/‖.
多级目录的各级目录也用―/‖分割,组成一个完整的路径名.
例如,当用户首次登录系统时当前工作目录为该用户的主目录,一般为/home/用户账号名目录.
当前目录表示符号为―.
‖,上级目录表示符号为―.
.
‖.
2.
2常用目录操作命令显示当前工作目录全名pwd改变当前工作目录cd[路径名]/proc/var/lib/root/tmp/usr/home/lib/dev/bin/local/jon/bin/bin//larty/etc实验二、文件系统主要命令第4页建立新目录mkdir路径名删除一个空目录rmdir路径名列出目录或文件属性信息ls[选项][路径名|文件名]2.
3常用文件操作命令串联显示文本文件内容cat[文件名1文件名2…]例如,demo1文件中仅有一行文字―Ok‖,demo2文件中仅有一行文字―hello‖.
显示demo1和demo2两文件的内容:$catdemo1demo2Okhello可以利用该命令将多个文件合并.
例如,将文件demo1和demo2合并为demo$catdemo1demo2>demo拷贝文件cp[选项]源文件名目标文件名可以借助统配符递归的拷贝整个目录.
例如,将sub子目录中文件全部拷贝到当前目录中:$cp-rv.
/sub/.
.
/―.
/sub/.
/demo1‖demo1‖―.
/sub/.
/demo‖demo‖―.
/sub/.
/demo2‖demo2‖文件换名或文件移动mv[选项]旧文件名新文件名删除文件rm[选项]文件名对文件内容进行排序sort[选项][文件名]例如,按字典升序排序列出文件demo的内容:$sortdemohelloOk在文件中查找给定的字符串或正则表达式grep[选项]要查找的字符串或正则表达式文件名例如,要在当前目录之下递归的查找哪些文件中哪一行上有―hello‖一词,可输入命令:$grep-r-n"hello".
/.
/sub/demo:2:hello.
/sub/demo1:1:hello.
/hello.
c:5:hello第一部分、操作系统命令实验第5页grep查出在子目录sub的demo文件的第2行和当前目录的hello.
c文件的第5行有‖hello‖一词按类型查找文件find[路径名][类型]文件名例如,要查找当前目录下是否有叫―demo‖的文件:$find.
/-namedemo.
/sub/demofind在当前目录的子目录中找到有一个叫―demo‖的文件.
文件归档和恢复tar[选项]归档文件名[源文件名]例如,将当前目录中的子目录sub压缩归档为文件sub.
tar:$tar-cvzfsub.
tarsubsub/sub/demosub/demo1sub/demo2将文件sub.
tar解压恢复到当前目录中:$tar-xvzfsub.
tarsubsub/demosub/demo1sub/demo2创建或编辑文本文件vi[文件名]vi编辑器为全屏幕文本键盘操作编辑器,无鼠标编辑功能.
具有命令态和编辑两种编辑状态.
初始启动时为命令态,此时键入的所有字符都被解释为vi子命令,无字符图形显示,仅有命令效果.
常用vi子命令:i向屏幕缓冲区当前光标前插入文本.
此时vi状态自动切换为编辑状态其后键入的字母数字都可以显示在屏幕上,同时键盘右边编辑小键盘全部激活,可利用它们定位光标位置进行屏幕编辑.
输入Esc键将结束插入编辑状态返回命令状态.
a向屏幕缓冲区当前光标后插入文本.
其他功能与i命令相同.
:进入提示行子命令.
此时光标进入屏幕最下行一冒号后,准备接受文件操作子命令.
常用文件操作子命令:w[文件名]如果vi启动时指定文件名,则可以不要[文件名],编辑的文本将写入启动时指定文件名,否则写入w命令指定的文件.
r文件名将r子命令指定的文件内容读入屏幕编辑缓冲区.
q或q!
退出vi编辑命令.
如果带有惊叹号则不保存当前编辑内容强行退出.
实验二、文件系统主要命令第6页2.
4文件属性文件的属性是对文件进行管理的一组控制信息.
常见文件属性有:文件类型linux中文件被划分为3种基本类型:普通文件(代表符号-)、目录文件(代表符号d)、设备文件(块设备代表符号b,字符设备代表符号c)文件权限linux中每个文件都具有3种用户权限和3种操作权限.
用户权限有:文件主权限(owner)、同组用户权限(group)、其他用户权限(other)操作权限有:读文件权限r、写文件权限w、执行文件权限x文件的链接数linux中文件可以通过使用ln命令给文件起多个别名,来共享一个文件.
共享次数称为链接计数,对该文件的删除仅当链接计数为0时才进行,否则仅对该链接计数值减1.
所属文件主账号所属用户组号文件长度文件创建或修改的日期和时间文件名可以从ls–l命令的输出中看到这些文件属性的信息.
例如:$ls-ldrwxr-xr-x2rootroot1024Aug1802:50bin-rw-r—r--1rootroor849Aug1803:00junk以上列出的第一行文件信息表示该文件为目录文件,文件主权限为可读可写可执行,同组用户权限为可读可执行,其他用户权限为可读可执行,文件链接计数为2,文件主是root,,文件属于root用户组,文件长度为1024字节,文件建立日期为8月18日2点50分,文件名为bin第二行文件信息表示该文件为普通文件,文件主权限为可读可写不可执行,同组用户权限为只读不可写不可执行,其他用户权限为只读不可写不可执行,文件链接计数为1,文件主是root,,文件属于root用户组,文件长度为849字节,建立日期为8月18日3点00分,文件名为junk.
文件的操作权限属性可以用chmod来修改.
chmod的第一种语法为:chomd{a,u,g,o}r,w,x}文件名这里,a,u,g,o分别代表所有用户,文件主,文件同组者,其他用户.
+,-,=分别代表添加、删除、赋予权限.
r,w,x分别代表可读、可写、可执行.
chmod的第一种语法为:chomdnnn文件名这里nnn代表3个八进制数字,每个分别对应文件主,文件同组者,其他用户.
每个八进制数的3位二进制数又分别代表读,写,执行权限.
1表示添加该权限,0表示删除该权限.
例如:第一部分、操作系统命令实验第7页使所有用户都具有对文件junk的读权限$chmoda+rjunk使文件主对文件junk具有执行权限,其他用用户只有读权限$chmod744junk实验三、进程管理主要命令实验目的观察和了解linux系统中进程的运行情况,熟悉linux系统中主要进程管理和进程控制命令的用法.
3.
1进程状态查询linux是一个多任务多用户系统,即这种系统可以使一个中央处理器以极短的时间轮流执行多个任务的进程,宏观上产生多个任务在并发执行的效果.
这些并发的进程又可分为前台进程和后台两类进程.
通常前台进程是一些需要人机交互的进程,如文本编辑、图像编辑等;而后台进程是一些无需人机交互的进程,如数值计算、文件打印、系统服务等等.
shell提供了一些控制并发进程的命令.
可以使用这些命令监控和管理任务的执行和联机用户的工作情况.
常用的进程控制命令:显示当前各个进程的工作信息命令ps例如要显示系统当前所有进程的详细信息,可输入命令:$psauxUSERPID%CPU%MEMVSZRSSTTYSTATSTARTTIMECOMMANDroot10.
00.
11984652S08:440:01init[5]root20.
00.
000SN08:440:00[ksoftirqd/0]root30.
00.
000S08:440:00[watchdog/0]root40.
00.
000S‖,表示输出将创建并发向其右边说明的目标,例如:$ls>filelist将创建文件filelist,并将列出的当前目录的文件名存入其中.
输出重定向符―>>‖,表示将追加输出到其右边说明的目标,例如:$ls>>filelist将列出的当前目录文件名追加存入filelist中.
管道操作符―|‖,表示连接一个命令的输出到另一个命令的输入,例如:$ls|sort|lp将列出的当前目录的文件名排序后送入打印队列.
I/O重定向和管道可以组合使用.
例如:$ls>filelist|sortls列出的当前目录的文件名存入文件filelist后,再通过sort命令排序显示.
4.
3命令控制符shell命令可以一次发出多个,也可以控制命令启动后在后台执行.
命令连接符―;‖可以在一行上连发多个命令.
例如:$ls;ls–l;ls–l–a先以短格式列出文件名,再以长格式列出文件名,再以长格式列出所有文件名.
命令后台启动符―&‖,表示启动的命令在后台执行.
例如:$grep―argv‖*.
c&;vi首先在后台启动查询命令在当前目录的所有C文件中查找字符串―argv‖,实验四、shell命令控制符第10页然后在前台启动vi文本编辑命令编写文件.
4.
4Shell变量shell不仅是命令解释器,而且也是一种功能强大的命令程序设计语言,可以用它来编写控制多任务处理的程序,定制系统工作环境.
因此,shell允许定义和使用变量.
Shell内部提供的一些全局性的变量定制了的系统工作环境,称为环境变量.
用户可以通过改变这些环境变量定制自己的工作环境,以及与系统内核通讯.
在此我们不对shell编程进行练习,主要熟悉和练习shell提供的一些常用的环境变量.
引用Shell变量的值时需要前导一个―$‖符号,系统环境变量一般为大写字母.
经常使用命令echo显示变量的值.
例如,显示命令搜索路径:echo$PATHShell变量赋值时不用前导―$‖,赋值号为―=‖.
Shell变量值一律为字符串,所以shell变量无需说明数据类型.
例如,重新设定命令搜索路径:PATH=―/usr/local/bin:/home/mydir:‖可以使用set命令察看当前设定的shell变量例如,常用的环境变量:用户主目录:HOME用户名:USER计算机名:HOSTNAME计算机类型:HOSTTYPE终端类型:TERM命令搜索路径:PATH当前使用的shell的路径:SHELLshell一级提示符:PS第一部分、操作系统命令实验第11页实验五、常用软件开发工具实验目的Linux环境中有众多的软件开发工具.
其中的C语言编译器gcc可以编译和构造Linux操作系统内核,是与操作系统关系最直接的语言开发工具,也是我们进行操作系统算法实验首选的开发工具.
因此本节实验的主要目的就是要熟悉gcc编译器及其相关的管理和调试工具.
5.
1gcc和g++语言编译器gcc能够支持多种C语言的变体,例如K&RC和ANSIC;GCC也是一个交叉平台编译器,能够开发不同CPU体系结构的软件;同时,GCC也能够进行代码优化,提高执行程序的运行速度.
g++是构建于gcc基础上的C++语言编译器.
gcc编译过程分为4个阶段:预处理编译汇编连接最简单的C语言编译的例子:用vi建立一个hello.
c文件$vihello.
c输入字符i,插入文本以下文本/**hello.
c*/#includeintmain(void){printf(―HelloWorld!
\n‖);return0;}最后输入字符:wq,返回命令行,键入以下编译命令:$gcchello.
c如果没有错误gcc将生成默认的可执行文件a.
out,执行a.
out:$.
/a.
outHelloWorld!
$gcc带有多达数页的编译选项,我们仅列出最常用的几项:-o可执行文件名指定输出的可执行文件名,而不是默认的a.
out-c只编译生成.
o的目标文件,不连接生成可执行文件-s只编译生成.
s的汇编文件,不连接生成可执行文件-g在可执行文件中加入标准调试信息-Wall允许GCC发出警告型错误信息实验五、常用软件开发工具第12页选项使用的例子:对以上hello.
c使用-o,-g常用选项重新编译、执行:$gcc-ghello.
c-ohello$.
/helloHelloWorld!
$GCC默认的扩展文件名:.
cC语言源代码.
C.
ccC++语言源代码.
i预处理后的C语言源代码.
ii预处理后的C++语言源代码.
S.
s汇编语言源代码.
o编译后的目标代码.
a.
so编译后的库代码5.
2make项目管理器make项目管理器(GNU中的名称为gmake)可以根据项目开发者说明的项目开发文件Makefile自动的进行编译配置和重复编译,能实现复杂项目的编译自动化.
项目开发文件Makefile的编写使用以下规则:目标体1:依赖体1[依赖体2[.
.
.
]]命令行1命令行2[.
.
.
]目标体2:依赖体1[依赖体2[.
.
.
]]命令行1命令行2[.
.
.
][.
.
.
]其中目标体是命令行要生成的输出文件,依赖体是命令行要输入的文件或选项,命令行序列是要创建目标体文件所需要的步骤,例如编译命令.
无特别指定,make总是使用当前目录中的Makefile进行自动编译.
例如我们在当前目录中有两个项目开发文件hello.
c和hello.
h,则Makefile文件可以编写为:hello:hello.
ogcchello.
o-ohellohello.
o:hello.
chello.
hgcc-shello.
cgcc-chello.
cclean:rmhello*.
o*.
smake命令的使用:$gmake输入make或makehello将生成Makefile中所有的目标文件,即hello,hello.
o,hello.
s.
$gmakehello.
o第一部分、操作系统命令实验第13页将仅生成目标文件hello.
o和hello.
s$gmakeclean是一条伪目标生成命令,该目标没有依赖体,它只执行对已生成目标文件的删除.
当我们对以上依赖体中的任意一个进行了修改,重新make时仅会引发对应目标体的重新生成,从而提高了编译的效率并保证了项目开发的正确性.
5.
3gdb程序调试器如果您在gcc编译选项中用到了-g调试选项,则编译出的可执行文件就会带有符号表.
这样的程序就可以使用gdb跟踪调试,观察到它的高级语言源代码的执行过程和变量的中间结果,从而能快速的排除程序运行时发生的错误.
以下是一个带有运行时错误的C程序,注意程序想通过传地址方式在一个函数中为字符变量C赋一个字符,但它引用了一个空指针,这将引发运行时的段非法错误使得程序异常终止.
但我们可以通过gdb跟踪到它产生错误的位置,从而分析出产生错误的原因.
/**debugmy.
c*/#includevoidmyputc(char*cptr){*cptr='a';printf("myputc=%c\n",*cptr);}intmain(void){charc;char*cptr;c='A';myputc(cptr);return0;}使用带-g选项的gcc编译、执行:$gcc-gdebugmy.
c-odebugmy$.
/debugmy段错误$使用gdb跟踪查错$gdb.
/debugmyGNUgdbRedHatLinux(6.
3.
0.
0-1.
122rh)Copyright2004FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouarewelcometochangeitand/ordistributecopiesofitundercertainconditions.
Type"showcopying"toseetheconditions.
ThereisabsolutelynowarrantyforGDB.
Type"showwarranty"fordetails.
ThisGDBwasconfiguredas"i386-redhat-linux-gnu".
.
.
Usinghostlibthread_dblibrary"/lib/libthread_db.
so.
1".
实验五、常用软件开发工具第14页(gdb)现在进入了gdb调试状态,可以使用gdb的调试子命令跟踪程序的执行.
Gdb常用命令:list[行号]列出指定行号的上下行(缺省为10行)break[源程序文件名:]行号建立一个断点run启动被调试的程序next从断点处向下执行一行step从断点处向下执行一行,当前行为函数则跟踪进入函数continue继续从断点处连续执行print变量名打印变量当前值quit退出gdb让我们现使用list命令查看一下要调试的程序是否已经装入,输入:(gdb)list105voidmyputc(char*cptr)6{7*cptr='a';8printf("myputc=%c\n",*cptr);9}10intmain(void)11{12charc;13char*cptr;14c='A';我们将断点设在第10行上,输入:(gdb)break15Breakpoint1at0x80483c0:filedebugmy.
c,line15.
开始跟踪执行,输入:(gdb)runStartingprogram:/root/ipc/debugmyReadingsymbolsfromsharedobjectreadfromtargetmemory.
.
.
done.
LoadedsystemsuppliedDSOat0xffffe000Breakpoint1,main()atdebugmy.
c:1515myputc(cptr);程序执行到第15行上停止,我们采用单步执行跟踪错误的发生,输入:(gdb)stepmyputc(cptr=0x9bbe40"U\211WVS\203L\215s")atdebugmy.
c:77*cptr='a';程序执行一行,进入函数myputc,再单步执行一行,再次输入:(gdb)stepProgramreceivedsignalSIGSEGV,Segmentationfault.
0x0804838dinmyputc(cptr=0x9bbe40"U\211WVS\203L\215s")atdebugmy.
c:77*cptr='a';此时gdb报告在执行改行时接受到一个段失败的信号,由此我们可以知道错误发生在该行上,进一步我们可以推断出该错误的发生是因为指针cptr未初始化,它指向第一部分、操作系统命令实验第15页了一个非法的地址,所以在向它指向的单元赋值时发生了段错误.
实验要求记录每步实验中出现的结果,分析输出结果,如果是错误的排除错误,如果是正确的说明您使用的命令完成那些操作系统功能.
您将怎样利用这些功能.
将您在实验中操作的命令信息、命令输出信息进行总结分析,比较shell命令工作方式和可视化系统图形命令操作的优劣.
将分析结果写成实验报告实验一、进程控制实验第16页第二部分操作系统算法实验本部分的实验内容主要是为了加深理解和掌握操作系统原理中的经典算法而安排的.
其中不涉及到要封装对象的实验全部用C语言编写.
涉及到要封装对象的实验使用C++语言编写.
实验环境均为Linux操作系统,开发工具为gcc和g++.
实验一、进程控制实验1.
1实验目的加深对于进程并发执行概念的理解.
实践并发进程的创建和控制方法.
观察和体验进程的动态特性.
进一步理解进程生命期期间创建、变换、撤销状态变换的过程.
掌握进程控制的方法,了解父子进程间的控制和协作关系.
练习Linux系统中进程创建与控制有关的系统调用的编程和调试技术.
1.
2实验说明1)与进程创建、执行有关的系统调用说明进程可以通过系统调用fork()创建子进程并和其子进程并发执行.
子进程初始的执行映像是父进程的一个复本.
子进程可以通过exec()系统调用族装入一个新的执行程序.
父进程可以使用wait()或waitpid()系统调用等待子进程的结束并负责收集和清理子进程的退出状态.
fork()系统调用语法:#includepid_tfork(void);fork成功创建子进程后将返回子进程的进程号,不成功会返回-1.
exec系统调用有一组6个函数,其中示例实验中引用了execve系统调用语法:#includeintexecve(constchar*path,constchar*argv[],constchar*envp[]);path要装入的新的执行文件的绝对路径名字符串.
argv[]要传递给新执行程序的完整的命令参数列表(可以为空).
envp[]要传递给新执行程序的完整的环境变量参数列表(可以为空).
Exec执行成功后将用一个新的程序代替原进程,但进程号不变,它绝不会再返回到调用进程了.
如果exec调用失败,它会返回-1.
wait()系统调用语法:#include#includepid_twait(int*status);pid_twaitpid(pid_tpid,int*status,intoption);status用于保留子进程的退出状态第二部分操作系统算法实验第17页pid可以为以下可能值:-1等待所有PGID等于PID的绝对值的子进程1等待所有子进程0等待所有PGID等于调用进程的子进程>0等待PID等于pid的子进程option规定了调用waitpid进程的行为:WNOHANG没有子进程时立即返回WUNTRACED没有报告状态的进程时返回wait和waitpid执行成功将返回终止的子进程的进程号,不成功返回-1.
getpid()系统调用语法:#include#includepid_tgetpid(void);pid_tgetppid(void);getpid返回当前进程的进程号,getppid返回当前进程父进程的进程号2)与进程控制有关的系统调用说明可以通过信号向一个进程发送消息以控制进程的行为.
信号是由中断或异常事件引发的,如:键盘中断、定时器中断、非法内存引用等.
信号的名字都以SIG开头,例如SIGTERM、SIGHUP.
可以使用kill-l命令查看系统当前的信号集合.
信号可在任何时间发生,接收信号的进程可以对接收到的信号采取3种处理措施之一:忽略这个信号执行系统默认的处理捕捉这个信号做自定义的处理信号从产生到被处理所经过的过程:产生(generate)->挂起(pending)->派送(deliver)->部署(disposition)或忽略(igore)一个信号集合是一个C语言的sigset_t数据类型的对象,sigset_t数据类型定义在中.
被一个进程忽略的所有信号的集合称为一个信号掩码(mask).
从程序中向一个进程发送信号有两种方法:调用shell的kill命令,调用kill系统调用函数.
kill能够发送除杀死一个进程(SIGKILL、SIGTERM、SIGQUIT)之外的其他信号,例如键盘中断(Ctrl+C)信号SIGINT,进程暂停(Ctrl+Z)信号SIGTSTP等等.
调用Pause函数会令调用进程的执行挂起直到一个任意信号到来后再继续运行.
调用sleep函数会令调用进程的执行挂起睡眠指定的秒数或一个它可以响应的信号到来后继续执行.
每个进程都能使用signal函数定义自己的信号处理函数,捕捉并自行处理接收的除SIGSTOP和SIGKILL之外的信号.
以下是有关的系统调用的语法说明.
kill系统调用语法:实验一、进程控制实验第18页#include#includeintkill(pid_tpid,intsig);pid接收信号的进程号signal要发送的信号kill发送成功返回接收者的进程号,失败返回-1.
pause系统调用语法:#includeintpause(void);pause挂起调用它的进程直到有任何信号到达.
调用进程不自定义处理方法,则进行信号的默认处理.
只有进程自定义了信号处理方法捕获并处理了一个信号后,pause才会返回调进程.
pause总是返回-1,并设置系统变量errno为EINTR.
sleep系统调用语法:#includeunsignedintsleep(unsignedintseconds);seconds指定进程睡眠的秒数如果指定的秒数到,sleep返回0.
signal系统调用语法为:#includetypedefvoid(*sighandler_t)(int);sighandler_tsignal(intsignum,sighandler_thandler);signum要捕捉的信号handler进程中自定义的信号处理函数名signal调用成功会返回信号处理函数的返回值,不成功返回-1,并设置系统变量errno为SIG_ERR.
1.
3示例实验以下实验示例程序应实现一个类似子shell子命令的功能,它可以从执行程序中启动另一个新的子进程并执行一个新的命令和其并发执行.
1)打开一终端命令行窗体,新建一个文件夹,在该文件夹中建立以下名为pctl.
c的C语言程序:/**Filename:pctl.
c*copyright:(C)2006byzhanghonglie*Function:父子进程的并发执行*/#include"pctl.
h"intmain(intargc,char*argv[]){inti;intpid;//存放子进程号intstatus;//存放子进程返回状态第二部分操作系统算法实验第19页char*args[]={"/bin/ls","-a",NULL};//子进程要缺省执行的命令signal(SIGINT,(sighandler_t)sigcat);//注册一个本进程处理键盘中断的函数pid=fork();//建立子进程if(pid=0)实验一、进程控制实验第20页printf("%dWakeup%dchild.
\n",getpid(),pid);printf("%ddon'tWaitforchilddone.
\n\n",getpid());}}returnEXIT_SUCCESS;}2)再建立以下名为pctl.
h的C语言头文件:#include#include#include#include#include#include//进程自定义的键盘中断信号处理函数typedefvoid(*sighandler_t)(int);voidsigcat(){printf("%dProcesscontinue\n",getpid());}3)建立以下项目管理文件Makefilehead=pctl.
hsrcs=pctl.
cobjs=pctl.
oopts=-g-call:pctlpctl:$(objs)gcc$(objs)-opctlpctl.
o:$(srcs)$(head)gcc$(opts)$(srcs)clean:rmpctl*.
o4)输入make命令编译连接生成可执行的pctl程序$gmakegcc-g-cpctl.
cgccpctl.
o-opctl5)执行pctl程序(注意进程号是动态产生的,每次执行都不相同)$.
/pctlIamChildprocess4113Myfatheris4112IamParentprocess4112Wakeup4113child.
4112don'tWaitforchilddone.
第二部分操作系统算法实验第21页4113Processcontinue4113childwillRunning:/bin/ls-a.
.
.
Makefilepctlpctl.
cpctl.
hpctl.
o$以上程序的输出说明父进程4112创建了一个子进程4113,子进程执行被暂停.
父进程向子进程发出键盘中断信号唤醒子进程并与子进程并发执行.
父进程并没有等待子进程的结束继续执行先行结束了(此时的子进程成为了孤儿进程,不会有父进程为它清理退出状态了).
而子进程继续执行,它变成了列出当前目录所有文件名的命令ls-a.
在完成了列出文件名命令之后,子进程的执行也结束了.
此时子进程的退出状态将有初始化进程为它清理.
6)再次执行带有子进程指定执行命令的pctl程序:$.
/pctl/bin/ls-lIamChildprocess4223Myfatheris4222IamParentprocess42224222Waitingforchilddone.
可以看到这一次子进程仍然被挂起,而父进程则在等待子进程的完成.
为了检测父子进程是否都在并发执行,请输入ctrl+z将当前进程放入后台并输入ps命令查看当前系统进程信息,显示如下:[1]+Stopped.
/pctl/bin/ls-l$ps-lFSUIDPIDPPIDCPRINIADDRSZWCHANTTYTIMECMD0S0408540830760-1413waitpts/100:00:00bash0T0422240850760-360finishpts/100:00:00pctl1T0422342220760-360finishpts/100:00:00pctl0R0423140850780-1302-pts/100:00:00ps可以看到当前系统中同时有两个叫pctl的进程,它们的进程号分别是4222和4223.
它们的状态都为―T‖,说明当前都被挂起.
4223的父进程是4222,而4222的父进程是4085,也就是bash-shell.
为了让pctl父子进程继续执行,请输入fg命令让pctl再次返回前台,显示如下:$fg.
/pctl/bin/ls-l现在pctl父子进程从新返回前台.
我们可以通过键盘发键盘中断信号来唤醒pctl父子进程继续执行,输入ctrl+c,将会显示:4222Processcontinue4223Processcontinue4223childwillRunning:/bin/ls-ltotal1708-rw-r--r--1rootroot176May811:11Makefile-rwxr-xr-x1rootroot8095May814:08pctl-rw-r--r--1rootroot2171May814:08pctl.
c-rw-r--r--1rootroot269May811:10pctl.
h-rw-r--r--1rootroot4156May814:08pctl.
o第22页Mychildexit!
status=0以上输出说明了子进程在捕捉到键盘中断信号后继续执行了指定的命令,按我们要求的长格式列出了当前目录中的文件名,父进程在接收到子进程执行结束的信号后将清理子进程的退出状态并继续执行,它报告了子进程的退出编码(0表示子进程正常结束)最后父进程也结束执行.
1.
4独立实验参考以上示例程序中建立并发进程的方法,编写一个多进程并发执行程序.
父进程首先创建一个执行ls命令的子进程然后再创建一个执行ps命令的子进程,并控制ps命令总在ls命令之前执行.
1.
5.
实验要求根据实验中观察和记录的信息结合示例实验和独立实验程序,说明它们反映出操作系统教材中进程及处理机管理一节讲解的进程的哪些特征和功能在真实的操作系统中它是怎样实现和反映出教材中讲解的进程的生命期、进程的实体和进程状态控制的.
你对于进程概念和并发概念有哪些新的理解和认识子进程是如何创建和执行新程序的信号的机理是什么怎样利用信号实现进程控制根据实验程序、调试过程和结果分析写出实验报告.
第二部分操作系统算法实验第23页实验二、线程和进/线程管道通信实验2.
1实验目的通过Linux系统中线程和管道通信机制的实验,加深对于线程控制和管道通信概念的理解,观察和体验并发进/线程间的通信和协作的效果,练习利用无名管道进行进/线程间通信的编程和调试技术.
2.
2实验说明1)与线程创建、执行有关的系统调用说明线程是在共享内存中并发执行的多道执行路径,它们共享一个进程的资源,如进程程序段、文件描述符和信号等,但有各自的执行路径和堆栈.
线程的创建无需像进程那样重新申请系统资源,线程在上下文切换时也无需像进程那样更换内存映像.
多线程的并发执行即避免了多进程并发的上下文切换的开销又可以提高并发处理的效率.
Linux利用了特有的内核函数__clone实现了一个叫phread的线程库,__clone是fork函数的替代函数,通过更多的控制父子进程共享哪些资源而实现了线程.
Pthread是一个标准化模型,用它可把一个程序分成一组能够并发执行的多个任务.
phread线程库是POSIX线程标准的实现,它提供了C函数的线程调用接口和数据结构.
线程可能的应用场合包括:在返回前阻塞的I/O任务能够使用一个线程处理I/O,同时继续执行其他处理.
需要及时响应多个前台用户界面操作同时后台处理的多任务场合.
在一个或多个任务受不确定事件影响时能够处理异步事件同时继续进行正常处理.
如果某些程序功能比其他功能更重要,可以使用线程以保证所有功能都出现,但那些时间密集型的功能具有更高优先级.
下面介绍pthread库中最基本的调用.
pthread_create系统调用语法:#includeIntpthread_create(pthread_t*thread,pthread_attr_t*attr,void*(*start_routine)(void*)Void*arg);pthread_create函数创建一个新的线程.
pthread_create在thread中保存新线程的标识符.
Attr决定了线程应用那种线程属性.
使用默认可给定参数NULL;(*start_routine)是一个指向新线程中要执行的函数的指针arg是新线程函数携带的参数.
Pthread_create执行成功会返回0并在thread中保存线程标识符.
执行失败则返回一个非0的出错代码.
实验二、线程和进/线程管道通信实验第24页pthread_exit系统调用语法:#includevoidpthread_exit(void*retval);pthread_exit函数使用函数pthread_cleanup_push调用任何用于该线程的清除处理函数,然后中止当前进程的执行,返回retval.
Retval可以由父线程或其他线程通过pthread_join来检索.
一个线程也可以简单地通过从其初始化函数返回来终止.
pthread_join系统调用语法:#includeintpthread_join(pthread_tth,void**thread_return);intpthread_detach(pthread_tth);函数pthread_join用于挂起当前线程,直到th指定的线程终止运行为止.
另一个线程的返回值如果不为NULL,则保存在thread_return指向的地址中.
一个线程所使用的内存资源在对该线程应用pthread_join调用之前不会被重新分配.
因而对于每个可切入的线程(默认的)必须调用一次pthread_join函数.
线程必须是可切入的而不是被分离的状态,并且其他线程不能对同一线程再应用pthread_join调用.
通过在pthread_create调用创建一个线程时使用PTHREAD_CREATE_DETACHED属性或者使用pthread_detach可以让线程处于被分离状态.
注意不像由fork创建的进程可以使用众多wait等待子进程退出,在pthread多线程中似乎没有等待某个线程退出的方法.
2)管道通信机制管道pipe是进程间通信最基本的一种机制.
在内存中建立的管道称为无名管道,在磁盘上建立的管道称为有名管道.
无名管道随着进程的撤消而消失,有名管道则可以长久保存,shell命令符|建立的就是无名管道,而shell命令mkfifo建立的是有名管道.
两个进程可以通过管道一个在管道一端向管道发送其输出,给另一进程可以在管道的另一端从管道得到其输入.
管道以半双工方式工作,即它的数据流是单方向的.
因此使用一个管道一般的规则是读管道数据的进程关闭管道写入端,而写管道进程关闭其读出端.
管道既可以采用同步方式工作也可以采用异步方式工作.
pipe系统调用的语法为:#includeintpipe(intpipe_id[2]);pipe建立一个无名管道,pipe_id[0]中和pipe_id[1]将放入管道两端的描述符如果pipe执行成功返回0.
.
出错返回-1.
管道读写的系统调用语法为:#includessize_tread(intpipe_id,constvoid*buf,size_tcount);ssize_twrite(intpipe_id,constvoid*buf,size_tcount);read和write分别在管道的两端进行读和写.
pipe_id是pipe系统调用返回的管道描述符.
Buf是数据缓冲区首地址,第二部分操作系统算法实验第25页count说明数据缓冲区以size_t为单位的长度.
read和write的返回值为它们实际读写的数据单位.
注意管道的读写默认的通信方式为同步读写方式,即如果管道读端无数据则读者阻塞直到数据到达,反之如果管道写端有数据则写者阻塞直到数据被读走.
2.
3示例实验1)以下示例实验程序实现并发的两个线程合作将整数X的值从1加到10的功能.
它们通过管道相互将计算结果发给对方.
(1)在新建文件夹中建立以下名为tpipe.
c的C语言程序/**description:tpipe.
c*copyright:(C)byzhanghonglie*Function:利用管道实现在在线程间传递整数*/#include#include#include#includevoidtask1(int*);//线程1执行函数原型voidtask2(int*);//线程2执行函数原型intpipe1[2],pipe2[2];//存放两个无名管道标号pthread_tthrd1,thrd2;//存放两个线程标识intmain(intargc,char*arg[]){intret;intnum1,num2;//使用pipe()系统调用建立两个无名管道.
建立不成功程序退出,执行终止if(pipe(pipe1)#include#includeintmain(intargc,char*argv[]){intpid;//进程号intpipe1[2];//存放第一个无名管道标号intpipe2[2];//存放第二个无名管道标号intx;//存放要传递的整数//使用pipe()系统调用建立两个无名管道.
建立不成功程序退出,执行终止if(pipe(pipe1)1)f(x)=1(x=1)f(y)=f(y-1)+f(y-2)(y>2)f(y)=1(y=1,2)请编程建立3个并发协作进程,它们分别完成f(x,y)、f(x)、f(y)2.
5实验要求根据示例实验程序和独立实验程序观察和记录的调试和运行的信息,说明它们反映出操作系统教材中讲解的进程协作和进程通信概念的哪些特征和功能在真实的操作系统中它是怎样实现和反映出教材中进程通信概念的.
你对于进程协作和进程通信的概念和实现有哪些新的理解和认识管道机制的机理是什么怎样利用管道完成进程间的协作和通信根据实验程序、调试过程和结果分析写出实验报告.
第二部分操作系统算法实验第31页实验三、进程调度算法实验3.
1实验目的加深对进程调度概念的理解,体验进程调度机制的功能,了解Linux系统中进程调度策略的使用方法.
练习进程调度算法的编程和调试技术.
3.
2实验说明在linux系统中调度策略(policy)可以是以下3种:SCHED_OTHER默认的分时调度策略(值等于0)SCHED_FIFO先进先先出调度策略(值等于1)SCHED_RR时间片轮转调度策略(值等于2)后两种专用于对响应时间有特殊要求的进程,并且会抢先于SCHED_OTHER调度策略的进程而执行.
一个具有SCHED_FIFO调度策略的进程只能被更高优先级的进程抢先,但具有SCHED_RR调度策略的进程必要时可以与同级进程共享时间片.
进程优先数(prio)由静态优先数和动态优先数两部分组成,值越小调度优先级越高.
具有SCHED_OTHER策略的进程静态优先数总是0.
动态优先数与进程的执行状态有关,但可以使用nice命令或系统调用加大进程优先数使其优先级降低,或用系统调用setpriority分别按进程或进程组或用户号设置介于-20到+20之间的动态优先数.
与进程调度策略有关的系统调用函数原型都声明在以下文件中:#include#include#include设置进程调度策略的系统调用语法为:intsched_setscheduler(pid_tpid,intpolicy,conststructsched_param*sp);pid进程号policy以上说明的3种调度策略之一sp调度参数结构指针,调度参数结构主要存有调度优先数structsched_param{.
.
.
intsched_priority;.
.
.
};返回值:执行成功后返回0获取进程调度策略的系统调用语法为:intsched_getscheduler(pid_tpid);pid进程号返回值:进程当前的调度策略设置进程动态优先数的系统调用语法为:intgetpriority(intwhich,intwho);实验三、进程调度算法实验第32页which设置的对象.
可以是:进程PRIO_PROCESS进程组PRIO_PGRP用户PRIO_USERwho对应设置对象的进程号或组号或用户号返回值:所有匹配进程中的最高优先数设置进程动态优先数的系统调用语法为:intsetpriority(intwhich,intwho,intprio);which设置的对象.
可以是:进程PRIO_PROCESS进程组PRIO_PGRP用户PRIO_USERwho对应设置对象的进程号或组号或用户号prio要设置的进程优先数返回值:所有匹配进程中的最高优先数3.
3示例实验以下示例实验程序要测试在linux系统中不同调度策略和不同优先数的调度效果.
1)在新建文件夹中建立以下名为psched.
c的C语言程序:/**Filename:psched.
c*copyright:(C)2006byzhanghonglie*Function:父进程创建3个子进程为它们设置不同的优先数和调度策略*/#include#include#include#include#includeintmain(intargc,char*argv[]){inti,j,status;intpid[3];//存放进程号structsched_paramp[3];//设置调度策略时使用的数据结构for(i=0;i0){;//取进程优先数放在调度策略数据结构中p[i].
sched_priority=(argv[i+1]!
=NULL)atoi(argv[i+1]):10;//父进程设置子进程的调度策略.
如果命令行第4,5,6参数指定了3个策略第二部分操作系统算法实验第33页值则按指定的数设置,否则都为默认策略sched_setscheduler(pid[i],(argv[i+4]!
=NULL)atoi(argv[i+4]):SCHED_OTHER,&p[i]);//父进程设置子进程的优先数,如果命令行第1,2,3参数指定了3个优先数则按指定的数设置,否则都为10setpriority(PRIO_PROCESS,pid[i],(argv[i+1]!
=NULL)atoi(argv[i+1]):10);}//各子进程循环报告其优先数和调度策略else{sleep(1);//每隔1妙报告一次进程号和优先级for(i=0;i#include创建一段共享内存系统调用语法:#includeintshmget(key_tkey,intsize,intflags);key共享内存的键值,可以为IPC_PRIVATE,也可以用整数指定一个size共享内存字节长度flags共享内存权限位.
shmget调用成功后,如果key用新整数指定,且flags中设置了IPC_CREAT位,则返回一个新建立的共享内存段标识符.
如果指定的key已存在则返回与key关联的标识符.
不成功返回-1实验四、进程同步实验第38页令一段共享内存附加到调用进程中的系统调用语法:#includechar*shmat(intshmid,char*shmaddr,intflags)shmid由shmget创建的共享内存的标识符shmaddr总为0,表示用调用者指定的指针指向共享段flags共享内存权限位shmat调用成功后返回附加的共享内存首地址令一段共享内存从到调用进程中分离出去的系统调用语法:#includeintshmdt(char*shmadr);shmadr进程中指向附加共享内存的指针shmdt调用成功将递减附加计数,当计数为0,将删除共享内存.
调用不成功返回-1.
创建一个信号灯数组的系统调用有语法:#includeintsemget(key_tkey,intnsems,intflags);key信号灯数组的键值,可以为IPC_PRIVATE,也可以用整数指定一个nsems信号灯数组中信号灯的个数flags信号灯数组权限位.
如果key用整数指定,应设置IPC_CREAT位.
semget调用成功,如果key用新整数指定,且flags中设置了IPC_CREAT位,则返回一个新建立的信号等数组标识符.
如果指定的整数key已存在则返回与key关联的标识符.
不成功返回-1操作信号灯数组的系统调用语法:#includeintsemop(intsemid,structsembuf*semop,unsignednops);semid由semget创建的信号灯数组的标识符semop指向sembuf数据结构的指针nops信号灯数组元素的个数.
semop调用成功返回0,不成功返回-1.
控制信号灯数组的系统调用语法:#includeintsemctl(intsemid,intsemnum,intcmd,unionsemunarg);semid由semget创建的信号灯数组的标识符semnum该信号灯数组中的第几个信号灯cmd对信号灯发出的控制命令.
例如:GETVAL返回当前信号灯状态SETVAL设置信号灯状态IPC_RMD删除标号为semid的信号灯arg保存信号灯状态的联合体,信号灯的值是其中一个基本成员unionsemun{intval;/*valueforSETVAL*/第二部分操作系统算法实验第39页.
.
.
.
.
.
};semctl执行不成功返回-1,否则返回指定的cmd的值.
创建消息队列的系统调用语法:#includeintmsgget(key_tkey,intflags)key消息队列的键值,可以为IPC_PRIVATE,也可以用整数指定一个flags消息队列权限位.
msgget调用成功,如果key用新整数指定,且flags中设置了IPC_CREAT位,则返回一个新建立的消息队列标识符.
如果指定的整数key已存在则返回与key关联的标识符.
成功返回-1.
追加一条新消息到消息队列的系统调用语法:#includeintmsgsnd(intmsqid,structmsgbuf*msgp,size_tmsgsz,intmsgflg);msqid由消息队列的标识符msgp消息缓冲区指针.
消息缓冲区结构为:structmsgbuf{longmtype;/*消息类型,必须大于0*/charmtext[1];/*消息数据,长度应于msgsz声明的一致*/}msgsz消息数据的长度msgflg为0表示阻塞方式,设置IPC_NOWAIT表示非阻塞方式msgsnd调用成功返回0,不成功返回-1.
从到消息队列中读出一条新消息的系统调用语法:#includeintmsgrcv(intmsqid,structmsgbuf*msgp,size_tmsgsz,longmsgtype,intmsgflg);msqid由消息队列的标识符msgp消息缓冲区指针.
消息缓冲区结构为:structmsgbuf{longmtype;/*消息类型,必须大于0*/charmtext[1];/*消息数据,长度应于msgsz声明的一致*/}msgsz消息数据的长度msgtype决定从队列中返回哪条消息:=0返回消息队列中第一条消息>0返回消息队列中等于mtype类型的第一条消息.
实验四、进程同步实验第40页intmsgctl(intmsqid,intcmd,structmsqid_ds*buf);msqid由消息队列的标识符cmd控制命令.
常用的有:IPC_RMID删除msgid标识的消息队列IPC_STAT为非破坏性读,从队列中读出一个msgid_ds结构填充缓冲bufIPC_SET改变队列的UID,GID,访问模式和最大字节数.
msgctl调用成功返回0,不成功返回-1.
4.
3示例实验以下示例实验程序应能模拟多个生产/消费者在有界缓冲上正确的操作.
它利用N个字节的共享内存作为有界循环缓冲区,利用写一字符模拟放一个产品,利用读一字符模拟消费一个产品.
当缓冲区空时消费者应阻塞睡眠,而当缓冲区满时生产者应当阻塞睡眠.
一旦缓冲区中有空单元,生产者进程就向空单元中入写字符,并报告写的内容和位置.
一旦缓冲区中有未读过的字符,消费者进程就从该单元中读出字符,并报告读取位置.
生产者不能向同一单元中连续写两次以上相同的字符,消费者也不能从同一单元中连续读两次以上相同的字符.
1)在当前新建文件夹中建立以下名为ipc.
.
h的C程序的头文件,该文件中定义了生产者/消费者共用的IPC函数的原型和变量:/**Filename:ipc.
h*copyright:(C)2006byzhonghonglie*Function:声明IPC机制的函数原型和全局变量*/#include#include#include#include#include#include#include#defineBUFSZ256//建立或获取ipc的一组函数的原型说明intget_ipc_id(char*proc_file,key_tkey);char*set_shm(key_tshm_key,intshm_num,intshm_flag);intset_msq(key_tmsq_key,intmsq_flag);intset_sem(key_tsem_key,intsem_val,intsem_flag);intdown(intsem_id);第二部分操作系统算法实验第41页intup(intsem_id);/*信号灯控制用的共同体*/typedefunionsemuns{intval;}Sem_uns;/*消息结构体*/typedefstructmsgbuf{longmtype;charmtext[1];}Msg_buf;//生产消费者共享缓冲区即其有关的变量key_tbuff_key;intbuff_num;char*buff_ptr;//生产者放产品位置的共享指针key_tpput_key;intpput_num;int*pput_ptr;//消费者取产品位置的共享指针key_tcget_key;intcget_num;int*cget_ptr;//生产者有关的信号量key_tprod_key;key_tpmtx_key;intprod_sem;intpmtx_sem;//消费者有关的信号量key_tcons_key;key_tcmtx_key;intcons_sem;intcmtx_sem;intsem_val;intsem_flg;intshm_flg;2)在当前新建文件夹中建立以下名为ipc.
c的C程序,该程序中定义了生产者/消费者共用的IPC函数:/*实验四、进程同步实验第42页*Filename:ipc.
c*copyright:(C)2006byzhonghonglie*Function:一组建立IPC机制的函数*/#include"ipc.
h"/**get_ipc_id()从/proc/sysvipc/文件系统中获取IPC的id号*pfile:对应/proc/sysvipc/目录中的IPC文件分别为*msg-消息队列,sem-信号量,shm-共享内存*key:对应要获取的IPC的id号的键值*/intget_ipc_id(char*proc_file,key_tkey){FILE*pf;inti,j;charline[BUFSZ],colum[BUFSZ];if((pf=fopen(proc_file,"r"))==NULL){perror("Procfilenotopen");exit(EXIT_FAILURE);}fgets(line,BUFSZ,pf);while(!
feof(pf)){i=j=0;fgets(line,BUFSZ,pf);while(line[i]i++;while(line[i]colum[j++]=line[i++];colum[j]='\0';if(atoi(colum)!
=key)continue;j=0;while(line[i]i++;while(line[i]colum[j++]=line[i++];colum[j]='\0';i=atoi(colum);fclose(pf);returni;}fclose(pf);return-1;}/**信号灯上的down/up操作*semid:信号灯数组标识符*semnum:信号灯数组下标*buf:操作信号灯的结构*/intdown(intsem_id)第二部分操作系统算法实验第43页{structsembufbuf;buf.
sem_op=-1;buf.
sem_num=0;buf.
sem_flg=SEM_UNDO;if((semop(sem_id,&buf,1))#include#include#include#include#include#include#defineBUFSZ256#defineMAXVAL100#defineSTRSIZ8#defineWRITERQUEST1//写请求标识#defineREADERQUEST2//读请求标识#defineFINISHED3//读写完成标识/*信号灯控制用的共同体*/typedefunionsemuns{intval;}Sem_uns;/*消息结构体*/typedefstructmsgbuf{longmtype;intmid;}Msg_buf;key_tbuff_key;intbuff_num;char*buff_ptr;intshm_flg;intquest_flg;key_tquest_key;intquest_id;intrespond_flg;key_trespond_key;intrespond_id;intget_ipc_id(char*proc_file,key_tkey);char*set_shm(key_tshm_key,intshm_num,intshm_flag);intset_msq(key_tmsq_key,intmsq_flag);intset_sem(key_tsem_key,intsem_val,intsem_flag);intdown(intsem_id);intup(intsem_id);第二部分操作系统算法实验第53页2)将消费者生产者问题实验中ipc.
c文件拷贝到当前目录中.
3)在当前目录中建立如下的控制者程序control.
c/**Filename:control.
c*copyright:(C)2006byzhonghonglie*Function:建立并模拟控制者进程*/#include"ipc.
h"intcount=MAXVAL;intmain(intargc,char*argv[]){inti;intrate;Msg_bufmsg_arg;structmsqid_dsmsg_inf;//建立一个共享内存先写入一串A字符模拟要读写的内容buff_key=101;buff_num=STRSIZ+1;shm_flg=IPC_CREAT|0644;buff_ptr=(char*)set_shm(buff_key,buff_num,shm_flg);for(i=0;i0){quest_flg=IPC_NOWAIT;//以非阻塞方式接收请求消息if(msgrcv(quest_id,&msg_arg,sizeof(msg_arg),WRITERQUEST,quest_flg)>=0){//有写者请求,允许写者写count-=MAXVAL;msg_arg.
mtype=msg_arg.
mid;msgsnd(respond_id,&msg_arg,sizeof(msg_arg),0);实验五、进程互斥实验第54页printf("%dquestwrite\n",msg_arg.
mid);}elseif(msgrcv(quest_id,&msg_arg,sizeof(msg_arg),FINISHED,quest_flg)>=0){//有读者完成count++;printf("%dreaderfinished\n",msg_arg.
mid);}elseif(msgrcv(quest_id,&msg_arg,sizeof(msg_arg),READERQUEST,quest_flg)>=0){//有读者请求,允许读者读count--;msg_arg.
mtype=msg_arg.
mid;msgsnd(respond_id,&msg_arg,sizeof(msg_arg),0);printf("%dquestread\n",msg_arg.
mid);}}//当count等于0时说明写者正在写,等待写完成if(count==0){msgrcv(quest_id,&msg_arg,sizeof(msg_arg),FINISHED,0);//以阻塞方式接收消息count=MAXVAL;printf("%dwritefinished\n",msg_arg.
mid);if(msgrcv(quest_id,&msg_arg,sizeof(msg_arg),READERQUEST,quest_flg)>=0){//有读者请求,允许读者读count--;msg_arg.
mtype=msg_arg.
mid;msgsnd(respond_id,&msg_arg,sizeof(msg_arg),0);printf("%dquestread\n",msg_arg.
mid);}}//当count小于0时说明有多个读者正在读,等待它们读完while(count#include#include#include#include#include#include#include#include#include/*信号灯控制用的共同体*/typedefunionsemuns{intval;}Sem_uns;//哲学家的3个状态(思考、饥俄、就餐)enumState{thinking,hungry,eating};//哲学家管程中使用的信号量第二部分操作系统算法实验第61页classSema{public:Sema(intid);~Sema();intdown();//信号量加1intup();//信号量减1private:intsem_id;//信号量标识符};//哲学家管程中使用的锁classLock{public:Lock(Sema*lock);~Lock();voidclose_lock();voidopen_lock();private:Sema*sema;//锁使用的信号量};//哲学家管程中使用的条件变量classCondition{public:Condition(char*st[],Sema*sm);~Condition();voidWait(Lock*lock,inti);//条件变量阻塞操作voidSignal(inti);//条件变量唤醒操作private:Sema*sema;//哲学家信号量char**state;//哲学家当前的状态};//哲学家管程的定义classdp{public:dp(intrate);//管程构造函数~dp();voidpickup(inti);//获取筷子voidputdown(inti);//放下筷子//建立或获取ipc信号量的一组函数的原型说明实验六、死锁问题实验第62页intget_ipc_id(char*proc_file,key_tkey);intset_sem(key_tsem_key,intsem_val,intsem_flag);//创建共享内存,放哲学家状态char*set_shm(key_tshm_key,intshm_num,intshm_flag);private:intrate;//控制执行速度Lock*lock;//控制互斥进入管程的锁char*state[5];//5个哲学家当前的状态Condition*self[5];//控制5个哲学家状态的条件变量};2)在当前目录中建立如下的哲学家就餐程序dp.
cc/**Filename:dp.
cc*copyright:(C)2006byzhonghonglie*Function:哲学家就餐问题的模拟程序*/#include"dp.
h"Sema::Sema(intid){sem_id=id;}Sema::~Sema(){}/**信号灯上的down/up操作*semid:信号灯数组标识符*semnum:信号灯数组下标*buf:操作信号灯的结构*/intSema::down(){structsembufbuf;buf.
sem_op=-1;buf.
sem_num=0;buf.
sem_flg=SEM_UNDO;if((semop(sem_id,&buf,1))down();}//开锁voidLock::open_lock(){sema->up();}//用于哲学家就餐问题的条件变量Condition::Condition(char*st[],Sema*sm){state=st;sema=sm;}实验六、死锁问题实验第64页/**左右邻居不在就餐,条件成立,状态变为就餐*否则睡眠,等待条件成立*/voidCondition::Wait(Lock*lock,inti){if((*state[(i+4)%5]!
=eating)&&(*state[i]==hungry)&&(*state[(i+1)%5]!
=eating))*state[i]=eating;//拿到筷子,进就餐态else{coutopen_lock();//开锁sema->down();//没拿到,以饥饿态等待lock->close_lock();//上锁}}/**左右邻居不在就餐,则置其状态为就餐,*将其从饥俄中唤醒.
否则什么也不作.
*/voidCondition::Signal(inti){if((*state[(i+4)%5]!
=eating)&&(*state[i]==hungry)&&(*state[(i+1)%5]!
=eating)){//可拿到筷子,从饥饿态唤醒进就餐态sema->up();*state[i]=eating;}}/**get_ipc_id()从/proc/sysvipc/文件系统中获取IPC的id号*pfile:对应/proc/sysvipc/目录中的IPC文件分别为*msg-消息队列,sem-信号量,shm-共享内存*key:对应要获取的IPC的id号的键值*/intdp::get_ipc_id(char*proc_file,key_tkey){#defineBUFSZ256第二部分操作系统算法实验第65页FILE*pf;inti,j;charline[BUFSZ],colum[BUFSZ];if((pf=fopen(proc_file,"r"))==NULL){perror("Procfilenotopen");exit(EXIT_FAILURE);}fgets(line,BUFSZ,pf);while(!
feof(pf)){i=j=0;fgets(line,BUFSZ,pf);while(line[i]i++;while(line[i]colum[j++]=line[i++];colum[j]='\0';if(atoi(colum)!
=key)continue;j=0;while(line[i]i++;while(line[i]colum[j++]=line[i++];colum[j]='\0';i=atoi(colum);fclose(pf);returni;}fclose(pf);return-1;}/**set_sem函数建立一个具有n个信号灯的信号量*如果建立成功,返回一个信号量的标识符sem_id*输入参数:*sem_key信号量的键值*sem_val信号量中信号灯的个数*sem_flag信号量的存取权限*/intdp::set_sem(key_tsem_key,intsem_val,intsem_flg){intsem_id;Sem_unssem_arg;//测试由sem_key标识的信号量是否已经建立if((sem_id=get_ipc_id("/proc/sysvipc/sem",sem_key))close_lock();//进入管程,上锁*state[i]=hungry;//进饥饿态self[i]->Wait(lock,i);//测试是否能拿到两只筷子coutopen_lock();//离开管程,开锁}//放下筷子的操作//状态改变为思考,如左右邻居有阻塞者则唤醒它voiddp::putdown(inti){intj;lock->close_lock();//进入管程,上锁*state[i]=thinking;//进思考态j=(i+4)%5;self[j]->Signal(j);//唤醒左邻居j=(i+1)%5;self[j]->Signal(j);//唤醒右邻居lock->open_lock();//离开管程,开锁cout1)atoi(argv[1]):3;tdp=newdp(rate);//建立一个哲学家就餐的管程对象第二部分操作系统算法实验第69页pid[0]=fork();//建立第一个哲学家进程if(pid[0]pickup(0);//拿起筷子tdp->putdown(0);//放下筷子}}pid[1]=fork();//建立第二个哲学家进程if(pid[1]pickup(1);//拿起筷子tdp->putdown(1);//放下筷子}}pid[2]=fork();//建立第三个哲学家进程if(pid[2]pickup(2);//拿起筷子tdp->putdown(2);//放下筷子}}pid[3]=fork();//建立第四个哲学家进程if(pid[3]pickup(3);//拿起筷子tdp->putdown(3);//放下筷子}}pid[4]=fork();//建立第五个哲学家进程if(pid[4]pickup(4);//拿起筷子实验六、死锁问题实验第70页tdp->putdown(4);//放下筷子}}return0;}3)在新建文件夹中建立以下Makefile文件head=dp.
hsrcs=dp.
ccobjs=dp.
oopts=-w-g-call:dpdp:$(objs)g++$(objs)-odpdp.
o:$(srcs)$(head)g++$(opts)$(srcs)clean:rmdp*.
o4)在新建文件夹中执行make命令编译连接生成可执行的哲学家就餐程序$gmakeg++-w-g-cdp.
ccg++dp.
o-odp5)执行的哲学家就餐程序dp$.
/dp1p1:4524eatingp2:4525hungryp3:4526eatingp4:4527hungryp5:4528hungryp1:4524thinkingp5:4528eatingp3:4526thinkingp2:4525eatingp1:4524hungryp5:4528thinkingp4:4527eatingp3:4526hungryp1:4524eatingp2:4525thinkingp5:4528hungry……可以看到5个哲学家进程在3中状态中不断的轮流变换,且连续的5个输出中不应第二部分操作系统算法实验第71页有多于2个的状态为eating,同一进程号不应有两个连续的输出.
您可用不同的执行速率长时间的让它们执行,观察是否会发生死锁或饥饿现象.
如果始终没有产生死锁和饥饿现象,可用kill按其各自的进程号终止它们的执行.
6)请修改以上dp.
cc程序,制造出几种不同的的死锁现象和饥饿现象,记录并分析各种死锁、饥饿现象和产生死锁、饥饿现象的原因.
6.
4独立实验在两个城市南北方向之间存在一条铁路,多列火车可以分别从两个城市的车站排队等待进入车道向对方城市行驶,该铁路在同一时间,只能允许在同一方向上行车,如果同时有相向的火车行驶将会撞车.
请模拟实现两个方向行车,而不会出现撞车或长时间等待的情况.
您能构造一个管程来解决这个问题吗6.
5实验要求总结和分析示例实验和独立实验中观察到的调试和运行信息.
分析示例实验是否真正模拟了哲学家就餐问题为什么示例程序不会产生死锁为什么会出现进程死锁和饥饿现象怎样利用实验造成和表现死锁和饥饿现象管程能避免死锁和饥饿的机理是什么您对于管程概念有哪些新的理解和认识条件变量和信号量有何不同为什么在管程中要使用条件变量而不直接使用信号量来达到进程同步的目的示例实验中构造的管程中的条件变量是一种什么样式的其中的锁起了什么样的作用你的独立实验程序是怎样解决单行道问题的您是怎样构造管程对象的根据实验程序、调试过程和结果分析写出实验报告.
实验七、内存页面置换算法实验第72页实验七、内存页面置换算法实验7.
1实验目的加深对于存储管理的了解,掌握虚拟存储器的实现原理;观察和了解重要的页面置换算法和置换过程.
练习模拟算法的编程技巧,锻炼分析试验数据的能力.
7.
2实验说明1.
示例实验程序中模拟两种置换算法:LRU算法和FIFO算法2.
能对两种算法给定任意序列不同的页面引用串和任意帧实内存块数的组合测试,显示页置换的过程.
3.
能统计和报告不同置换算法情况下依次淘汰的页号、缺页次数(页错误数)和缺页率.
比较两种置换算法在给定条件下的优劣.
4.
为了能方便的扩充页面置换算法,更好的描述置换过程,示例实验程序采用了C++语言用Replace类描述了置换算法及其属性.
7.
3示例实验1)在新建文件夹中建立以下vmrp.
h文件:/**Filename:vmrp.
h*copyright:(C)2006byzhonghonglie*Function:声明虚拟内存页置换类*/#include#include#includeclassReplace{public:Replace();~Replace();voidInitSpace(char*MethodName);//初始化页号记录voidReport(void);//报告算法执行情况voidFifo(void);//先进先出算法voidLru(void);//最近最旧未用算法voidClock(void);//时钟(二次机会)置换算法voidEclock(void);//增强二次机会置换算法voidLfu(void);//最不经常使用置换算法voidMfu(void);//最经常使用置换算法private:int*ReferencePage;//存放要访问到的页号int*EliminatePage;//存放淘汰页号第二部分操作系统算法实验第73页int*PageFrames;//存放当前正在实存中的页号intPageNumber;//访问页数intFrameNumber;//实存帧数intFaultNumber;//失败页数};2)在新建文件夹中建立以下vmrp.
cc文件:/**Filename:vmrp.
cc*copyright:(C)2006byzhonghonglie*Function:模拟虚拟内存页置换算法的程序*/#include"vmrp.
h"Replace::Replace(){inti;//设定总得访问页数,并分配相应的引用页号和淘汰页号记录数组空间cout>PageNumber;ReferencePage=newint[sizeof(int)*PageNumber];EliminatePage=newint[sizeof(int)*PageNumber];//输入引用页号序列(页面走向),初始化引用页数组cout>ReferencePage[i];//引用页暂存引用数组//设定内存实页数(帧数),并分配相应的实页号记录数组空间(页号栈)cout>FrameNumber;PageFrames=newint[sizeof(int)*FrameNumber];}Replace::~Replace(){}voidReplace::InitSpace(char*MethodName){inti;cout0;j--)PageFrames[j]=PageFrames[j-1];PageFrames[0]=next;break;}}if(PageFrames[0]==next){//如果引用页已放栈顶,则为不缺页,报告当前内存页号for(j=0;j=0)cout0;j--)PageFrames[j]=PageFrames[j-1];PageFrames[0]=next;//引用页放栈顶//报告当前实存中页号for(j=0;j=0)cout=0)coutEliminatePage[l++]=0)cout=0)coutEliminatePage[l++]Lru();vmpr->Fifo();return0;}3)在新建文件夹中建立以下Makefile文件head=vmrp.
hsrcs=vmrp.
ccobjs=vmrp.
oopts=-w-g-call:vmrpvmrp:$(objs)g++$(objs)-ovmrpvmrp.
o:$(srcs)$(head)g++$(opts)$(srcs)clean:rmvmrp*.
o4)执行make命令编译连接,生成可执行文件vmpr第二部分操作系统算法实验第77页$gmakeg++-g-cvmrp.
ccvmrp.
hg++vmrp.
o-ovmrp5)执行vmpr命令,输入引用页数为12,引用串为Belady串,内存页帧数为3$.
/vmprPleaseinputreferencepagenumbers:12Pleaseinputreferencepagestring:123412512345Pleaseinputpageframes:3FIFO112123423->1413->2412->3512->4512512532->1534->2534Eliminatepage:123412Numberofpagefaults=9Rateofpagefaults=75%LRU121321432->1143->2214->3521->4152215321->5432->1543->2Eliminatepage:1234512Numberofpagefaults=10Rateofpagefaults=83.
3%以上输出报告了FIFO和LRU两种算法的页置换情况.
其中每一行数字为当前实存实验七、内存页面置换算法实验第78页中的页号,->右边的数字表示当前被淘汰的页号.
每种算法最后3行输出为:依次淘汰页号,缺页数,页出错率.
6)再次执行vmpr命令,仍然输入Belady串,仅将页帧数改为4$.
/vmprPleaseinputreferencepagenumbers:12Pleaseinputreferencepagestring:123412512345Pleaseinputpageframes:4FIFO1121231234123412345234->15134->25124->35123->44123->54523->1Eliminatepage:123451Numberofpagefaults=10Rateofpagefaults=83.
3%LRU1213214321143221435214->3152421543215->44321->55432->1Eliminatepage:3451Numberofpagefaults=8Rateofpagefaults=66.
7%从以上输出中可以看出FIFO置换算法的Belady异常现象,即当在相同的引用串下内存页帧数从3帧增加到4帧,页出错率反而从75%增加到了83.
3%.
而在相同的情况下LUR置换算法无此异常现象.
第二部分操作系统算法实验第79页7.
4独立实验请在以上示例实验程序中补充―增强二次机会‖等置换算法的模拟程序.
输入不同的内存页面引用串和实存帧数,观察并分析其页面置换效果和性能,并将其与LRU和FIFO算法进行比较.
改进以上示例实验程序,使之能够随机的产生内存页面引用串,以便能动态的观测各种置换算法的性能.
7.
5实验要求1.
说明您做了哪些不同的引用串在不同实存帧中的测试,发现了哪些现象2.
选择一些典型的现象作出不同算法中帧数与缺页数的曲线图.
3.
说明您的程序是怎样模拟―增强二次机会‖等置换算法的4.
综合分析实验结果中各种页面置换算法各适应于怎样的页面引用串和和内存帧数.
5.
根据实验程序、调试过程和结果分析写出实验报告.
实验八、磁盘移臂调度算法实验第80页实验八、磁盘移臂调度算法实验8.
1实验目的加深对于操作系统设备管理技术的了解,体验磁盘移臂调度算法的重要性;掌握几种重要的磁盘移臂调度算法,练习模拟算法的编程技巧,锻炼研究分析试验数据的能力.
8.
2实验说明1.
示例实验程序中模拟两种磁盘移臂调度算法:SSTF算法和SCAN算法2.
能对两种算法给定任意序列不同的磁盘请求序列,显示响应磁盘请求的过程.
3.
能统计和报告不同算法情况下响应请求的顺序、移臂的总量.
比较两种算法在给定条件下的优劣.
4.
为了能方便的扩充磁盘移臂调度算法,更好的描述磁盘移臂调度过程,示例实验程序采用了C++语言用DiskArm类描述了磁盘移臂调度算法及其属性.
8.
3示例实验1)在新建文件夹中建立以下dask.
h文件:/**Filename:dask.
h*copyright:(C)2006byzhonghonglie*Function:声明磁盘移臂调度类*/#include#include#includeclassDiskArm{public:DiskArm();~DiskArm();voidInitSpace(char*MethodName);//初始化寻道记录voidReport(void);//报告算法执行情况voidFcfs(void);//先来先服务算法voidSstf(void);//最短寻道时间优先算法voidScan(void);//电梯调度算法voidCScan(void);//均匀电梯调度算法voidLook(void);//LOOK调度算法private:int*Request;//磁盘请求道号int*Cylinder;//工作柱面道号号第二部分操作系统算法实验第81页intRequestNumber;//磁盘请求数intCurrentCylinder;//当前道号intSeekDirection;//磁头方向intSeekNumber;//移臂总数intSeekChang;//磁头调头数};2)在新建文件夹中建立以下dask.
cc文件:/**Filename:dask.
cc*copyright:(C)2006byzhonghonglie*Function:磁盘移臂调度算法*/#include"dask.
h"DiskArm::DiskArm(){inti;//输入当前道号cout>CurrentCylinder;//磁头方向,输入0表示向小道号移动,1表示向大道号移动cout>SeekDirection;//输入磁盘请求数,请求道号cout>RequestNumber;cout>Request[i];}DiskArm::~DiskArm(){}//初始化道号,寻道记录voidDiskArm::InitSpace(char*MethodName){inti;cout=Current)&&!
Direction)||((Cylinder[i]abs(Current-Cylinder[j])){//到下一道号比当前距离近,下一道号为当前距离Distance=abs(Current-Cylinder[j]);Shortest=j;}}if(((Cylinder[Shortest]>=Current)&&!
Direction)||((Cylinder[Shortest]Fcfs();dask->Sstf();}3)在新建文件夹中建立以下Makefile文件head=dask.
hsrcs=dask.
ccobjs=dask.
oopts=-w-g-call:daskdask:$(objs)g++$(objs)-odaskdask.
o:$(srcs)$(head)g++$(opts)$(srcs)clean:rmdask*.
o4)执行make命令编译连接,生成可执行文件dask$gmakeg++-w-g-cdask.
ccg++dask.
o-odask5)执行dask命令,输入当前道号,当前寻道方向,当前请求寻道数,当前请求寻道的道号串:.
/daskPleaseinputCurrentcylinder:53PleaseinputCurrentDirection(0/1):0PleaseinputRequestNumbers:8PleaseinputRequestcylinderstring:9818337122141246567FCFS5353->98->183183->3737->122122->1414->124124->6565->67SeekNumber:640ChangDirection:7SSTF5353->65->6767->37->1414->98->122->124->183SeekNumber:236第二部分操作系统算法实验第85页ChangDirection:3$可以看到以上程序的执行演示了FCFS和SSTF两种磁盘移臂调度算法响应磁盘请求的次序(其中每换一行表示磁头发生调头).
统计出了这两种算法的调度性能,从中看出在以上磁盘柱面请求序列下SSTF调度算法所产生的磁头移动为236柱面,约为FCFS调度算法所产生的磁头移动数量640柱面的三分之一稍多一点.
磁头调头数3次也比FCFS调度算法的7次少了4次.
因此对于以上磁盘柱面请求序列,SSTF调度算法将比FCFS调度算法大大提高了磁盘的响应速度.
8.
4独立实验请在以上示例实验程序中补充SCAN,C-SCAN,LOOK磁盘移臂调度算法的模拟程序.
输入不同的磁盘柱面请求序列,观察和分析其调度效果和性能,并将其与FCFS和SSTF算法进行比较.
改进以上示例实验程序,使之能够随机的产生磁盘柱面请求序列,以便能动态的观测各种调度算法的性能.
8.
5实验要求1.
说明您做了哪些磁盘请求序列的测试,发现了哪些现象2.
选择一些典型磁盘请求序列的响应结果,画出不同算法中的寻道曲线图.
3.
说明您的程序是怎样模拟SCAN等算法的4.
综合分析实验结果中各种算法各适应于怎样的磁盘柱面寻道请求情况.
5.
根据实验程序、调试过程和结果分析写出实验报告实验九、文件系统接口实验第86页实验九、文件系统接口实验9.
1实验目的通过文件系统调用的编程和调试,加深对于文件系统的更深入的了解,体验文件系统接口机制的功能,掌握基于文件描述符的主要I/O操作技术.
通过本试验的调试可更深入的理解教材中有关文件的分类方法,文件的逻辑结构、文件的物理结构、文件的存取控制、文件的保护方式等文件管理功能,以及Unix/Linux系统中一切皆文件的理念.
9.
2实验说明1)打开文件与文件描述符文件描述符是一个打开文件记录表的索引值,它对应一个正整数.
它是许多低级I/O操作和网路操作的基本编程接口.
例如,每个进程启动时都会自动打开3个文件:标准输入、标准输出、标准错误输出.
这3个文件对应的文件描述符的默认值为0,1,2.
为记忆方便同时也赋予它们3个标识名stdin、stdout、stderr.
有了文件描述符对于打开文件的所有I/O操作都可以方便的使用文件描述符来完成.
除了标准输入、标准输出、标准错误输出3个文件的描述符由系统自动给出之外,进程要打开的其他文件必须由进程通过打开文件系统调用open获得它的文件描述符后才能进行读、写、定位等操作.
操作完成后应通过关闭文件操作close将文件描述符对应的缓冲区刷新并清理掉打开文件记录.
open系统调用的语法:#include#include#includeintopen(constchar*pathname,intflags)pathname要打开或要创建的文件名串指针flags要打开或要创建的文件的访问方式,有以下几种标志:O_RDONLY只读方式打开O_WRONLY只写方式打开O_RDWR读写方式打开O_CREAT文件不存在则建立O_EXCL与O_CREAT连用,如果文件已存在,则使打开失败O_NOCTTY如果打开文件为终端,不让其成为打开进程的终端O_TRUNC如果文件存在,则将其长度截为0O_APPEND追加方式打开(文件写指针置文件尾)O_NONBLOCK以非阻塞方式打开.
如读操作阻塞将只会读出0字节O_NODELY同O_NONBLOCKO_SYNC在数据物理的写入设备后才返回第二部分操作系统算法实验第87页open调用成功后返回一个文件描述符.
不成功返回-1,出错号放系统变量errno中.
close系统调用的语法为:#includeintclose(intfd)fd由open返回的文件描述符2)使用文件描述符读read、写write文件操作的系统调用语法为:#includessize_tread(intfd,constvoid*buf,size_tcount);ssize_twrite(intfd,constvoid*buf,size_tcount);fd由open返回的文件描述符buf读写数据缓冲区指针count指定要读写的字节数read/write调用成功返回实际读写的字节数,不成功返回-1,出错号放errno.
3)使用文件描述符定位文件读写指针操作的lseek系统调用语法为:#include#includeoff_tlseek(intfd,off_toffset,intwhence);fd由open返回的文件描述符offset文件读写指针要移动的相对字节偏量,可以为负值.
Whence文件读写指针要移动的相对位置,可以为:SEEK_SET从文件头移动offset字节SEEK_CUR从当前读写位置移动offset字节SEEK_END从文件尾移动offset字节lseek调用成功返回新文件读写指针位置,不成功返回-1,出错号放errno.
4)使用文件描述符获得文件控制信息操作的fstat系统调用语法为:#include#includeintfstat(intfd,structstat*buf);fd由open返回的文件描述符bufstat结构指针stat结构定义了一个打开文件的控制信息结构.
该结构的字段说明如下structstat{dev_tst_dev;//设备文件名ino_tst_ino;//文件的I节点编号mode_tst_mode;//文件的保护字段nlink_tst_nlink;//文件链接数uid_tst_uid;//文件所有者编号gid_tst_gid;//文件所有者组号dev_tst_rdev;//设备类型off_tst_size;//文件的字节总长度实验九、文件系统接口实验第88页blksize_tst_blksize;//文件系统I/O块长blkcnt_tst_blocks;//文件已分配的块数time_tst_atime;//文件最后访问的时间time_tst_mtime;//文件最后修改的时间time_tst_ctime;//文件最后改变的时间};fstat调用成功会把该文件的控制信息保存到buf所指向的数据结构中,并返回0值;调用失败返回-1,出错号放errno.
9.
3示例实验示例实验程序通过打开用户指定的不同类型的文件,检验不同类型文件的属性和操作方法.
1)在新文件夹中中建立以下名为filexm.
h的C语言头文件:/**Filename:filexm.
h*copyright:(C)byzhonghonglie*Function:获取的文件管理信息,检测文件读写操作.
*/#include#include#include#include#include#include#include#defineBUFSZ32//判断文件类型voidis_filetype(mode_tmode){//显示文件类型printf("FileType:\t");//链接文件if(S_ISLNK(mode))printf("SymbolicLinke\n");//普通文件elseif(S_ISREG(mode))printf("Regular\n");//目录文件elseif(S_ISDIR(mode))printf("Directoy\n");//字符设备elseif(S_ISCHR(mode))printf("CharacterDevice\n");//块设备elseif(S_ISBLK(mode))printf("BlockDevice\n");//管道文件elseif(S_ISFIFO(mode))printf("FIFO\n");//套接口elseif(S_ISSOCK(mode))printf("Socket\n");第二部分操作系统算法实验第89页//不可识别的设备elseprintf("Unkowntype\n");}2)在新文件夹中中建立以下名为filexm.
c的C语言程序:/**Filename:filexm.
c*copyright:(C)byzhonghonglie*Function:获取的文件管理信息,检测文件读写操作.
*/#include"filexm.
h"intmain(intargc,char*argv[]){intfd;//打开文件的文件描述字structstatbuf;//保存打开文件信息的缓冲区chardata[BUFSZ];//文件读写缓冲区//检查命令是否带有指定的文件名if(argc!
=2){perror("USAGE:.
/filexmfilename");exit(EXIT_FAILURE);}//检查命令指定的文件名是否存在,如果存在打开该文件if((fd=open(argv[1],O_RDONLY))0){//读BUFSZ字节write(2,data,BUFSZ);//写到标准输出设备上printf("\n");}//关闭文件if(close(fd)#includeintsocket(intdomain,inttype,intprotocol);domain说明本套接口的通信域.
以下是一些常用的通信域:AF_UNIXUNIX内部协议AF_INETIPv4Internet协议AF_INET6IPv6Internet协议AF_IPXIPX-Novell协议AF_APPLETALKAppletalk协议type说明本套接口类型,主要有以下类型:SOCK_STREAM面向连接的,可靠的顺序的双向连接SOCK_DGRAM非面向连接的,不可靠连接SOCK_RAW用于内部网络协议(root用户专用)protocol说明一个附加的协议,无附加协议时总为0.
Socket系统调用很类似于open系统调用,成功后返回一个类似文件描述符的套接口描述符.
不成功返回-1,出错号放系统变量errno中.
分配一个套接口的操作对于服务器和客户机来说都是一样的.
对于服务器来说在成功分配到一个套接口后,下一步还要将一个进程与该套接口绑定起来,准备接收客户机的连接.
客户机不必做这项工作.
第二部分操作系统算法实验第93页bind绑定一个套接口的系统调用语法为:#include#includeintbind(intsockfd,conststructsockaddr*my_addr,socklen_taddrlen);sockfd由socket操作分配的套接口描述符my_addrsockaddr结构指针sockaddr结构为:structsockaddr{sa_family_tsa_family;charsa_data[14];}sa_family通信域sa_data14个字节的协议地址这个域地址在编程时由实际采用的通信域地址来替换.
例如您工作在Internet通信域,则说明的实际地址结构应为以下Internet通信域地址:structsockaddr_in{sa_family_tsin_family;/*AF_INET通信协议*/u_int16_tsin_port;/*网络端口号*/structin_addrsin_addr;/*IP地址结构*/};/*IP地址结构*/structin_addr{u_int32_ts_addr;/*IP地址*/};其中的网络端口号可以用以下函数得到:#includeuint16_thtons(uint16_thostshort);将一个短整数转换为一个网络端口号uint32_thtonl(uint32_thostlong);将一个整数转换为一个网络端口号其中的IP地址s_addr可以用常数INADDR_ANY(0.
0.
0.
0)任意地址填充;或用常数INADDR_LOOPBACK(127.
0.
0.
1)本机地址填充;也可以从以下gethostbyname函数返回的hostent结构中得到,gethostbyname将一个由主机域名或点分十进制表示的IP地址转换为一个IP协议格式定义的hostent结构.
#includeexterninth_errno;structhostent*gethostbyname(constchar*name);structhostent{char*h_name;/*公开的主机名*/char**h_aliases;/*主机别名表*/inth_addrtype;/*协议类型,总为AF_INET或AF_INET6*/inth_length;/*地址长度*/char**h_addr_list;/*地址表*/实验十、分布式系统实验第94页}#defineh_addrh_addr_list[0]/*首条地址*/gethostbyname成功返回一个指向hostent结构的指针,不成功返回NULL,出错号放h_errno中.
(2)完成连接对于服务器来说在成功获取并绑定一个套接口后,还要调用侦听客户机请求的系统调用list,在接收队列上侦听客户的接入信号.
list系统调用的语法:#includeintlisten(intsockfd,intbacklog);sockfd由socket操作分配的套接口描述符backlog接入信号缓冲队列的大小成功返回0,不成功返回-1,出错号放系统变量errno中.
当一个接入信号抵达服务器套接口后,首先挂入缓冲队列等待处理.
在侦听到有信号接入后,accecp系统调用负责检索和接收一个挂入的接入信号.
accept系统调用的语法:#include#includeintaccept(intsockfd,structsockaddr*addr,socklen_t*addrlen);sockfd由socket操作分配的套接口描述符addrsockaddr结构指针,它是发送方通信域的一个地址addrlenaddr能够容纳的最大字节数成功返回非负值,不成功返回-1,出错号放系统变量errno中.
对于客户机上的进程,在成功分配到一个套接口后需要通过connect系统调用连接上远程服务器上对应的服务进程.
connect系统调用的语法:#include#includeintconnect(intsockfd,conststructsockaddr*serv_addr,socklen_taddrlen);sockfd由socket操作分配的套接口描述符serv_addrsockaddr结构指针,addrlen设置serv_addr能够容纳的最大字节数connect成功返回0,不成功返回-1,出错号放系统变量errno中.
(3)传送数据一旦建立好连接,就可以开始在服务器/客户机之间传输数据了.
对于面向连接的传输,recv系统调用用于接收套接口传来的数据,send系统调用用于向套接口发送的数据.
recv和send系统调用的语法:#include#includessize_trecv(intsocket,void*buf,size_tlen,intflags);第二部分操作系统算法实验第95页ssize_tsend(intsocket,constvoid*buf,size_tlen,intflags);socket已建立连接的套接口buf接收或发送数据的缓冲区指针len接收或发送数据的缓冲区的长度flags操作标志,可以为:对于recv操作MSG_OOB处理带外数据.
不设置,处理一般非带外数据MSG_PEEK查看接入消息但不处理MSG_WAITALL等待缓冲区数据填满后再返回对于send操作MSG_OOB处理带外数据.
不设置,处理一般非带外数据MSG_DONTROUTE不使用路由recv执行成功返回接收到的字节数,不成功返回-1,出错号放系统变量errno中.
send执行成功返回发送出的字节数,不成功返回-1,出错号放系统变量errno中.
(4)关闭连接当完成数据通信后需要使用close系统调用关闭套接口,以便断开网络的连接.
Close系统调用的语法:#includeintclose(intsocket);10.
3示例实验示例实验程序要实现一个两个远程客户端程序通过远程服务器完成类似单机中两进程双向管道通信的操作.
两个客户端程序同时向服务器发送数据,服务器在收到两个客户机发来的数据后,经简单的处理后再将数据交叉发回两客户机,从而实现两客户程序的信息交换.
示例程序中服务器的IP地址采用本机回送地址127.
0.
0.
1,这样我们仅在同一台机器上就可以完成我们的实验.
1)在新建文件夹中建立以下server.
c文件/**Filename:server.
c*copyright:(C)byzhonghonglie*Function:信息交换服务器.
*/#include#include#include#include#include#include#include#include#defineBUFSIZE16384charbuf[2][BUFSIZE];//收发数据缓冲区实验十、分布式系统实验第96页intserver_socket;//服务器套接口intport=8088;//服务端口号structsockaddr_inserver_in;//服务器网络地址intclient_socket[2];//客户端套接口socklen_tclient_size;//客户端网络地址长度structsockaddr_inclient_in;//客户端网络地址intmain(intargc,char*argv[]){inti,len[2];//申请TCP/IP协议的套接口if((server_socket=socket(AF_INET,SOCK_STREAM,0))==-1){perror("calltosocket");exit(1);}//初始化IP地址bzero(&server_in,sizeof(server_in));//清0server_in.
sin_family=AF_INET;server_in.
sin_addr.
s_addr=INADDR_ANY;server_in.
sin_port=htons(port);//套接口捆定IP地址if(bind(server_socket,(structsockaddr*)&server_in,sizeof(server_in))==-1){perror("calltobind");exit(1);}//监听网络请求if(listen(server_socket,200)==-1){perror("calltolisten");exit(1);}//循环等待接收信息和处理信息printf("Acceptingconnections.
.
.
\n");while(1){//接收第一客户请求if((client_socket[0]=accept(server_socket,(structsockaddr*)&client_in,&client_size))==-1){perror("calltoaccept1");exit(1);}//接收第二客户请求第二部分操作系统算法实验第97页if((client_socket[1]=accept(server_socket,(structsockaddr*)&client_in,&client_size))==-1){perror("calltoaccept2");exit(1);}//获取第一客户发送的数据if(recv(client_socket[0],buf[0],BUFSIZE,00)==-1){perror("calltorecv1");exit(1);}printf("receivedfromclient1:%s\n",buf[0]);//获取第二客户发送的数据if(recv(client_socket[1],buf[1],BUFSIZE,0)==-1){perror("calltorecvv2");exit(1);}printf("receivedfromclient2:%s\n",buf[1]);//都转换为大写len[0]=strlen(buf[0]);for(i=0;i#include#include#include#include#include#include#include#defineBUFSIZE16384charbuf[BUFSIZE];//收发数据缓冲区//要连接的服务器的IP地址为本机回送地址char*host_name="127.
0.
0.
1";intport=8088;//服务端口号intserver_socket;structsockaddr_inserver_in;structhostent*server_name;intmain(intargc,char*argv[]){intrate;charstr[64]="ok";//可以在命令行第二参数指定客户发送的字符穿,第三参数指定一个延迟秒数if(argc==3){rate=atoi(argv[2]);strcpy(str,argv[1]);}elseif(argc==2){rate=1;strcpy(str,argv[1]);}elserate=1;//转换服务器域名或IP地址为IP结构体if((server_name=gethostbyname(host_name))==0){perror("Errorresolvinglocalhost\n");exit(1);}//初始化IP地址结构bzero(&server_in,sizeof(server_in));server_in.
sin_family=AF_INET;第二部分操作系统算法实验第99页server_in.
sin_addr.
s_addr=htonl(INADDR_ANY);server_in.
sin_addr.
s_addr=((structin_addr*)(server_name->h_addr))->s_addr;server_in.
sin_port=htons(port);while(1){//获取远程服务器套接口描述符if((server_socket=socket(AF_INET,SOCK_STREAM,0))==-1){perror("calltosocket");exit(1);}//发送连接请求到到服务器if(connect(server_socket,(void*)&server_in,sizeof(server_in))==-1){perror("calltoconnect");exit(1);}//发送数据到服务器if(send(server_socket,str,strlen(str),0)==-1){perror("errorinsend\n");exit(1);}printf("Sendingmessage:%s\n",str);//从服务器接收数据if(recv(server_socket,buf,BUFSIZE,0)==-1){perror("errorinrecv");exit(1);}printf("ResponefromServer:%s\n",buf);sleep(rate);//断开连接close(server_socket);}}3)在新建文件夹中建立以下Makefile文件s_src=server.
cs_obj=server.
oc_src=client.
cc_obj=client.
oopts=-g-call:serverclientserver:$(s_obj)gcc$(s_obj)-oserver实验十、分布式系统实验第100页server.
o:$(s_src)gcc$(opts)$(s_src)client:$(c_obj)gcc$(c_obj)-oclientclient.
o:$(c_src)gcc$(opts)$(c_src)clean:rmserverclient*.
o4)使用make命令编译生成可执行的sever和client程序$makegcc-g-cserver.
cgccserver.
o-oservergcc-g-cclient.
cgccclient.
o-oclient5)在当前目录中启动服务程序$.
/serverAcceptingconnections.
.
.
以上输出信息表示此时服务器已开始工作,准备接收客户机的请求.
6)打开一个新的终端窗体进入当前目录启动第一个客户端程序$.
/clientfirstSendingmessage:first可以看到由于现在只有一个客户所以它在等待第二个客户通过服务器发回的数据.
7)现在再打开一个新的终端窗体进入当前目录启动第二个客户端程序$.
/clientsecondSendingmessage:secondResponefromServer:FIRSTSendingmessage:secondResponefromServer:FIRST.
.
.
.
.
.
现在因为服务器同时收到了两个客户的数据,它可以将第一个客户发来的字母数据转换为大写转发给第二个客户,将第二个客户发来的字母数据转换为大写转发给第一个客户.
两个客户程序在收到服务器发回的数据后也继续向服务器发送数据.
在第一客户终端看到:Sendingmessage:firstResponefromServer:SECONDSendingmessage:firstResponefromServer:SECOND第二部分操作系统算法实验第101页Sendingmessage:firstResponefromServer:SECOND.
.
.
.
.
.
在服务器窗体中此时可以看到它接收和发送的字符串:receivedfromclient1:firstreceivedfromclient2:secondsendtoclient1:SECONDsendtoclient2:FIRSTreceivedfromclient1:firstreceivedfromclient2:secondsendtoclient1:SECONDsendtoclient2:FIRST.
.
.
.
.
.
10.
4独立实验请利用服务器/客户机网络计算模式,完成实验2独立实验中提出的计算任务.
例如令服务器负责分派计算任务给3个客户机,3个客户机一个负责计算n的阶乘,一个负责计算fibonacc序列,一个负责将另外两个客户机计算的结果加起来返回给用户,从而实现一个网上的分布式计算实验.
10.
5实验要求根据示例实验和独立实验,分析服务器/客户机网络计算模式的特点.
说明服务器/客户机基本工作步骤有那些它们是怎样建立连接和交换数据的它们反应出教材中介绍的分布式系统哪些原理和概念您是怎样利用服务器/客户机网络计算模式实现一个网上的分布式计算实验的.
根据实验程序、调试过程和结果分析写出实验报告.
附录A操作系统原理实验建议第102页附录A操作系统原理实验建议A.
1认真阅读操作系统原理教程在开始我们每项算法实验之前应认真温习操作系统教材相关章节讲授的算法原理以及认真阅读实验说明和示例程序.
在理解了示例程序的基本思路后再结合算法原理展开我们的实验和分析.
A.
2实验时间安排可以考虑以下的顺序和时间开展实验,其中不包括独立实验的设计时间.
提速啦的来历提速啦是 网站 本着“良心 便宜 稳定”的初衷 为小白用户避免被坑 由赣州王成璟网络科技有限公司旗下赣州提速啦网络科技有限公司运营 投资1000万人民币 在美国Cera 香港CTG 香港Cera 国内 杭州 宿迁 浙江 赣州 南昌 大连 辽宁 扬州 等地区建立数据中心 正规持有IDC ISP CDN 云牌照 公司。公司购买产品支持3天内退款 超过3天步退款政策。提速啦的市场定位提速啦主...
在前面的文章中就有介绍到半月湾Half Moon Bay Cloud服务商有提供洛杉矶DC5数据中心云服务器,这个堪比我们可能熟悉的某服务商,如果我们有用过的话会发现这个服务商的价格比较贵,而且一直缺货。这里,于是半月湾服务商看到机会来了,于是有新增同机房的CN2 GIA优化线路。在之前的文章中介绍到Half Moon Bay Cloud DC5机房且进行过测评。这次的变化是从原来基础的年付49....
今天中午的时候有网友联系到在选择网站域名建站和主机的时候问到域名和IP地址有没有关联,或者需要注意的问题。毕竟我们在需要建站的时候,我们需要选择网站域名和主机,而主机有虚拟主机,包括共享和独立IP,同时还有云服务器、独立服务器、站群服务器等形式。通过这篇文章,简单的梳理关于网站域名和IP之间的关系。第一、什么是域名所谓网站域名,就是我们看到的类似"www.laozuo.org",我们可以通过直接记...