合操作系统实验指导书612.docx

上传人:b****6 文档编号:7078011 上传时间:2023-01-17 格式:DOCX 页数:38 大小:536.83KB
下载 相关 举报
合操作系统实验指导书612.docx_第1页
第1页 / 共38页
合操作系统实验指导书612.docx_第2页
第2页 / 共38页
合操作系统实验指导书612.docx_第3页
第3页 / 共38页
合操作系统实验指导书612.docx_第4页
第4页 / 共38页
合操作系统实验指导书612.docx_第5页
第5页 / 共38页
点击查看更多>>
下载资源
资源描述

合操作系统实验指导书612.docx

《合操作系统实验指导书612.docx》由会员分享,可在线阅读,更多相关《合操作系统实验指导书612.docx(38页珍藏版)》请在冰豆网上搜索。

合操作系统实验指导书612.docx

合操作系统实验指导书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)是一个格式化的可变长的信息

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 幼儿教育 > 幼儿读物

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1