合操作系统实验指导书612.docx
《合操作系统实验指导书612.docx》由会员分享,可在线阅读,更多相关《合操作系统实验指导书612.docx(38页珍藏版)》请在冰豆网上搜索。
合操作系统实验指导书612
《操作系统》实验指导书
操作系统课程组◎编著
计算机科学与技术系
目录
前言-1-
实验项目一熟悉LINUX基本命令及编程环境-2-
实验项目二进程管理-4-
实验项目三进程调度-9-
实验项目四进程通信-15-
实验项目五存储管理-24-
实验项目六Shell程序设计-30-
前言
《操作系统》是计算机本科各专业的专业核心课程,其实践性、应用性很强,实验教学环节是必不可少的一个重要环节。
通过《操作系统》实验部分教学,使学生加深理解和更好的掌握操作系统的基本原理、技术和方法,巩固所学理论知识,激发实验兴趣,掌握实验要领,培养对操作系统理论课程所学知识融会贯通和综合运用的能力。
通过实验,使学生深入了解和熟练掌握Linux操作系统的使用,及在Linux操作系统下进行程序设计开发的方法,掌握操作系统中进程管理、进程调度、进程通信和存储管理的方法,使学生具有初步分析实际操作系统的能力,为今后学习使用其它的程序设计环境和语言打好基础。
为了收到良好的实验效果,编写这本实验指导书。
在指导书中,每一个实验均按照该课程实验大纲的要求编写,力求紧扣理论知识点、突出设计方法、明确设计思路,通过多种形式完成实验任务,最终引导学生有目的、有方向地完成实验任务,得出实验结果。
实验前,指导教师布置实验任务,给定实验内容,进行一定的分析和讲解,学生进行预习,提前设计实验方案,之后进入实验室进行实验;实验中,要求学生按照实验要求进行实验,认真完成每个实验项目的具体内容,指导教师全程指导协调实验进行,对于实验中学生所提问题进行具体解答;实验后,学生应当及时总结实验过程,并按照实际情况对实验报告进行填写,能对在实验过程中发生的问题及时分析并找到解决方案,提交实验报告;指导教师需要对实验报告进行认真批阅,并根据需要选取重点内容进行点评分析。
实验项目一熟悉LINUX基本命令及编程环境
1、实验类型
本实验为验证性实验。
2、实验目的与任务
1.熟悉Linux操作系统的安装和使用;熟悉使用Linux字符界面,窗口系统的常用命令。
2.掌握运用Linux常用的编程工具;掌握如何编辑、编译、运行程序。
3、实验准备
1.熟悉linux系统中常用命令及其功能
2.熟悉vi编辑器或Gedit编辑器的各项功能
3.复习C语言程序的编写。
4、实验内容
1.练习使用Gedit编辑器
使用Gedit编辑器用C语言编写一个HelloWorld程序,并保存。
具体操作:
点击“任务栏→位置→主文件夹”,打开主文件夹位置文件浏览器,空白处右键单击,弹出菜单选择“创建文档→空文件”,新建一个空文件,并命名为“hello.c”,右键单击“hello.c”,选择“使用Gedit打开”,在Gedit编辑器中编辑代码如下:
#include
intmain()
{
printf("Hello,Wrold!
\n");
}
编辑完成后,点击“保存”,保存文件。
#include
voidmain(){
intx,y;
printf("pleaseinputx:
");
scanf("%d",&x);
if(x<6)
{
y=x-12;
printf("x=%d,y=%d\n",x,y);
}
elseif(x<15)
{
y=3*x-1;
printf("x=%d,y=%d\n",x,y);
}
else
{
y=5*x+9;
printf("x=%d,y=%d\n",x,y);
}
}
2.使用gcc编译源程序。
gcc是linux下的一种c程序编译工具,使用方法如下:
编译:
gcc-ofilename1filename.c(或者gccfilename.c-ofilename1),其中:
filename.c是源文件名,filename1是目标文件名,o代表object
具体操作:
点击“任务栏→应用程序→附件→终端”,当前默认路径即为主文件夹,输入“gcchello.c-ohello”,回车运行后,若无任何提示,怎说明编译成功,已生成可执行文件“hello“,若提示有错误,则根据具体提示回到Gedit中修改源程序,保存后重新编译.
3.执行程序
执行:
./filenamel其中:
filename1是目标文件名。
具体操作:
在“终端”中输入“./hello”,回车后运行,若无错误,终端中将显示运行结果“Hello,Wrold!
”。
5、注意事项
1.gcc编译器不能编译不带扩展名的c语言程序。
2.注意编译和运行程序的基本过程。
3.重新编辑源程序后,必须重新编译,才会生成新的可执行程序。
实验项目二进程管理
1、实验类型
本实验为验证性实验。
2、实验目的
1.理解进程的概念,掌握父、子进程创建的方法。
2.认识和了解并发执行的实质,掌握进程的并发及同步操作。
3、实验预备知识
1.fork()函数
头文件:
#include
#include
函数原型:
pid_tfork( void);
(pid_t是一个宏定义,其实质是int,被定义在#include中)
返回值:
若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1
函数说明:
一个现有进程可以调用fork函数创建一个新进程。
由fork创建的新进程被称为子进程(childprocess)。
fork函数被调用一次但返回两次。
两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。
注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。
linux将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。
为什么fork会返回两次?
由于在复制时复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。
因为fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的,过程如图2.1。
调用fork之后,数据、堆栈有两份,代码仍然为一份但是这个代码段成为两个进程的共享代码段都从fork函数中返回,箭头表示各自的执行处。
当父子进程有一个想要修改代码段时,两个进程真正分裂。
图2.1fork()函数分裂示意图
示例代码:
#include//对于此程序而言此头文件用不到
#include
#include
int main(int argc, char **argv)
{
int pid=fork();
if (pid<0)
printf("error!
");
else if(pid==0)
printf("Thisisthechildprocess!
");
else
printf("Thisistheparentprocess!
childprocessid=%d",pid);
return 0;
}
fork函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,在父进程和子进程中各返回一次。
fork的另一个特性是所有由父进程打开的描述符都被复制到子进程中。
父、子进程中相同编号的文件描述符在内核中指向同一个file结构体,也就是说,file结构体的引用计数要增加。
2.wait()函数
头文件:
#include
#include
函数原型:
pid_twait(int*status);
返回值:
如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。
函数说明:
wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。
如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。
子进程的结束状态值会由参数status返回,而子进程的进程识别码也会一起返回。
如果不在意结束状态值,则参数status可以设成NULL。
子进程的结束状态值请参考下面的waitpid()。
示例代码:
#include
#include
#include
#include
intmain(int argc, char **argv)
{
pid_tpid;
intstatus,i;
if(fork()==0)
{
printf("Thisisthechildprocess.pid=%d\n",getpid());
exit(5);
}
else
{
sleep
(1);
printf("Thisistheparentprocess,waitforchild...\n");
pid=wait(&status);
i=WEXITSTATUS(status);
printf("child’spid=%d.exitstatus=%d\n",pid,i);
}
}
3.waitpid()函数
头文件:
#include
#include
函数原型:
pid_twaitpid(pid_tpid,int*status,intoptions);
返回值:
如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。
函数说明:
waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。
如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。
子进程的结束状态值会由参数status返回,而子进程的进程识别码也会一快返回。
如果不在意结束状态值,则参数status可以设成NULL。
参数pid为欲等待的子进程识别码,其他数值意义如下:
pid<-1等待进程组识别码为pid 绝对值的任何子进程。
pid=-1等待任何子进程,相当于wait()。
pid=0等待进程组识别码与目前进程相同的任何子进程。
pid>0等待任何子进程识别码为pid的子进程。
参数option可以为0或下面的组合:
WNOHANG如果没有任何已经结束的子进程则马上返回,不予以等待。
WUNTRACED如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
子进程的结束状态返回后存于status,底下有几个宏可判别结束情况:
WIFEXITED(status)如果子进程正常结束则为非0值。
WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED来判断是否正常结束才能使用此宏。
WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真。
WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED来判断后才使用此宏。
WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。
一般只有使用WUNTRACED时才会有此情况。
WSTOPSIG(status)取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED来判断后才使用此宏。
4.exit()函数
头文件:
#include
函数原型:
voidexit(intstatus);
返回值:
无。
函数说明:
进程结束正常终止,返回结束状态。
status为进程结束状态,是返回给父进程的一个整数,以备查考。
为了及时回收进程所占用的资源并减少父进程的干预,UNIX/LINUX利用exit()来实现进程的自我终止,通常父进程在创建子进程时,应在进程的末尾安排一条exit(),使子进程自我终止。
exit(0)表示进程正常终止,exit
(1)表示进程运行有错,异常终止。
如果调用进程在执行exit()时,其父进程正在等待它的终止,则父进程可立即得到其返回的整数。
核心须为exit()完成以下操作:
(1)关闭软中断
(2)回收资源
(3)写记帐信息
(4)置进程为“僵死状态”
4、实验内容
1.编写一C语言程序,实现在程序运行时通过系统调用fork()创建两个子进程,使父、子三进程并发执行,父亲进程执行时屏幕显示“Iamfather”,儿子进程执行时屏幕显示“Iamson”,女儿进程执行时屏幕显示“Iamdaughter”。
#include
#include
#include
#include
intmain()
{
intpid;
printf("forkprogramstarring\n");
pid=fork();
if(pid==0)
printf("son\n");
else
{
pid=fork();
if(pid==0)
printf("daughter\n");
else
printf("father\n");
}
}
2.多次连续反复运行这个程序,观察屏幕显示结果的顺序,直至出现不一样的情况为止。
记下这种情况,试简单分析其原因。
3.修改程序,在父、子进程中分别使用wait()、exit()等系统调用“实现”其同步推进,并获取子进程的ID号及结束状态值。
多次反复运行改进后的程序,观察并记录运行结果。
5、实验报告要求
1.列出实验内容1、3各程序清单,并以截图形式记录相应运行结果。
2.对实验运行结果进行分析:
1)实验内容1运行结果为什么无固定顺序,fork()函数创建进程是如何并发执行的。
2)实验内容3是如何实现父子进程的同步执行的。
#include
#include
#include
intmain(){
intn;
char*message;
intpid,status,i;
printf("forkprogramstarring\n");
pid=fork();
if(pid==0)
{
message="son";
n=3;
}
else
{
pid=fork();
if(pid==0){
message="daughter";
n=3;
}
else{
message="father";
n=3;
}
for(;n>0;n--){
puts(message);
sleep
(1);
}
}
}
实验项目三进程调度
1、实验类型
本实验为验证性实验。
2、实验目的
1.理解进程控制块和进程组织方式;
2.掌握时间片轮转调度算法实现处理机调度。
3、实验预备知识
1.实验基本原理
进程控制块通过链表队列的方式组织起来,系统中存在运行队列和就绪队列(为简单起见,不设阻塞队列),进程的调度就是进程控制块在运行队列和就绪队列之间的切换。
当需要调度时,从就绪队列中挑选一个进程占用处理机,即从就绪队列中删除一个进程,插入到运行队列中,当占用处理机的进程运行的时间片完成后,放弃处理机,即在运行队列中的进程控制块经过一段时间(时间片)后,从该队列上删除,如果该进程运行完毕,则删除该进程(节点);否则,则插入到就绪队列中。
2.实验中使用的数据结构
(1)PCB进程控制块
内容包括参数①进程名name;②要求运行时间runtime;③已运行时间runedtime;④本轮运行时间killtime。
(2)进程队列
为简单起见,只设运行队列,就绪队列两种数据结构,进程的调度在这两个队列中切换,如图3.1所示。
图3.1PCB链表
3.rand()函数和srand()函数
库函数中系统提供了两个函数用于产生随机数:
srand()和rand()。
函数一:
intrand(void);
从srand(seed)中指定的seed开始,返回一个[0,RAND_MAX(0x7fff)]间的随机整数。
函数二:
voidsrand(unsignedseed);
参数seed是rand()的种子,用来初始化rand()的起始值。
函数rand()是真正的随机数生成器,而srand()会设置供rand()使用的随机数种子。
如果你在第一次调用rand()之前没有调用srand(),那么系统会为你自动调用srand()。
而使用同种子相同的数调用srand()会导致相同的随机数序列被生成。
srand((unsigned)time(NULL))则使用系统定时/计数器的值做为随机种子。
每个种子对应一组根据算法预先生成的随机数,所以,在相同的平台环境下,不同时间产生的随机数会是不同的,相应的,若将srand(unsigned)time(NULL)改为srand(TP)(TP为任一常量),则无论何时运行、运行多少次得到的“随机数”都会是一组固定的序列,因此srand生成的随机数是伪随机数。
但是,要注意的是所谓的“伪随机数”指的并不是假的随机数。
其实绝对的随机数只是一种理想状态的随机数,计算机只能生成相对的随机数即伪随机数。
计算机生成的伪随机数既是随机的又是有规律的——一部份遵守一定的规律,一部份则不遵守任何规律。
比如“世上没有两片形状完全相同的树叶”,这体现到了事物的特性——差异性;但是每种树的叶子都有近似的形状,这正是事物的共性——规律性。
从这个角度讲,我们就可以接受这样的事实了:
计算机只能产生伪随机数而不是绝对的随机数。
系统在调用rand()之前都会自动调用srand(),如果用户在rand()之前曾调用过srand()给参数seed指定了一个值,那么rand()就会将seed的值作为产生伪随机数的初始值;而如果用户在rand()前没有调用过srand(),那么系统默认将1作为伪随机数的初始值。
如果给了一个定值,那么每次rand()产生的随机数序列都是一样的。
所以为了避免上述情况的发生我们通常用srand((unsigned)time(0))或者srand((unsigned)time(NULL))来产生种子。
如果仍然觉得时间间隔太小,可以在(unsigned)time(0)或者(unsigned)time(NULL)后面乘上某个合适的整数。
例如,srand((unsigned)time(NULL)*10)。
另外,关于time_ttime(0):
time_t被定义为长整型,它返回从1970年1月1日零时零分零秒到目前为止所经过的时间,单位为秒。
srand()、rand()用法举例:
#include
#include
voidmain()
{
inti,j;
srand(10);//srand((int)time(0));
for(i=0;i<10;i++)
{
j=(int)(rand())%20;
printf("%d\n",j);
}
}
4.malloc()函数
头文件:
#include
函数原型:
void*malloc(unsignedintsize);
函数说明:
其作用是在内存的动态存储区中分配一个长度为size的连续空间,此函数的值(即“返回值”)是一个指向分配域其实地址的指针(类型为void)。
若此函数未能成功执行则返回空指针。
5.程序流程图
图3.2模拟进程调度的流程图
6.部分参考程序
(1)PCB数据结构
structPCB
{
intname;
intruntime;
intrunedtime;
intkilltime;
structPCB*next;
};
typedefstructPCBPCB;
(2)创建就绪列表
#defineLENsizeof(PCB)
PCB*runqueue;//运行队列指针
PCB*top,*tail,*temp;//就绪队列指针
inti;
srand((int)time(0));
for(i=0;i{
temp=(PCB*)malloc(LEN);
temp->name=i;
temp->runtime=rand()%15;
temp->runedtime=0;
temp->next=NULL;
temp->killtime=0;
if(i==0)
{
top=temp;
tail=temp;
}
else
{
tail->next=temp;
tail=temp;
}
printf("processname%d,runtime=%d,runedtime=%d,killtime=%d\n",tail->name,tail->runtime,tail->runedtime,tail->killtime);
}
7.按时间片轮转算法进行进程调度的过程描述。
第1步:
取就绪队列的队首结点为运行队列的结点,修改就绪队列队首指针后移;
第2步:
调度运行队列结点,即运行队列结点的要求运行时间减去时间片时间;
第3步:
a.若修改后要求运行时间<=0,则表示该进程结点运行完毕,修改进程结点的PCB信息,记录runtime,runedtime,killtime等信息。
并将结点信息输出。
b.否则,表示该进程结点未完成,记录runtime,runedtime,killtime等信息,将结点信息输出。
并将该结点置于就绪队列的队尾,等待下次调度,同时修改队尾指针。
第4步:
若就绪队列非空,则继续执行第1步,直至就绪队列为空。
4、实验内容
1.建立合理的PCB数据结构,建立含有8个进程结点的就绪队列,每个进程的要求运行时间随机产生,要求每个进程的要求运行时间不大于15。
2.设置时间片大小(3~6),使用时间片轮转调度算法实现处理机调度。
5、实验报告要求
1.列出实验内容所要求的程序清单,并以截图形式记录相应运行结果;
2.对实验运行结果进行分析:
如果时间片设置值过大或过小,会对进程的调度产生何种影响。
实验项目四进程通信
1、实验类型
本实验为综合性实验。
2、实验目的
1.了解什么是消息,熟悉消息传送原理。
2.了解和熟悉共享存储机制。
3.掌握消息的发送与接收的实现方法。
3、实验预备知识
任务一消息的发送和接收
1.实验基本原理
消息(message)是一个格式化的可变长的信息