进程管理实验.docx
《进程管理实验.docx》由会员分享,可在线阅读,更多相关《进程管理实验.docx(22页珍藏版)》请在冰豆网上搜索。
进程管理实验
广州大学学生实验报告
开课学院及实验室:
计算机科学与工程实验室2015年11月29日
实验课程名称
操作系统实验
成绩
实验项目名称
实验1进程管理实验
指导老师
一、实验目的
1、掌握进程的概念,明确进程的含义
2、认识并了解并发执行的实质
3、加深对进程概念的理解,明确进程和程序的区别
4、分析进程争用资源的现象,学习解决进程互斥的方法
5、了解Linux/windows系统中进程通信的基本原理
6、熟悉LINUX系统中进程之间软中断通信的基本原理
7、熟悉UNIX/LINUX支持的管道通信方式
8、熟悉消息传送的机理
9、了解和熟悉共享存储机制
2、实验内容
1、编写一段程序,使用系统调用fork()创建两个子进程。
当此程序运行时,在系统中有一个父进程和两个子进程活动。
让每一个进程在屏幕上显示一个字符:
父进程显示'a',子进程分别显示字符'b'和字符'c'。
试观察记录屏幕上的显示结果,并分析原因。
2、修改上述程序,每一个进程循环显示一句话。
子进程显示'daughter…'及'son……',父进程显示'parent……',观察结果,分析原因。
3、用fork()创建一个进程,再调用exec()用新的程序替换该子进程的内容,利用wait()来控制进程执行顺序。
4、修改实验代码2中的程序,用lockf()来给每一个进程加锁,以实现进程之间的互斥,观察并分析出现的现象。
5、写一个使用守护进程(daemon)的程序,来实现:
(1)创建一个日志文件/var/log/Mydaemon.log
(2)每5秒都向其中写入一个时间戳(使用time_t的格式)注意:
要root权限才能在/var/log创建文件。
6、
(1)编写程序:
用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即按^c键);捕捉到中断信号后,父进程用系统调用kill()向两个子进程发出信号,
子进程捕捉到信号后分别输出下列信息后终止:
Childprocess1iskilledbyparent!
Childprocess2iskilledbyparent!
父进程等待两个子进程终止后,输出如下的信息后终止:
Parentprocessiskilled!
(2)分析利用软中断通信实现进程同步的机理
7、编写程序实现进程的管道通信。
用系统调用pipe()建立一管道,二个子进程P1和P2分别向管道各写一句话:
Child1issendingamessage!
Child2issendingamessage!
父进程从管道中读出二个来自子进程的信息并显示(要求先接收P1,后P2)。
8、消息的创建、发送和接收。
使用系统调用msgget(),msgsnd(),msgrev(),及msgctl()编制一长度为1k的消息发送和接收的程序。
9、编制一长度为1k的共享存储区发送和接收的程序。
3、实验原理
1、进程创建
2、进程控制
3、信号量机制
4、管道机制
5、消息通信机制及共享存储区机制。
四、实验设备
Win7下虚拟机VMware-workstation-11.0.0及CentOS-5.8-i386
5、实验要求
调试并运行一个允许n 个进程并发运行的进程管理模拟系统。
了解该系统的进程控制、同步及通讯机构,每个进程如何用一个 PCB 表示、其内容的设置;各进程间的同步关系;系统在运行过程中显示各进程的状态和有关参数变化情况的意义。
六、实验程序
1、进程创建
#include
main()
{
intp1,p2;
while((p1=fork())==-1);/*创建子进程p1*/
if(p1==0)putchar('b');
else
{
while((p2=fork())==-1);/*创建子进程p2*/
if(p2==0)putchar('c');
elseputchar('a');
}
}
2、进程管理
#include
main()
{
intp1,p2,i;
while((p1=fork())==-1);/*创建子进程p1*/
if(p1==0)
for(i=0;i<10;i++)
printf("daughter%d\n",i);
else
{
while((p2=fork())==-1);/*创建子进程p2*/
if(p2==0)
for(i=0;i<10;i++)
printf("son%d\n",i);
else
for(i=0;i<10;i++)
printf("parent%d\n",i);
}
}
3、进程控制
#include
#include
main()
{
intpid;
pid=fork();/*创建子进程*/
switch(pid)
{
case-1:
/*创建失败*/
printf("forkfail!
\n");
exit
(1);
case0:
/*子进程*/
execl("/bin/ls","ls","-1","-color",NULL);
printf("execfail!
\n");
exit
(1);
default:
/*父进程*/
wait(NULL);/*同步*/
printf("lscompleted!
\n");
exit(0);
}
}
4、进程互斥
#include
#include
main( )
{
intp1,p2,i;
while((p1=fork())==-1);/*创建子进程p1*/
if(p1==0)
{
lockf(1,1,0);/*加锁,这里第一个参数为stdout(标准输出设备的描述符)*/
for(i=0;i<10;i++)
printf("daughter%d\n",i);
lockf(1,0,0);/*解锁*/
}
else
{
while((p2=fork())==-1);/*创建子进程p2*/
if(p2==0)
{
lockf(1,1,0);/*加锁*/
for(i=0;i<10;i++)
printf("son%d\n",i);
lockf(1,0,0);/*解锁*/
}
else
{
lockf(1,1,0);/*加锁*/
for(i=0;i<10;i++)
printf("parent%d\n",i);
lockf(1,0,0);/*解锁*/
}
}
}
5、守护进程
#include
#include
#include
#include
main()
{
time_tt;//建立time_t格式变量
FILE*fp;//建立文件
fp=fopen("/var/log/Mydaemon.log","a");//打开文件
pid_tpid;//守护神
pid=fork();
if(pid>0){
printf("Daemononduty!
\n");
exit(0);
}
elseif(pid<0){
printf("Can'tfork!
\n");
exit(-1);
}
while
(1){
if(fp>=0){
sleep(5);//等待5秒再往文件中写入时间戳
printf("Daemononduty!
\n");
t=time(0);
fprintf(fp,"Thecurrenttimeis%s\n",asctime(localtime(&t)));
}
}
fclose(fp);//关闭文件
}
6、信号通信机制
#include
#include
#include
voidwaiting(),stop();
intwait_mark;
main()
{
intp1,p2,stdout;
while((p1=fork())==-1);/*创建子进程p1*/
if(p1>0)
{
while((p2=fork())==-1);/*创建子进程p2*/
if(p2>0)
{
wait_mark=1;
signal(SIGINT,stop);/*接收到^c信号,转stop*/
waiting();
kill(p1,16);/*向p1发软中断信号16*/
kill(p2,17);/*向p2发软中断信号17*/
wait(0);/*同步*/
wait(0);
printf("Parentprocessiskilled!
\n");
exit(0);
}
else
{
wait_mark=1;
signal(17,stop);/*接收到软中断信号17,转stop*/
waiting();
lockf(stdout,1,0);
printf("Childprocess2iskilledbyparent!
\n");
lockf(stdout,0,0);
exit(0);
}
}
else
{
wait_mark=1;
signal(16,stop);/*接收到软中断信号16,转stop*/
waiting();
lockf(stdout,1,0);
printf("Childprocess1iskilledbyparent!
\n");
lockf(stdout,0,0);
exit(0);
}
}
voidwaiting()
{
while(wait_mark!
=0);
}
voidstop()
{
wait_mark=0;
}
7、进程的管道通信
#include
#include
#include
intpid1,pid2;
main()
{
intfd[2];
charoutpipe[100],inpipe[100];
pipe(fd);/*创建一个管道*/
while((pid1=fork())==-1);
if(pid1==0)
{
lockf(fd[1],1,0);
sprintf(outpipe,"child1processissendingmessage!
");
/*把串放入数组outpipe中*/
write(fd[1],outpipe,50);/*向管道写长为50字节的串*/
sleep(5);/*自我阻塞5秒*/
lockf(fd[1],0,0);
exit(0);
}
else
{
while((pid2=fork())==-1);
if(pid2==0)
{lockf(fd[1],1,0);/*互斥*/
sprintf(outpipe,"child2processissendingmessage!
");
write(fd[1],outpipe,50);
sleep(5);
lockf(fd[1],0,0);
exit(0);
}
else
{wait(0);/*同步*/
read(fd[0],inpipe,50);/*从管道中读长为50字节的串*/
printf("%s\n",inpipe);
wait(0);
read(fd[0],inpipe,50);
printf("%s\n",inpipe);
exit(0);
}
}
}
8、客户端服务端消息的发送与接收
1)、client.c
#include
#include
#include
#defineMSGKEY75
structmsgform
{longmtype;
charmtext[1000];
}msg;
intmsgqid;
voidclient()
{
inti;
msgqid=msgget(MSGKEY,0777);/*打开75#消息队列*/
for(i=10;i>=1;i--)
{
msg.mtype=i;
printf(“(client)sent\n”);
msgsnd(msgqid,&msg,1024,0);/*发送消息*/
}
exit(0);
}
main()
{
client();
}
2)、server.c
#include
#include
#include
#defineMSGKEY75
structmsgform
{longmtype;
charmtext[1000];
}msg;
intmsgqid;
voidserver()
{
msgqid=msgget(MSGKEY,0777|IPC_CREAT);/*创建75#消息队列*/
do
{
msgrcv(msgqid,&msg,1030,0,0);/*接收消息*/
printf(“(server)received\n”);
}while(msg.mtype!
=1);
msgctl(msgqid,IPC_RMID,0);/*删除消息队列,归还资源*/
exit(0);
}
main()
{
server();
}
9、共享存储区通信
#include
#include
#include
#defineSHMKEY75
intshmid,i;int*addr;
voidclient()
{inti;
shmid=shmget(SHMKEY,1024,0777);/*打开共享存储区*/
addr=shmat(shmid,0,0);/*获得共享存储区首地址*/
for(i=9;i>=0;i--)
{while(*addr!
=-1);
printf("(client)sent\n");
*addr=i;
}
exit(0);
}
voidserver()
{
shmid=shmget(SHMKEY,1024,0777|IPC_CREAT);/*创建共享存储区*/
addr=shmat(shmid,0,0);/*获取首地址*/
do
{
*addr=-1;
while(*addr==-1);
printf("(server)received\n");
}while(*addr);
shmctl(shmid,IPC_RMID,0);/*撤消共享存储区,归还资源*/
exit(0);
}
main()
{
while((i=fork())==-1);
if(!
i)server();
system(“ipcs-m”);
while((i=fork())==-1);
if(!
i)client();
wait(0);
wait(0);
}
7、总结心得
(1)实验分析:
1、进程创建
进程的创建运行结果 bca(有时会出现bac)
分析:
从进程执行并发来看,输出bac,acb等情况都有可能。
原因:
fork()创建进程所需的时间多于输出一个字符的时间,因此在主进程创建进程2的同时,进程1就输出了“b”,而进程2和主程序的输出次序是有随机性的,所以会出现上述结果
2、进程管理
进程的管理运行结果 parent… son… daughter…daughter…或 parent… son… parent…daughter…等
第一种运行结果
第二种运行结果
分析:
由于函数printf()输出的字符串之间不会被中断,因此,字符串内部的字符顺序输出时不变。
但是 , 由于进程并发执行时的调度顺序和父子进程的抢占处理机问题,输出字符串的顺序和先后随着执行的不同而发生变化。
这与打印单字符的结果相同。
思考题:
(1)系统是怎样创建进程的?
答:
一旦操作系统发现了要求创建新进程的事件后,便调用进程创建原语Creat()按下述步骤创建一个新进程。
1)申请空白PCB。
为新进程申请获得唯一的数字标识符,并从PCB集合中索取一个空白PCB
2)为新进程分配资源。
为新进程的程序和数据以及用户栈分配必要的内存空间。
3)初始化进程控制块。
包括:
初始化标识信息,处理机状态信息,处理机状态控制信息。
4)将新进程插入就绪队列,如果进程就绪队列能够接纳新进程,便将新进程插入到就绪队列中。
(2)当首次调用新创建进程时,其入口在哪里?
答:
fork系统调用创建的子进程继承了原进程的context,也就是说fork调用成功后,子进程与父进程并发执行相同的代码。
但由于子进程也继承了父进程的程序指针,所以子进程是从fork()后的语句开始执行(也就是新进程调用的入口)。
另外fork在子进程和父进程中的返回值是不同的。
在父进程中返回子进程的PID,而在子进程中返回0。
所以可以在程序中检查PID的值,使父进程和子进程执行不同的分支。
3、进程控制
运行结果:
执行命令ls-l-color,(按倒序)列出当前目录下所有文件和子目录;lscompleted!
分析:
程序在调用fork()建立一个子进程后,马上调用wait(),使父进程在子进程结束之前,一直处于睡眠状态。
子进程用exec()装入命令ls,exec()后,子进程的代码被ls的代码取代,这时子进程的PC指向ls的第1条语句,开始执行ls的命令代码。
注意在这里wait()给我们提供了一种实现进程同步的简单方法。
思考题:
(1)可执行文件加载时进行了哪些处理?
答:
初始化,开辟内存,显示窗口是后期可选工作。
每个程序,任何一个程序,任何一个可执行文件,启动运行时都要调用Ntdll.dll中的NtCreateProcess()。
将源代码转换为机器可认识代码的过程。
编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序。
具体经过下几个处理:
例如:
C源程序->编译预处理->编译->优化程序->汇编程序->链接程序->可执行文件
(2)什么是进程同步?
wait()是如何实现进程同步的?
1)我们把异步环境下的一组并发进程因直接制约而互相发送消息而进行互相合作、互相等待,使得各进程按一定的速度执行的过程称为进程间的同步。
进程同步是进程之间直接的相互作用,是合作进程间有意识的行为。
2)如果我们对一个消息或事件赋以唯一的消息名,则我们可用过程wait(消息名)表示进程等待合作进程发来的消息。
这样,wait()就实现了进程间的同步。
4、进程互斥
运行结果:
parent… son… daughter…daughter…或 parent… son… parent…daughter…等
第一种可能的运行结果
第二种可能运行结果
分析:
上锁后与未上锁的输出结果相同,也是随着执行时间不同,输出结果的顺序有所不同。
因为上述程序执行时,不同进程之间不存在共享临界资源(其中打印机的互斥性已有由操作系统保证)问题,所以,加锁与不加锁效果相同。
5、守护进程
运行结果:
创建一个日志文件/var/log/Mydaemon.log,每5秒就向其中写入一个时间戳(使用time_t的格式)
运行程序
5秒之后可以看到
6、信号通信机制
运行结果:
执行程序屏幕无反应
按下Ctrl+C后,屏幕显示Parentprocessiskilled!
分析:
上述程序中,signal()都放在一段程序的前面部位,而不是在其他接收信号处。
这是因为signal()的执行只是为进程指定信号值16或17的作用,以及分配相应的与stop()过程链接的指针。
因而,signal()函数必须在程序前面部分执行。
本方法通信效率低,当通信数据量较大时一般不用此法。
思考题:
1、该程序段前面部分用了两个wait(0),它们起什么作用?
该程序段前面部分用了两个wait(0),这是因为父进程必须等待两个子进程终止后才终。
wait()函数常用来控制父进程与子进程的同步。
在父进程中调用wait()函数,则父进程被阻塞。
进入等待队列,等待子进程结束。
当子进程结束时,会产生一个终止状态字,系统会向父进程发出SIGCHLD信号。
当接到信号后,父进程提取子进程的终止状态字,从wait()返回继续执行原程序。
2、该程序段中每个进程退出时都用了语句exit(0),为什么?
该程序中每个进程退出时都用了语句exit(0),这是进程的正常终止。
在正常终止时,exit()函数返回进程结束状态。
异常终止时,则由系统内核产生一个代表异常终止原因的终止状态,该进程的父进程都能用wait()得到其终止状态。
在子进程调用exit()后,子进程的结束状态会返回给系统内核,由内核根据状态字生成终止状态,供父进程在wait()中读取数据。
若子进程结束后,父进程还没有读取子进程的终止状态,则系统就子进程的终止状态置为“ZOMBIE”并保留子进程的进程控制块等信息,等父进程读取信息后,系统才彻底释放子进程的进程控制块。
若父进程在子进程结束之前就结束的话,则子进程就变成了“孤儿进程”,系统进程init会自动“收养”该子进程,成为该子进程的父进程即父进程标识号变为1,当 子进程结束时,init会自动调用wait()读取子进程的遗留数据,从而避免系统中留下大量的垃圾。
3、为何预期的结果并未显示出?
p1、p2都会捕捉该中断信号。
对于父进程,当它捕捉到中断信号后就会转向程序中指定的函数“stop()”,当“stop()”执行完毕后,父进程被唤醒,从中断处继续运行。
对于子进程,由于没有给它们指定收到中断信号后的动作,它们会在捕捉到中断信号后执行默认操作,即结束自己。
所以当我们发出中断信号后,父进程按预计方式正常执行,而p1、p2就被自己结束了,从而也就不会有预计的结果了。
4、程序该如何修改才能得到正确结果?
为了使程序运行得到正确的结果,可以在每个子进程程序段开头加上忽略“^C”中断信号的语句,即:
signal(SIGINT,SIG_IGN)。
#include
#include