合操作系统实验指导书612Word格式文档下载.docx
- 文档编号:20170048
- 上传时间:2023-01-17
- 格式:DOCX
- 页数:38
- 大小:536.83KB
合操作系统实验指导书612Word格式文档下载.docx
《合操作系统实验指导书612Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《合操作系统实验指导书612Word格式文档下载.docx(38页珍藏版)》请在冰豆网上搜索。
具体操作:
点击“任务栏→位置→主文件夹”,打开主文件夹位置文件浏览器,空白处右键单击,弹出菜单选择“创建文档→空文件”,新建一个空文件,并命名为“hello.c”,右键单击“hello.c”,选择“使用Gedit打开”,在Gedit编辑器中编辑代码如下:
#include<
stdio.h>
intmain()
{
printf("
Hello,Wrold!
\n"
);
}
编辑完成后,点击“保存”,保存文件。
voidmain(){
intx,y;
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("
}
else
{
y=5*x+9;
printf("
}
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.重新编辑源程序后,必须重新编译,才会生成新的可执行程序。
实验项目二进程管理
2、实验目的
1.理解进程的概念,掌握父、子进程创建的方法。
2.认识和了解并发执行的实质,掌握进程的并发及同步操作。
3、实验预备知识
1.fork()函数
头文件:
unistd.h>
sys/types.h>
函数原型:
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()函数分裂示意图
示例代码:
//对于此程序而言此头文件用不到
int
main(int
argc,
char
**argv)
pid=fork();
if
(pid<
0)
printf("
error!
else
if(pid==0)
Thisisthechildprocess!
else
Thisistheparentprocess!
childprocessid=%d"
pid);
return
0;
fork函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,在父进程和子进程中各返回一次。
fork的另一个特性是所有由父进程打开的描述符都被复制到子进程中。
父、子进程中相同编号的文件描述符在内核中指向同一个file结构体,也就是说,file结构体的引用计数要增加。
2.wait()函数
sys/wait.h>
pid_twait(int*status);
如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。
wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。
如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。
子进程的结束状态值会由参数status返回,而子进程的进程识别码也会一起返回。
如果不在意结束状态值,则参数status可以设成NULL。
子进程的结束状态值请参考下面的waitpid()。
stdlib.h>
intmain(int
**argv)
pid_tpid;
intstatus,i;
if(fork()==0)
Thisisthechildprocess.pid=%d\n"
,getpid());
exit(5);
else
sleep
(1);
Thisistheparentprocess,waitforchild...\n"
);
pid=wait(&
status);
i=WEXITSTATUS(status);
child’spid=%d.exitstatus=%d\n"
,pid,i);
3.waitpid()函数
pid_twaitpid(pid_tpid,int*status,intoptions);
waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。
子进程的结束状态值会由参数status返回,而子进程的进程识别码也会一快返回。
参数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()函数
stdlib.h>
voidexit(intstatus);
无。
进程结束正常终止,返回结束状态。
status为进程结束状态,是返回给父进程的一个整数,以备查考。
为了及时回收进程所占用的资源并减少父进程的干预,UNIX/LINUX利用exit()来实现进程的自我终止,通常父进程在创建子进程时,应在进程的末尾安排一条exit(),使子进程自我终止。
exit(0)表示进程正常终止,exit
(1)表示进程运行有错,异常终止。
如果调用进程在执行exit()时,其父进程正在等待它的终止,则父进程可立即得到其返回的整数。
核心须为exit()完成以下操作:
(1)关闭软中断
(2)回收资源
(3)写记帐信息
(4)置进程为“僵死状态”
1.编写一C语言程序,实现在程序运行时通过系统调用fork()创建两个子进程,使父、子三进程并发执行,父亲进程执行时屏幕显示“Iamfather”,儿子进程执行时屏幕显示“Iamson”,女儿进程执行时屏幕显示“Iamdaughter”。
intpid;
forkprogramstarring\n"
pid=fork();
if(pid==0)
son\n"
else
{
pid=fork();
if(pid==0)
daughter\n"
else
father\n"
2.多次连续反复运行这个程序,观察屏幕显示结果的顺序,直至出现不一样的情况为止。
记下这种情况,试简单分析其原因。
3.修改程序,在父、子进程中分别使用wait()、exit()等系统调用“实现”其同步推进,并获取子进程的ID号及结束状态值。
多次反复运行改进后的程序,观察并记录运行结果。
5、实验报告要求
1.列出实验内容1、3各程序清单,并以截图形式记录相应运行结果。
2.对实验运行结果进行分析:
1)实验内容1运行结果为什么无固定顺序,fork()函数创建进程是如何并发执行的。
2)实验内容3是如何实现父子进程的同步执行的。
intmain(){
intn;
char*message;
intpid,status,i;
if(pid==0)
message="
son"
;
n=3;
}
if(pid==0){
message="
daughter"
n=3;
else{
father"
for(;
n>
n--){
puts(message);
实验项目三进程调度
1.理解进程控制块和进程组织方式;
2.掌握时间片轮转调度算法实现处理机调度。
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()用法举例:
time.h>
voidmain()
inti,j;
srand(10);
//srand((int)time(0));
for(i=0;
i<
10;
i++)
j=(int)(rand())%20;
%d\n"
j);
4.malloc()函数
malloc.h>
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<
NUM;
i++)
temp=(PCB*)malloc(LEN);
temp->
name=i;
runtime=rand()%15;
runedtime=0;
next=NULL;
killtime=0;
if(i==0)
top=temp;
tail=temp;
tail->
next=temp;
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步,直至就绪队列为空。
1.建立合理的PCB数据结构,建立含有8个进程结点的就绪队列,每个进程的要求运行时间随机产生,要求每个进程的要求运行时间不大于15。
2.设置时间片大小(3~6),使用时间片轮转调度算法实现处理机调度。
1.列出实验内容所要求的程序清单,并以截图形式记录相应运行结果;
如果时间片设置值过大或过小,会对进程的调度产生何种影响。
实验项目四进程通信
本实验为综合性实验。
1.了解什么是消息,熟悉消息传送原理。
2.了解和熟悉共享存储机制。
3.掌握消息的发送与接收的实现方法。
任务一消息的发送和接收
消息(message)是一个格式化的可变长的信息
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 操作系统 实验 指导书 612