实 验 报 告 书
学 生 姓 名
学 号
班 级
2012 — 2013 学年 第 一 学期
《计算机操作系统》实验
报告
软件系统测试报告下载sgs报告如何下载关于路面塌陷情况报告535n,sgs报告怎么下载竣工报告下载
实验名称
进程管理
实验序号
1
实验日期
2012年12月
实验人
一、实验目的和要求
1. 加深对进程概念的理解,明确进程和程序的区别
2. 进一步认识并发执行的实质
3. 分析进程争用资源的现象,学习解决进程互斥的方法
4. 了解Linux系统中进程通信的基本原理
二、相关背景知识
1.初步了解了进程的并发执行,互斥,同步等相关理论知识;
2.明白父进程如何通过fork()函数创建子进程,pipe()函数创建管道,以及跟实验有关的lock(),signal(),exit(),wait(),kill()等函数的调用过程;
3.知道如何使用UNIX/Linux系统自带的功能强大的可视化的编辑器Vi。
三、实验内容
1. 进程的创建
2. 进程的控制
3. 进程的软中断通信
4. 进程的管道通信
四、关键数据结构与函数的说明
1. fork( ):创建一个新进程
返回值的意义:0:创建子进程,从子进程返回;>0:从父进程返回,返回值为子进程的进程标识符;-1:创建失败。如果fork( )调用成功,它向父进程返回子进程的PID,并向子进程返回0,即fork( )被调用了一次,但返回了两次。此时OS在内存中建立一个新进程,所建的新进程是调用fork( )父进程(pare的副本,称为子进程。
2. wait():进程的等待,常用来控制父进程与子进程的同步:
在父进程中调用wait函数,则父进程被阻塞,进入等待队列,等待子进程结束。当子进程结束时,会产生一个终止状态字,系统会向父进程发出SIGCHLD信号。当接收到信号后,父进程提取子进程的终止状态字,从wait返回继续执行原程序。
3. exit():结束进程
进程结束最常用的方法是调用exit函数,在main函数中调用的return,最终也是调用exit,这些都属于进程的正常终止。
4. lockf(files,mode,size):用于锁定文件的某些段或者整个文件
files是文件描述符;mode是锁定和解锁:1表示锁定,0表示解锁.size是锁定或解锁的字节数,为0,表示从文件的当前位置到文件尾.。
5. signal(int signum,void(* handler)(int)):signal()会依参数signum 指定的信号编号来设置该信 号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。
6. kill(pid_t pid,int sig),可以用来送参数sig指定的信号给参数pid指定的进程。
五、编译与执行过程截图
1. 进程的创建
进程的创建:系统调用fork()创建两个子进程。当程序运行时,系统中有一个父进程和两个子进程活动。父进程显示字符“a”;子进程分别显示字符“b”和字符“c”。如图,有acb;abc两种情况出现了
2.进程的控制
子进程和父进程互相抢占处理机,导致输出“乱序”现象的出现,可以看到在“bbb”中出现了“aaa”。
因为加锁了,所以各子进程和父进程各自打印出各自的字符串,并没有出现穿插的现象。
3.进程的软中断通信
使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即按Del键);当捕捉到中断信号后,父进程用系统调用kill()向两个子进程发出信号,子进程捕捉到信号后分别输出:
Child process 1 is killed by parent!
Child process 2 is killed by parent!
父进程等待两个子进程终止后输出:
Parent process is killed!
4.进程的管道通信
使用系统调用pipe()建立一条管道线;两个子进程P1和P2分别向管道各写一句话:
Child 1 is sending a message!
Child 2 is sending a message!
而父进程则从管道中读出来自于两个子进程的信息,先接收P1发来的消息,然后再接收P2发来的消息,显示在屏幕上。
六、实验结果与分析
1.进程的创建运行结果:acb,abc,其实,从进程执行并发来看,输出abc的任何排列都是有可能的。因为fork()创建进程所需的时间虽然可能多于输出一个字符的时间,但各个进程的时间片的获得却不是一定是顺序的,父进程与子进程的输出内容会叠加在一起,输出次序带有随机性。
2.进程的控制:
<程序一>子进程和父进程输出字符串有相互嵌插的现象。由于进程并发执行时的调度顺序和父子进程的抢占处理机问题,输出字符串的顺序和先后随着执行的不同而发生变化。这与打印单字符的结果相同。
<程序二>因为lockf(1,1,0)锁定标准输出设备,lockf(1,0,0)解锁标准输出设备,在lockf(1,1,0)与lockf(1,0,0)中间的for循环输出不会被中断,加锁与不加锁效果不相同。
3.软中断:不做任何操作等待五秒钟父进程回在子进程先退出后退出,并打印退出的顺序;或者点击ctrl+C后程序退出并打印退出的顺序。其中SIGALRM是用alarm函数设置的timer超时或setitimer函数设置的interval timer超时;SIGINT由Interrupt Key产生,通常是CTRL+C或者DELETE
4.进程的管道:进程p1和进程p2分别从管道的一端向管道输入了信息,父进程从管道的另一端读出了信息,先为p1的消息再为p2的消息,连接了一个读进程和一个写进程实现他们之间通信的一个共
享。首先,通过pipe()先创建了一个管道,子进程p1,p2是共享输入端的,所以要通过lockf()进行加锁解锁实现互斥,并且是先由p1向管道发送消息,然后是p2。父进程通过wait(0)等待子进程结束后从管道读取消息。
七、调试时遇到的问题及解决方法(提供BUG截屏)
1.在第2(1)题中因为循环次数太少,所以并没有出现乱序的情况,后来老师提醒我们可以把循环次数大大增加,因为处理机的速度是很快的,如果不把循环次数i的值设为比较大的数的话,很可能体现不出来进程并发执行时的调度顺序和父子进程的抢占处理机问题,这个数字的设定也因机器而异,本来在我的程序中i的设置值为500,后来我改为了10000,才出现了乱序的现象。
2. 在3题中,因为我自己的粗心导致注释行出错,使kill(p1,16)函数没有调用成功,只出现了“parent process is killed!”,没有输出其余两句。在出现错误之后我立马去检查,便解决了这个问题。
3.在第3题中,要求让父进程捕捉键盘上来的中断信号(即按Del键),但是在实际实验中当我按下Del键时并没反映,后来经查资料,知道了在Linux系统下应该是按下Ctrl+C键。
八、调试后的程序源代码
/*第一题:creat.c*/
#include
main( )
{
int p1,p2;
while((p1=fork( ))==-1); /*创建子进程p1*/
if (p1==0) putchar('b');
else
{
while((p2=fork( ))==-1); /*创建子进程p2*/
if(p2==0) putchar('c');
else putchar('a');
}
}
/*第2题(1):creatf.c*/
#include
main( )
{
int p1,p2,i;
while((p1=fork( ))==-1); /*创建子进程p1*/
if (p1==0)
{
for(i=0;i<10000;i++)
printf("aaa");
}
else
{
while((p2=fork( ))==-1); /*创建子进程p2*/
if(p2==0)
{
for(i=0;i<10000;i++)
printf("bbb");
}
else
{
for(i=0;i<1000;i++)
printf("ccc");
}
}
}
/*第2题(2):lock.c*/
main()
{
int p1,p2,i;
if(p1=fork())
{
lockf(1,1,0);
for(i=0;i<10000;i++)
printf("aaa");
lockf(1,0,0);
exit(0);
}
else
{
if(p2=fork())
{
lockf(1,1,0);
for(i=0;i<10000;i++)
printf("bbb");
lockf(1,0,0);
exit(0);
}
else
{
lockf(1,1,0);
for(i=0;i<10000;i++)
printf("ccc");
lockf(1,0,0);
exit(0);
}
}
}
/*第3题 33.c*/
#include
#include
#include
void waiting(),stop(),alarming();
int wait_mark;
main()
{
int p1,p2;
if(p1=fork()) /*创建子进程p1*/
{
if(p2=fork()) /*创建子进程p2*/
{
wait_mark=1;
signal(SIGINT,stop); /*接收到^c信号,转stop*/
signal(SIGALRM,alarming);/*接受SIGALRM*/
waiting();
kill(p1,16); /*向p1发软中断信号*/
kill(p2,17); /*向p2发软中断信号*/
wait(0); /*同步*/
wait(0);
printf("parent process is killed!\n");
exit(0);
}
else
{
wait_mark=1;
signal(17,stop);
signal(SIGINT,SIG_IGN); /*忽略^c信号*/
while (wait_mark!=0);
lockf(1,1,0);
printf("child process2 is killed by parent!\n");
lockf(1,0,0);
exit(0);
}
}
else
{
wait_mark=1;
signal(16,stop);
signal(SIGINT,SIG_IGN); /*忽略^c信号*/
while (wait_mark!=0)
lockf(1,1,0);
printf("child process1 is killed by parent!\n");
lockf(1,0,0);
exit(0);
}
}
void waiting()
{
sleep(5);
if (wait_mark!=0)
kill(getpid(),SIGALRM);
}
void alarming()
{ wait_mark=0;}
void stop()
{ wait_mark=0;}
/*第4题:pipe.c*/
#include
#include
#include
int pid1,pid2;
main( )
{
int fd[2];
char outpipe[100],inpipe[100];
pipe(fd); /*创建一个管道*/
while ((pid1=fork( ))==-1);
if(pid1==0)
{
lockf(fd[1],1,0);
sprintf(outpipe,"child 1 process is sending message!"); /*把串放入数组outpipe中*/
write(fd[1],outpipe,50); /*向管道写长为字节的串*/
lockf(fd[1],0,0);
exit(0);
}
else
{
while((pid2=fork( ))==-1);
if(pid2==0)
{
lockf(fd[1],1,0); /*互斥*/
sprintf(outpipe,"child 2 process is sending message!");
write(fd[1],outpipe,50);
lockf(fd[1],0,0);
exit(0);
}
else
{
wait(0); /*同步*/
read(fd[0],inpipe,50); /*从管道中读长为字节的串*/
printf("%s\n",inpipe);
wait(0);
read(fd[0],inpipe,50);
printf("%s\n",inpipe);
exit(0);
}
}
}
九、实验体会
1.此次实验,第一个题目和第二个题目是比较简单的,也没有涉及到很多函数,所以自己能够理解,并且在调试的过程中也是没遇到什么问题。为我完成第3题和第4题节约了时间,所以基础的东西需要掌握好。
2.此次实验查了许多资料,因为并不是所有的程序都是自己编写出来的,因为对Signal()函数以及kill()函数都不是清楚,所以我就利用网络进行了查询,也确实查找了函数的功能,同时还知道了Signal处理函数中经常要用到的一些信号等,另外也理解了老师说的“kill并不是杀死的意思”。除此之外,我也查询了fork()函数的相关知识,本来对书上的例子不是很理解,后来知道了fork()函数的返回值及含义的时候我就明白了。
3.另外在实验中我把管道和队列联系起来,因为是从一端进,另一端出。当然管道和队列还是有区别的,但是这对于我的理解是有帮助的,我觉得在学习中应该多去联想自己已经学过的知识,这样应该会帮助自己掌握新的知识。