操作系统实验二进程创建.docx
- 文档编号:9593283
- 上传时间:2023-05-20
- 格式:DOCX
- 页数:17
- 大小:28.35KB
操作系统实验二进程创建.docx
《操作系统实验二进程创建.docx》由会员分享,可在线阅读,更多相关《操作系统实验二进程创建.docx(17页珍藏版)》请在冰点文库上搜索。
操作系统实验二进程创建
实验二进程创建
实验学时:
3
实验类型:
(验证)
一、实验目的
1.理解进程概念以及进程和程序的区别;
2.理解进程并发执行的实质:
3.掌握解决进程同步问题的基本方法;
4.培养学生分析和设计程序的能力。
二、实验条件
Linux平台。
三、实验原理及相关知识
1•实验相关知识点
进程的基本概念;进程控制;进程同步;经典进程的同步问题。
2.系统调用
系统调用是一种进入系统空间的办法。
通常,在OS的核心中都设置了一组用于实现各种系统功能的子程序,并将它们提供给程序员使用。
程序员在需要OS提供某种服务的时候,便可以调用一条系统调用命令,去实现希望的功能,这就是系统调用。
因此,系统调用就像一个黑箱子一样,对用户屏蔽了操作系统的具体动作而只是提供了调用功能的接口。
不同的操作系统有各自的系统调用方法。
如windowsAPI,便是windows的系统调用。
Linux的系统调用与之不同的是源于Linux内核代码完全公开,所以可以细致的分析出其系统调用的机制。
3.系统调用和普通函数的区别
(1)运行于不同的系统状态用户程序可以通过系统调用进入系统空间,在核心态执行;而普通函数则只能在用户空间当中运行。
(2)通过软中断切换山于用户程序使用系统调用后要进入系统空间,所以需要调用一个软中断:
而普通函数在被调用时则没有这个过程。
4.系统调用的类型
系统调用的作用与它属在的操作系统有密切关系。
根据操作系统的性质不
同,它们所提供的系统调用会有一定的差异,不过对于普通操作系统而言,应该具有下面儿类系统调用:
(1)进程控制类型;
(2)文件操纵类型;(3)进程通信类型;(4)信息维护类型。
5•系统调用的实现机制
由于操作系统的不同,其系统调用的实现方式可能不一样,然而实现机制应该是大致相同的,一般包含下面儿个步骤:
(1)设置系统调用号,在系统当中,往往设置多条系统调用命令,并赋予每条系统调用命令一个唯一的系统调用号。
根据分配系统调用号方式的不同分为:
直接方式和参数表方式。
(2)处理系统调用
操作系统中有一张系统调用入口表。
表中的每个表目都对应一条系统调用命令,它包含有该系统调用自带参数的数LI、系统调用命令处理程序的入口地址等。
操作系统内核便是根据所输入的系统调用号在该表中查找到相应的系统调用,进而转入它的入口地址去执行它。
6.Linux的系统调用机制
Linux的系统调用是通过中断机制实现的。
中断这个概念涉及到汁算机系统结构方面的知识,显然它与微处理器等硬件有着密不可分的关系。
中断(Interrupt),是指计算机在执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得CPU不得不暂时中断当前正在执行的程序而转去执行相应的事件处理程序,待处理完毕后再返回原来被中断处继续执行的过程。
其发生一般而言是“异步”的,换句话说就是在无法预测的情况下发生的(比如系统掉电)。
所以计算机的软硬件对于中断的相应反应完全是被动的。
软中断,是对硬中断的一种模拟,发送软中断就是向接收进程的proc结构中的相应项发送一个特定意义的信号。
软中断必须等到接收进程执行时才能生效。
陷阱(Trap),即III软件产生的中断,指处理机和内存内部产生的中断,它包括程运算引起的各种错误,如地址非法、校验错误、页面失效等。
它山专门的指令,如X86中的“INTn”,在程序中有意的产生。
所以说陷阱是主动的,
"同步”的。
异常(Exception),一般也是异步的,多半是由于不小心或无意造成的,比如在进行除法操作时除数为0,就会产生一次异常。
7.相关函数
fork()函数用于创建一个新进程(在进程);
调用格式:
intforic();
其中返回int值的取值意义如下:
正确返回:
等于0:
创建子进程,从子进程返回的ID值;
大于0:
从父进程返回子进程的进程ID值;
错误返回:
等于创建失败。
子进程和父进程的调度执行:
子进程被创建后就进入就绪队列和父进程分别独立地等待调度。
子进程继承父进程的程序段代码,子进程被调度执行时,也会和父进程一样从fork()返回。
从共享程序段代码的角度来看,父进程和子进程所执行的程序代码是同一个,在内存中只有一个程序段副本;但是从编程的角度来看,为了使子进程和父进程做不同的事,需要在程序中区分父进程和子进程的代码段。
这就需要借助于从fork()带回的值来标志当前进程身份。
从fork()返回后,都会执行语句:
pid=fork();
得到返回的值pid,有如下儿种惜况:
(1)若pid小于0,则表示fork()出错,相应语句为:
辻(pid<0){
printf(forkerror\n"):
exit(O);}
(2)若pid等于0,则表示当前进程是子进程,继续执行的后面的代码是子进程要做的事,相应语句可写成:
if(pid=0){
printf("Thechildprocessisrunningnow!
\nn):
exit(0):
}
(3)若pid大于0,则表示当前进程是父进程,继续执行的后面的代码是父进程要做的事,相应语句可写成:
if(pid>0){
printf("Theparentprocessisrunningnow!
\n):
exit(0);}
山于父进程和子进程分别独立地进入就绪队列等待调度,所以谁会先得到调度是不确定的,这与系统的调度策略和系统当前的资源状态有关。
因此谁先从fork()返回,继续执行后面的语句也是不确定的。
父进程和子进程的存放及资源共享:
当父进程创建子进程时,首先为子进程分配山task向量数组的指针指向的task_struct(即PCB结构)结构的内存、所需的堆栈、页表等,创建进程标识号(在系统的进程标识号组中是唯一的),并且将其父进程的进程标识号填入task_struct中的家族信息中。
然后将父进程的task_struct的相关内容拷贝到子进程的task.struct中,对一些数据成员进行初始化。
子进程从父进程处继承的资源包括:
真实用户标识号和组标识号、有效用户标识号和组标识号、进程组标识号、对话标识号、控制终端、根U录与当前工作U录、设置用户标识号和设置组标识号记位、信号标识、文件描述符、文件缺省创建权限掩码、可访问的内存区、线程、环境变量及其它资源分配。
wait()函数
wait()函数常用来控制父进程与子进程的同步。
在父进程中调用wait()函数,则父进程被阻塞,进入等待队列,等待子进程结束。
当子进程结束时,会产生一个终止状态字,系统会向父进程发出SIGCHLD信号。
当接到信号后,父进程提取子进程的终止状态字,从wait()函数返回继续执行原程序。
其调用格式为:
#include
#include
(pid_t)wait(int*status);
正确返回:
大于0:
子进程的进程ID值;等于0:
其它。
错误返回:
等于-1:
调用失败。
kill()函数
系统调用格式
intkill(pid,sig)
参数定义
intpid,sig;
其中,pid是一个或一组进程的标识符,参数sig是要发送的软中断信号。
(1)pid>0时,核心将信号发送给ID为pid的进程。
(2)pid=O时,核心将信号发送给与发送进程同个进程组的所有进程。
(3)pid=-I时,核心将信号发送给所有用户标识符真正等于发送进程的有效用户标识号的进程。
signal()
预置对信号的处理方式,允许调用进程控制软中断信号。
系统调用格式
signal(sigjunction)
头文件为:
#include
参数定义
signal(sig,function)
参数:
intsig;void(*func)()
signal()会依参数sig指定的信号编号来设置该信号的处理函数。
当指定信号到达时就会跳转到参数function指定的函数执行。
其中sig用于指定信号的类型,sig为0则表示没有收到任何信号,如下表:
值
名r
说明
01
SIGHUP
挂起(hangup)当终端机察觉到终止连线操作时便会传送这个信号
02
SIGINT
中断.为用戸从键盘按飞键或^break时,则会产生此信号
03
SIGQUIT
退出,当用户从键盘按quit键时.如CTRL+V则会产生此信号
04
SIGILL
非法指令(进程执行了一个不合法徳CPU指令)
05
SIGTRAP
跟踪陷阱(tracetrap),当子进程因被追踪而暂停时产生此信号给父进程
06
SIGIOT
I0T指令
07
SIGBUS
总线错误,BUS发生错误时会产生此信号
08
SIGFPE
浮点运算溢出
09
SIGKILL
杀死.终止进程的信号,此信号不能被拦截或忽略
10
SIGUSR1
用户自泄义信号1
11
SIGSEGV
段违例(segmentationviolation),进程试图去访问其虚地址空间以外的位置
12
SIGUSR2
用户自定义信号2
13
SIGPIPE
错误的管道:
欲写入无读取端的管道时产生此信号
14
SIGALRM
闹钟。
当某进程希望在某时间后接收信号时发此信号
15
SIGTERM
软件终止(softwaretermination)
16
SIGSTKFLT
堆栈错误
17
SIGHLD
子进程暂停或结束时便会产生此信号给父进程
18
SIGC0NT
此信号会让暂停的进程继续执行
19
SIGSTOP
此信号用来让进程暂停执行,此信号不能被拦截或忽略
20
SIGTSTP
当由键盘(CTRL+Z)表示暂停时就产生此信号
function:
该进程中的一个函数地址,在核心返回用户态时,它以软中断信号的序号作为参数调用该函数。
function的解释如下:
(1)function=l(SIG_IGN)W,进程对sig类信号不予理睬,亦即屏蔽了该类信号;
(2)fimction=0(SIG_DFL)时,缺省值,进程在收到sig信号后应终止自己;
(3)function为非0,非1类整数时‘function的值即作为信号处理程序的指针。
pause()函数
让进程暂停(进入睡眠状态)直到被信号(signal)所中断。
waitpid()函数
会暂停H前进程的执行,直到有信号来到或子进程结束。
exit()函数
终止进程的执行。
系统调用格式:
voidexit(status)
intstatus;
其中,status是返回给父进程的一个整数,以备查考。
为了及时回收进程所占用的资源并减少父进程的干预,UNIX/LINUX利用exit()来实现进程的自我终止,通常父进程在创建子进程时,应在进程的末尾安排一条exit(),使子进程自我终止。
exit(0)表示进程正常终止,exit(l)表示进程运行有错,异常终止。
如果调用进程在执行exit()时,其父进程正在等待它的终止,则父进程可立即得到其返回的整数。
核心须为exit()完成以下操作:
(1)关闭软中断;
(2)回收资源;(3)写记帐信息;⑷置进程为“僵死状态”。
四、实验步骤
1.进程的创建
在下面程序中,使用系统调用fork()创建一个子进程。
当此程序运行时,在系统中有一个父进程和一个子进程同步运行,在程序中分别获得父进程和子进程的进程号【使用系统调用getpid()L
include
include
include
intmain()
(
intpid;
pid=fork();
if(pid==O)//子进程
{
printf(HIamchild!
\nN);
prinlfCchildpidis%d\nH.getpid());printf(Hchildexit\nM);
}
elseif(pid>0)〃父进程
{
printf(MIamparent!
\nN);
printf(nparentpidis%d\n,\getpid());printf(uparentexit\nM);
}
else〃创建失败
printf(°forkfaile!
\n°);
}
编译上面程序,观察父进程和子进程的PID号有何关系?
如何区分父进程和子进程?
getpid获得进程号和fork创建返回的PID号有何关系?
【扩展】父子进程的同步
#include
^include
#include
include
intmain()
(
intpid;
pid=fork();if(pid==O)
{
printffTamchild!
\nN);
printf(Hchildpidis%d\nH.getpid());
sleep(10);〃子进程sleep10秒轴
printf(uchildexit\nH);
exit(O);〃退岀
}
elseif(pid>0)
{
printf(nIamparent!
\nN);
printf(Mparcntpidis%d\n'\getpid());
wait(0);〃父进程等待子进程退出
printf(°parentexit\n'*);
}
else
printf(Hforkfaile!
\iiM);printf(Hprogramend!
\nH);
}-
对比该段程序与上一段程序的异同?
如果没有父进程中的wait(0),会出现什么现象?
使用ps-aux查看子进程的状态。
如果将子进程中sleep系统调用改成系统调用pause(),将什么结果?
试实验之。
【如果一个进程休眠,而没有进程通知将导致其一直睡眠下去。
】
2.多进程运行的结果不可重现
#include
#include
#include
intniain()
intval=5;
//child1
if(fork()==0){
val=val+1;
printf(Mnowchild1valueis:
%d\n",val);exit(O);
)
//child2
if(fork()==0)
{
val=val+1;
printf("nowchild2valueis:
%d\n'\val);exit(O);
}
//waitforallchildrentoterminatewhile(wait(NULL)>0);
printf(Mnowparentvalueis:
%d\n'\val);return0;
)
分析并编译上述程序,观察运行结果,试考虑如何保证运行结果的正确?
结合输出结果分析程序的流程。
3・生产者和消费者问题
编制一段程序,实现经典的进程同步问题生产者和消费者问题。
下面程序使用
共享存储区域技术实现进程通信,其中两个子进程作为消费者,一个子进程作为生产者。
定义说明:
structsembuf{
unsignedshortsem_num;shortsem^op;
shortsentJig;
};
unionsenum{
intval;
structsemid^cls^buf;unsignedshort\irray;structseminfo*_huf;
void*_ad;
};
〈参考程序〉
#include
#include
#include
^include
#include
^include
^include
^include
^defineMAXSEM5
〃声明三个信号量标识符ID
intfullid:
intemptyid:
intmutxid;
intmain()
{
structsembufP,V;;〃定义P,V操作所使用的数据结构
unionsemunarg;〃给信号量賦初值的参数
〃声明共享内存
int*array;
int*sum;
int*sct;
int*get;
〃采用匿名方式映射共享内存//
//nunapO函数的功能,一个文件或者其它对象映射进内存,返回被映射区的指针
array=(int*)mmap(NULL,
sizeof(int)*5,PROT_READIPROT_WRITE.MAP_SHAREDIMAP_ANONYMOUS sum=(int*)mmap(NULL, sizeof(int),PROT_READIPROT_WRITE.MAP_SHAREDIMAP_ANONYMOUS,-1,0); get=(int*)mmap(NULL, sizeof(int),PROT_READIPROT_WRITE.MAP_SHAREDIMAP_ANONYMOUS,-1,0); set=(int*)mmap(NULL, sizeof(int),PROT_READIPROT_WRITE.MAP_SHAREDIMAP_ANONYMOUS,-1Q); *sum=0; *get=0; *set=0; 〃生成信号集semget(key_tkey,intsize,intflag)创建一个信号量集,英中IPC_PRIVATE说明只能用于父子进程通信,1为共享区的大小,00666说明可读写。 fullid=semget(IPC_PRIVATE,1.IPC.CREATI00666);〃生成只有一个信号虽: 的信号集emptyid=semget(IPC_PRIVATEJ.IPC_CREATI00666); mutxid=semget(IPCJRIVATE,1JPC.CREATI00666); 〃为信号量赋初值fullidxO;emptyid=5,mutxid=1 //semctl(intshmid,intcmd,structshmid_ds*buf)〃对信号量集的控制 arg.val=0; if(semctl(fullid,0,SETVAL,arg)==-I)perror(HsemctlsetvalerrorH); arg.val=MAXSEM; if(semctl(emptyid,0,SETVAL,arg)=-1)pcrror(nsemctlsetvalerror。 ; arg.val=1; if(semctl(mutxid,0,SETVAL,arg)=-l)perror(Hsetctlsetvalerror"); /*信号疑的值与相应资源的使用情况有关,当它的值大于0时,表示当前可用的资源数的数量: 当它的值小于0时,其绝对值表示等待使用该资源的进程个数。 信号量的值仅能由PV操作来改变*/ 〃初始化P・V操作 //左义信号量的V操作 V.sem_num=0; 〃o表示第一个信号 V.sem_op=1; V.sem_flg=SEM_UNDO; 〃对信号量加1操作 〃调用进程朋溃,信号咼计数复位,防止永久占用 〃左义看号量的P操作P.sem_num=0;P.sem_op=-l;P.scm_flg=SEM_UNDO; 〃对信号量进行减1操作 〃生产者进程 if(fork()=0) inti=0; while(i<100) {semop(emptyid,&P,1);〃对信号Semptyid执行P操作 semop(nnitxid,&P,1);〃对信号Mmutxid执彳亍P操作 array[*(set)%MAXSEM]=i+1;printf("Producer%d\n*\array[(*set)%MAXSEM]);(*set)++; semop(mutxid,&V,1);//对信号Mmutxid执行V操作semop(fullid.&V,1);〃对信号虽: fullid执行V操作i++; } sleep(lO);printf(MProducerisover11); exit(O); else //ConsumerA进程 if(fork()=0) while(l) { semop(fullid.&P,1); semop(mutxid,&P,1); if(*get==100)break; ♦sum+=array[(*get)%MAXSEMJ; printf(MThcComsumerAGetNumber%d\nM,array[(*get)%MAXSEM]);(*get)++; if(*get==100)printf(MThesumis%d\nH,*sum); semop(mutxid,&V,1); semop(emptyid,&V,1); sleep (1); } printf(MConsumerAisover”); exit(O); } else //ConsumerB进程 if(fork()==0){while(l){semop(fullid,&P,1);semopdnutxid,&P,1); if(*get==100) break; *sum+=array[(*get)%MAXSEM];printf("TheComsumerBGetNumber%d\n,\array[(*get)%MAXSEM]);(*get)++; if(*get==100) printf(HThesumis%d*sum); semop(mutxid,&V,1); semop(emptyid,&V,1); sleep(l); } printf(HConsumerBisover,r); exit(O); } }//end }//endif //sleep(20); return0; } 实验要求: 结合理论讲解的生产者和消费者的处理模式,理解生产者和消费者问题的程 序,运行程序分析其结果。 试修改程序增加一个生产者。 实验报告 1列出调试通过程序的清单,分析运行结果。 2给出必要的程序设计思路和方法(或列出流程图)。 3总结上机调试过程中所遇到的问题和解决方法及感想。 五、思考题 读者和写者问题 在Linux下编写程序解决读者和写者问题(参照生产者和消费者问题
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 操作系统 实验 进程 创建