操作系统课程设计实验报告以Linux为例.docx
《操作系统课程设计实验报告以Linux为例.docx》由会员分享,可在线阅读,更多相关《操作系统课程设计实验报告以Linux为例.docx(16页珍藏版)》请在冰豆网上搜索。
操作系统课程设计实验报告以Linux为例
《操作系统课程设计》
实验报告
学号:
姓名:
苏州大学计算机科学与技术学院
2014年9月
目 录
一、实验环境
Linux平台
◆硬件平台:
普通PC机硬件环境。
◆操作系统:
Linux环境,例如,红旗Linux或RedHatLinux;启动管理器使用GRUB。
◆编译环境:
伴随着操作系统的默认gcc环境。
◆工作源码环境:
一个调试的内核源码,版本不低于2.4.20。
二、实验报告总体要求
在2013年11月25日前提交实验报告。
实验报告至少要求包含以下内容:
1.引言:
概述本次实验所讨论的问题,工作步骤,结果,以及发现的意义。
2.问题提出:
叙述本篇报告要解决什么问题。
注意不可以抄写实验要求中的表述,要用自己的话重新组织我们这里所提出的问题。
3.解决方案:
叙述如何解决自己上面提出的问题,可以用小标题3.1,3.2…等分开。
这是实验报告的关键部分,请尽量展开来写。
注意,这部分是最终课程设计的基本分的部分。
这部分不完成,本课程设计不会及格。
4.实验结果:
按照自己的解决方案,有哪些结果。
结果有异常吗?
能解释一下这些结果吗?
同别人的结果比较过吗?
注意,这部分是实验报告出彩的地方。
本课程设计要得高分,应该在这部分下功夫。
5.结束语:
小结并叙述本次课程设计的经验、教训、体会、难点、收获、为解决的问题、新的疑惑等。
6.附录:
加了注释的程序清单,注释行数目至少同源程序行数目比1:
2,即10行源程序,至少要给出5行注释。
实验一 编译Linux内核
实验时间
6小时
实验目的
认识Linux内核的组成,掌握配置、编译、安装Linux内核的步骤。
实验目标
下载2.6.19或更新的Linux内核,配置该内核使其支持NTFS,并在新的内核中修改其版本为LinuxNameTestKernelx.x.x,其中,Name是你的名字(汉语拼音);x.x.x是新内核的版本号,最后在你的机器上编译安装这个新内核。
背景知识
参见《RedHatEnterpriseLinux4入门与提高》第20章。
实验步骤
1.验证gcc的可用:
在你自己的工作目录下,编译链接运行HelloWorld程序。
2.在http:
//www.kernel.org上下载指定的内核,或者查找更新的稳定版内核并下载之。
3.准备相关工具。
提示:
如当前运行的Linux内核是基于2.4版本的,则需要更新以下软件:
module-init-tools和mkinitrd。
具体更新信息可参见下载内核源代码中的Documentation/Changes这个文件。
4.把源代码解压缩至/usr/src中,最终形成/usr/src/linuxx.x.x/目录(x.x.x是新内核的版本号)。
提示:
这里的注意点是路径的选择,一般要放在/usr/src/linuxx.x.x/目录下面,以满足Makefile对路径设置的初始要求。
5.进入源代码的根目录(/usr/src/linuxx.x.x),找到合适自己的内核配置方法,并按照实验目标对其进行配置。
6.修改/usr/src/linuxx.x.x/include/linux/version.h文件中的版本信息。
注:
如果没有这个文件,请执行命令:
makeinclude/linux/version.h
7.编译内核。
8.安装模块文件。
9.安装内核文件。
10.重新启动新内核。
实验结果
1.实验步骤1中,编译链接运行程序你下达了哪些命令?
2.实验步骤2中,你下载了哪个版本的内核文件?
3.实验步骤3中,你是否安装了相关工具?
如安装,则写出安装过程。
4.实验步骤4中,你是用哪些命令解压缩内核文件的?
5.实验步骤5中,你用了哪种内核配置的方法?
6.你对实验步骤6中涉及的文件做了怎样的修改?
7.实验步骤7-9的过程,是否出现错误?
如有,你是如何解决的?
8.观察你机器中GRUB的配置文件,它在安装完新内核后发生了哪些变化?
9.新内核启动过程是否成功?
如有错误,是哪些错误?
你是如何消错的?
(如不够,可另附页)
实验报告
实验二 观察Linux行为
实验时间
6小时
实验目的
学习Linux内核、进程、存储和其他资源的一些重要特征。
实验目标
编写一个程序使用/proc机制检查反映机器平均负载、进程资源利用等方面的各种内核值。
在得到内核状态之后,将所观察到的行为在屏幕上输出。
背景知识
Linux、Solaris和其他版本的UNIX提供了一种非常有用的检查内核状态机制,叫做/proc文件系统。
这是可以用来完成本练习的关键机制。
1./proc文件系统
/proc文件系统是一种操作系统机制,它的接口就像传统UNIX文件系统的一个目录(在根目录中)。
可以改变到/proc正如改变到任何其他目录,例如,
bash$cd/proc
使/proc作为当前目录。
一旦把/proc作为当前目录,就可以用ls命令列出它的内容。
其内容看起来像普通的文件和目录。
但是,/proc或者其子目录中的文件实际上是读取内核变量并以ASCII字符串方式报告它们的程序。
这些例程中的一些仅在伪文件打开时读取内核表,而其他例程在每次文件被访问时读表。
因此各种读函数的工作方式可能与预期的有所不同,因为它们并没有在真正操作文件。
Linux提供的/proc实现可以读取很多不同的内核表。
/proc中包含一些目录和文件,每个文件读取一个或多个内核变量。
而具有数字名称的子目录包括更多的伪文件读取其进程ID和目录名相同的进程的有关信息。
self目录包含了正在使用/proc进程的特定进程信息。
/proc目录树的确切内容随Linux的版本而不同,所以必须对伪文件进行实验查看所提示的信息。
/proc中的文件可以像普通的ASCII文件一样进行读取。
例如,向shell敲入以下命令:
bash$cat/proc/version
将得到打印到stdout的类似下面的信息:
Linuxversion2.2.12(gccversionegcs-2.91.66
19990314/Linux(egcs-1.1.2release))#1MonSep2710:
40:
35
EDT1999
为了读取一个伪文件的内容,可以打开文件然后使用stdio程序库中的例程如fgets()或者fscanf()来读取文件。
所读的确切文件(和表)依赖于所使用的特定Linux版本。
想要知道到底有些什么文件接口通过/proc对你可用,请查看系统上的proc使用手册页。
2.使用argc和argv
在程序的B部分和C部分,需要从shell向程序传递参数。
Linux中,C主程序依然可以采用argc和argv来传递参数。
具体用法这里不再赘述。
问题陈述
1.编写一个程序,通过检查内核状态报告Linux内核行为。
程序在stdout上打印以下信息:
●CPU类型和型号。
●内核版本
●从系统最后一次启动以来的时间,形式为dd:
hh:
mm:
ss(例如,3天13小时46分32秒应该写出03:
13:
46:
32)。
2.改写A部分程序,加入命令行参数,在stdout上再打印以下信息:
●CPU花费在用户态、系统态和空闲态的时间。
●系统接收到的磁盘请求。
●内核执行的上下文转换的次数。
●系统最后启动的时间。
●从系统启动开始创建的进程数。
3.改写B部分程序,根据不同的命令行参数,在stdout上再打印以下信息:
●计算机配置的内存数量。
●当前可用的内存数量。
●平均负载列表(至上一分钟的平均数)。
该信息将使另外的程序可以查看各个时间的这些值,因此用户可以了解平均负载如何随时间间隔而变化。
对于本程序,提供两个命令行参数:
(1)一个表明应该以什么样的频率从内核读取平均负载。
(2)一个表明应该以多长的时间间隔读取平均负载。
A部分程序可以叫做ksamp,B部分程序ksamp–s,C部分程序ksamp–l260,表示平均负载观察将运行60秒,每隔2秒取样一次。
为了观察系统上的负载,需要确保计算机正在做一些其他的工作而不是仅仅运行你的程序。
例如,打开和关闭窗口、移动窗口,甚至在其他窗口运行一些程序。
组织方案
对于B部分和C部分,程序必须在命令行上有不同的参数。
因此最先的动作之一应该是解析调用程序的命令行以确定通过argv数组传递到它的shell参数。
可参考如下代码:
intmain(intargc,char*argv[]){
charrepTypeName[16];
…
//决定报告类型
reportType=STANDARD;
strcpy(repTypeName,“Standard”);
if(argc>1){
sscanf(argv[1],“%c%c”,&c1,&c2);
if(c1!
=“-“){
fprintf(stderr,“usage:
observer[-s][-lintdur]\n”);
exit
(1);
}
if(c2==‘s’){
reportType=SHORT;
strcpy(repTypeName,“Short”);
}
if(c2==‘l’){
reportType=LONG;
strcpy(repTypeName,“Long”);
internal=atoi(argv[2]);
duration=atoi(argv[3]);
}
}
…
}
在得到今天的当前时间并打印出一个包含你所检查机器名字的问候之后,就完成了初始化工作。
#include
…
//完成初始化
gettimeofday(&now,NULL);//得到当天时间
printf(“Statusreporttype%sat%s\n”,repTypeName,ctime(&(now.tv_sec)));
//得到主机文件名并打印它
thisPorcFile=fopen(“/proc/sys/kernel/hostname”,“r”);
fgets(lineBuf,LB_SIZE+1,thisPorcFile);
printf(“Machinehostname:
%s”,lineBuf);
fclose(thisPorcFile);
现在可以准备开始工作,也就是说,通过使用各种/proc文件开始读取内核变量。
上述代码段包含一个如何读取/proc/sys/kernel/hostname文件的例子。
可以用它作为原型通过读取其他的伪文件来完成练习,这需要对/proc进行一些探索以及在研究不同的目录时对各种伪文件进行检查。
在C部分中将要计算平均负载。
对于这个问题,代码需要睡眠一段时间,醒来后采样当前平均负载,然后再返回睡眠状态。
这里是一段可以完成该工作的代码:
while(iteration sleep(interval);
sampleLoadAvg();
iteration+=interval;
}
实验步骤
1.在proc文件系统中找出需要读取的文件。
2.设计读取proc文件系统中文件的方法。
3.设计读取某文件指定标识处内容的方法。
4.完成问题陈述A。
5.完成问题陈述B。
6.完成问题陈述C。
实验结果
1.实验步骤1的结果是什么?
2.请描述实验步骤2的流程。
3.请描述实验步骤3的流程。
4.请描述实验步骤4的方案。
5.请描述实验步骤5的方案。
6.请描述实验步骤6的方案。
(如不够,可另附页)
实验报告
实验三 进程间通信
实验时间
6小时
实验目的
初步了解Linux系统中,进程间通信的方法。
实验目标
编写一个程序,用Linux中的IPC机制,完成两个进程“石头、剪子、布”的游戏。
背景知识:
1.Linux中的IPC机制简介
进程间通信(InterprocessCommunication,IPC)实现了进程之间同步和交换数据的功能。
本实验要求完成的是一个或几个用户态的进程,依靠内核提供的进程间通信的机制,完成几个用户进程之间的通信。
通常,在Linux中允许以下几种进程间通信的机制:
管道和命名管道(FIFO)
最适合在进程之间实现生产者/消费者的交互。
有些进程往管道中写入数据,而另外一些进程则从管道中读出数据。
信号量
这就是我们在原理课程中讲述的内核信号量的用户态版本。
消息
允许进程异步的交换消息(小块数据)。
可以认为消息是传递附加信息的信号。
共享内存区
当进程之间在高效的共享大量数据的时候,这是一种最适合实现的交互方式。
另外,Linux也允许相同主机上的进程之间,利用套接字(socket)进行通信。
但套接字引入的最初目的是为了应用程序和网络接口之间实现数据通信,因此在这里就不再介绍。
2.FIFO
POSIX引入了一个名为mkfifo()的系统调用专门用来创建FIFO,mkfifo()的函数原型为:
intmkfifo(constchar*pathname,mode_tmode);
mkfifo()会根据参数pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限。
FIFO一旦被创建,就可以使用普通的open()、read()、write()和close()系统调用进行访问。
mkfifo()函数若成功,则返回0,否则返回-1,错误原因存于errno中。
下面是一段示例代码。
//判断FIFO是否存在,如果不存在则建立
if(access(FIFO_NAME,F_OK)==-1){
res=mkfifo(FIFO_NAME,0777);
if(res!
=0){
fprintf(stderr,"Couldnotcreatefifo%s\n",FIFO_NAME);
exit
(1);
}
}
3.SystemVIPC
SystemVIPC包含一组系统调用,上文提及的利用信号量、消息和共享内存区进行进程间通信,都属于其中。
IPC数据结构是在进程请求IPC资源(信号量、消息队列或共享内存区)时动态创建的。
每个IPC资源都是持久的,除非被进程显式的释放,否则永远驻留在内存中。
由于一个进程可能需要相同类型的多个IPC资源,因此每个新资源都使用一个32位的IPC关键字来标识。
这里重点介绍消息队列通信。
(1)系统调用msgget()
为了创建一个新的消息队列,或者访问一个现有的队列,可以使用系统调用msgget(),其函数原型为:
intmsgget(ket_tkey,intmsgflg);
msgget()的第一个参数是IPC关键字的值。
这个关键字的值将被拿来与内核中其他消息队列的现有值相比较。
比较之后,打开或访问操作依赖于第二个参数的内容。
●IPC_CREAT——如果内核中不存在该队列,则创建它。
●IPC_EXCL——与IPC_CREAT一起使用时,若队列早已存在则将出错。
如果只使用了IPC_CREAT,msgget()或者返回新创建消息队列的标识符,或者返回现有的具有同一个关键字值的队列的标识符。
如果同时使用了IPC_CREAT和IPC_EXCL,那么将可能会有两个结果。
或者创建一个新的队列,或者如果该队列存在,则调用将出错,并返回-1。
IPC_EXCL本身是没有什么用处的,但在与IPC_CREAT组合使用时,它可以用于保证没有一个现存的队列为了访问而被打开。
有个可选的八进制许可模式,它是与掩码进行OR操作以后得到的。
这是因为从功能上讲,每个IPC对象的访问权限与Linux文件系统的文件许可权是相似的。
下面是两段示例代码,说明如何创建一个新的消息队列,以及如何使用一个已有的消息队列。
//建立消息队列,只有相同用户可以使用
queue_id=msgget(QUEUE_ID,IPC_CREAT|IPC_EXCL|0600);
if(queue_id==-1){
perror("main:
msgget");
exit
(1);
}
//取得消息队列ID
queue_id=msgget(QUEUE_ID,0);
if(queue_id==-1){
perror("main:
msgget");
exit
(1);
}
(2)消息缓冲区
这个特殊的数据结构可以认为是消息数据的模板。
虽然定义这种类型的数据结构是程序员的职责,但是读者绝对有必要知道实际上存在msgbuf类型的结构。
在linux/msg.h中,它的定义如下:
/*messagebufferformsgsndandmsgrcvcalls*/
structmsgbuf{
longmtype;
charmtext[1];
};
msgbuf结构中有两个成员:
●mtype——它是消息类型,以正数表示。
●mtext——它就是消息数据。
不要被消息数据元素(mtext)的名称所误导,应用程序的编程人员是可以重新定义msgbuf这个结构的。
对于给定消息的最大的大小,存在一个内部的限制。
在Linux中,它在linux/msg.h中定义。
(3)系统调用msgsnd()
一旦获得了消息队列的标识符,就可以开始在该队列上执行相关操作了。
为了向队列传递消息,可以使用msgsnd()系统调用:
intmsgsnd(intmsqid,structmsgbuf*msgp,size_tmsgsz,intmsgflg);
第一个参数是消息队列的标识符,它是前面调用msgget()获得的返回值。
第二个参数是一个指针,指向我们重新定义和载入的消息缓冲区。
msgsz则包含了消息的大小,它是以字节为单位的,其中不包括消息类型的长度。
msgflg可以设置为0(忽略),也可以设置为IPC_NOWAIT。
如果消息队列已满,则消息将不会被写入到队列中,控制权将被还给调用进程。
如果没有指定IPC_NOWAIT,则调用进程将被阻塞,直到可以写消息为止。
下面是一段示例程序:
//申请消息缓冲区
msg=(structmsgbuf*)malloc(sizeof(structmsgbuf)+MAX_MSG_SIZE);
//发送数据
msg->mtype=1;
sprintf(msg->mtext,"helloworld");
rc=msgsnd(queue_id,msg,strlen(msg->mtext)+1,0);
if(rc==-1){
perror("main:
msgsnd");
exit
(1);
}
(4)系统调用msgrcv()
一旦消息队列中有消息了,进程就可以从队列中获得消息,可以使用系统调用msgrcv()来完成这个功能:
ssize_tmsgrcv(intmsqid,structmsgbuf*msgq,size_tmsgsz,longmsgtype,int
msgflg);
显然,第一个参数是用来指定在消息获取过程中所使用的队列的。
第二个参数代表消息缓冲区变量的地址,获取的消息存放在这里。
第三个参数代表消息缓冲区结构的大小,不包括mtype成员的长度,可以使用下面的公式来计算大小:
msgsz=sizeof(structmymsgbuf)–sizeof(long);
第四个参数指定要从队列中获取的消息的类型。
内核将查找队列中具有匹配类型的最老的消息,并把它的一个拷贝返回到由msgp变量指定的地址中。
如果把IPC_NOWAIT作为一个参数传递给msgflg,而队列中没有任何消息。
则该次调用会向调用进程返回ENOMSG。
否则,调用进程将阻塞,直到满足msgrcv()参数的消息到达队列为止。
下面是一段示例程序:
//申请消息缓冲区
msg=(structmsgbuf*)malloc(sizeof(structmsgbuf)+MAX_MSG_SIZE);
//读取消息队列中的数据
rc=msgrcv(queue_id,msg,MAX_MSG_SIZE+1,msg_type,0);
if(rc==-1){
perror("main:
msgrcv");
exit
(1);
}
实验步骤
本实验可以创建三个进程,其中,一个进程为裁判进程,另外两个进程为选手进程。
可以将“石头、剪子、布”这三招定义为三个整型值。
胜负关系:
石头〉剪子〉布〉石头。
选手进程按照某种策略(例如,随机产生)出招,交给裁判进程判断大小。
裁判进程将对手的出招和胜负结果通知选手。
比赛可以采取多盘(>100盘)定胜负,由裁判宣布最后结果。
每次出招由裁判限定时间,超时判负。
每盘结果可以存放在文件或其他数据结构中。
比赛结束,可以打印每盘的胜负情况和总的结果。
1.设计表示“石头、剪子、布”的数据结构,以及它们之间的大小规则。
2.设计比赛结果的存放方式。
3.选择IPC的方法。
4.根据你所选择的IPC方法,创建对应的IPC资源。
5.完成选手进程。
6.完成裁判进程。
---------------以下要求选作---------------
7.决出班级的前三甲,与另外班级的前三甲比赛,决出年级冠军。
8.如果有兴趣,再把这个实验改造成网络版。
即在设计时就要考虑IPC层的封装。
实验结果
1.请写出实验步骤1的数据结构。
2.请写出实验步骤1的大小规则。
3.请写出实验步骤2的数据结构。
4.实验步骤3中,你所选择的IPC方法是什么?
5.在实验步骤3中你为何选择该方法?
6.实验步骤3,如果选择消息队列机制,请描述消息缓冲区结构。
7.在实验步骤4中,你是如何创建IPC资源的?
8.请描述实验步骤5中程序主要流程或关键算法。
9.请描述实验步骤6中程序主要流程或关键算法。
10.实验部分结果的展示。
(如不够,可另附页)
实验报告