《网络贸易实务》实验报告
开课实验室: 电子商务实验室 20##年 4 月 17日
第二篇:实验一 进程通信——管道和信号实验报告
进程管理实验报告
【姓名】?
【学号】?
【实验题目】进程管理
【实验目的】
a.加深对进程概念的理解,明确进程和程序的区别;
b.进一步认识并发执行的实质;
c.分析进程争用资源的现象,学习解决进程互斥的方法;
d.了解Unix系统中进程通信的基本原理
【实验预备知识】
学习UNIX中有关进程创建、控制和通信的部分。
【实验方法】
利用Unix系统提供的内部函数创建进程并管理进程,从而实现进程控制、进程间通信和进程的管道通信。
【实验内容】
(1)进程的创建
编写程序,创建两个子进程。当此程序运行时,系统中有一个父进程和两个子进程。父进程在屏幕上显示“Parent”,子进程分别在屏幕上显示“Child1”和“Child2”。
(2)进程控制
如果在程序中使用系统调用lockf()来给每一个进程加锁,可以实现进程之间的互斥,观察并分析出现的现象。
(3)进程间通信
①编制一个程序,使其实现进程的软中断通信。
要求:使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即DEL键);当捕捉到中断信号后,父进程用系统调用kill()向两个进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
Child Process 1 is Killed by Parent!
Child Process 2 is Killed by Parent!
父进程等待两个子进程终止后,输出如下信息后终止:
Parent Process is killed!
②在上面的程序中增加语句signal(SIGINT, SIG_IGN)和signal(SIGQUIT, SIG_IGN),观察执行结果,并分析原因。
(4)进程的管道通信
编制一段程序,实现进程的通信。使用系统调用pipe()建立一条管道;两个子进程P1和P2分别向管道各写一句话:
Child 1 is sending a message!
Child 2 is sending a message!
而父进程则从管道中读出来自两个子进程的信息,显示在屏幕上。要求父进程先接收子进程P1发来的消息,然后再接收子进程P2发来的消息。
【系统调用说明】
(1) fork
功能:创建一个新进程。
格式:
int fork()
参数:无
返回值:
·0:创建子进程,从子进程返回的id值
·大于0:从父进程返回的了进程id值
·-1:创建失败
头文件:无
用法举例:
int x;
while ( ( x == fork() ) == -1 ); /* 创建子进程 */
(2) lockf()
功能:用途封锁文件的某些段或者整个文件。
格式:
int lockf( int files, int function, long size )
参数:
·files:文件描述符,代表锁操作的文件;
·function:锁定和解锁,1表示锁定,0表示解锁。
·size:锁定或解锁的字节数,若用0,表示从文件的当前位置到文件尾。 返回值:
略
头文件:unistd.h
用法举例:
FILE fp;
fp=open(filename,O_RDWR);
......
lockf( fp, 1, 0 ); /* 给文件fp加锁 */
......
lockf( fp, 0, 0 ); /* 解fp文件的锁 */
(3) wait
功能:wait导致进程睡眠直到它发现一个已经退出的子进程或者一个进程跟踪态下而暂停。
格式:
int wait(stat_loc)
参数:
·wait((int *)0)的功能是等待任一子进程终止,但不保存进程的终止原因。 ·参数stat_loc指向的值为该进程的终止原因:
- 如果子进程暂停,该值的高8位存有发送给该子进程而使该子进程暂停下来的信号,低8位为0177。
- 如果子进程由于调用exit系统调用而终止,则该值的低8位为0,高8位为子进程终止时,exit系统调用中参数status值的低8位。
- 如果子进程因信号终止,该值的高8位为0,低8位为引起终止的信号值。此外如低第7位为1,则表示产生一个core文件。
返回值:
·调用wait的进程并无需等待的子进程,则wait立刻返回,返回值为-1。 ·调用wait的进程等待到了子进程终止信号,则返回子进程PID。
头文件:无
用法举例:
int pid, stat=0;
......
pid = wait( &stat );
(4) exit
功能:exit使调用进行终止,并向正在等待的父进程传递状态status的低8位。 格式:
void exit( int status )
void _exit( int status )
参数:
·status:向父进程传递低8位状态信息。
说明:
对应于某些信号,核心可能在内部调用exit以终止进程。exit()调用释放用户所有的系统调用资源,而_exit()则只做部分释放,因而尽可能少地使用_exit。
(5) kill
功能:把信号sig发送给由pid标识的进程。
格式:
int kill( int pid, int sig )
参数:
·pid>0:把信号发送进程标识符是pid的进程。
·pid=0:把信号发送进程组ID是发送者pid的进程,即发送给以该进程为首的同组进程中。
·pid=-1:如果发送者的有效用户I是D超级用户,则把信号发送给除0、1外的所有进程,否则把信号发送给其实际用户ID等于发送者有效用户ID的所有进程。
·pid<-1:把信号发送给进程组ID为pid的绝对值的进程。发送者的实际或有效用户ID必须等于接收进程的实际或有效用户ID或者发送者的有效用户ID是超级用户。 ·sig:发送的信号。
头文件:
signal.h
用法举例:
int p1;
...
while ( ( p1 = fork( ) ) == -1 ); /* 创建子进程,得到进程的PID值p1 */ ...
kill( p1 , 16 ); /* 向PID为p1的进程发送信号‘16’*/
(6) signal
功能:signal允许调用进程按用户设置的func函数处理指定的信号sig。 格式:
void (*signal(sig,func))()
参数:
·int sig:可接的信号。
·void(*func)():处理信号的函数。
头文件:sig/signal.h
sig的值:略
用法举例:
signal( SIGINT, stop ); /* 接收‘Del’信号,并转函数stop */ signal( SIGINT, SIG_IGN ); /* 忽略‘Del’信号 */
signal( SIGQUIT, SIG_IGN ); /* 忽略‘quit’信号 */
【程序结构】
①数据结构:无特殊结构
②程序结构:if-else分支结构、while循环结构、顺序结构
③主要算法:无特殊算法
【实验结果】
A. 有代表性的执行结果:
(1)进程创建的C程序执行结果:
Parent
Child1
Parent
Child2
(2)进程间通信的C程序执行结果:
Child Process 1 is Killed by Parent!
Child Process 2 is Killed by Parent!
Parent Process is killed!
(3)进程间管道通信的C程序执行结果:
Child 1 is sending a message!
Child 2 is sending a message!
B.结果分析及解释:
(1)进程创建并输出信号的流程图如下:
父进程运行(创建子进程1直至成功)
子进程1运行(输出:Child1进程1完成)
父进程运行(创建子进程2直至成功)
子进程2运行(输出:Child2.进程2完成)
父进程运行(输出:Parent完成)
(2)进程间通信:
父进程运行(创建子进程1直至成功)
父进程运行(创建子进程2直至成功)
父进程运行(接收中断信号,并将中断信号传递给子进程1、2) 子进程1运行(接收父进程中断信号输出并终止进程)
子进程2运行(接收父进程中断信号后输出并终止进程)
父进程运行(接收子进程终止信号兵输出后终止进程)
(3)进程间的管道通信:
创建管道
父进程运行(创建子进程1直至成功)
子进程运行(向管道里写数据并终止)
父进程运行(从管道里面读数据并输出)
父进程运行(创建子进程2直至成功)
子进程2运行(向管道写数据并终止)
父进程运行(从管道读数据并输出后终止进程)
【问题分析】
⑴wait函数在本程序中的用法说明:wait()函数的功能是等待子进程运行结束。wait(0)表示等待子进程正常结束。wait(0)与exit(0)函数对应,exit(0)表示进程正常终止,exit(1)表示进程运行有错,异常终止。通常父进程在创建子进程时,应在进程的末尾安排一条exit( ),使子进程自我终止。如果子进程没有完成,父进程一直等待。wait( )将调用进程挂起功能,直至其子进程因暂停或终止而发来软中断信号(SIGCHLD)将其唤醒为止。如果在wait( )前已有子进程暂停或终止,则父进程做适当处理后便返回。
在本程序中,父进程创建两个子进程,再用kill()向两个子进程发出中断信号,子进程P1和P2接到信号后,用exit(0)函数正常终止自我进程(向父进程发SIGCHLD信号。 父进程的wait()函数收到子进程的SIGCHLD信号后,对子进程作适当处理后(资源回收)后返回本进程)。因为父进程有两个子进程,所以需要两个wait()函数来等待子进程的结束。
⑵signal函数在本程序中的说明:signal()函数的功能是预置对信号的处理方式。格式:
signal(sig,function)
sig用于指定信号的类型,function为非0、非1整数时,function的值即作为信号处理程序的指针。在本程序中调用signal(SIGINT,stop),其功能是让父进程捕捉键盘的中断信号(ctrl+c),当父进程捕捉到键盘中断信号后,父进程用系统调用函数kill()向两个子进程发出中断信号16,17。子进程收到16,17信号后调用exit(0)自我终止进程。而父进程用wait(0)等待子进程的结束。
⑶pause()函数在进程间通信时是必须的,因为pause()函数使进程挂起从而等待终端输入(本例中:从键盘输入Ctrl+C),否则程序将自动输出进程间通信的输出结果。
⑷在子进程1、2中使用signal(SIGINT,SIGIGN)是必需的,因为从键盘输入Ctrl+C后,会终止所有接收终端信号的进程。否则,将不会输出题目中要求的结果。
⑸在进程间通信的实验中,我们还会得到另外一种结果:
Child Process 2 is Killed by Parent!
Child Process 1 is Killed by Parent!
Parent Process is killed!
原因分析:从进程调度的角度看,子进程被创建后处于就绪态,此时,父进程和子进程作为两个独立的进程,共享同一个代码段,分别参加调度、执行、直至进程结束。但是谁会先得到调度,与系统的调度策略和系统当前的资源状态有关,是不确定的,因此,谁先从fork()函数中返回继续执行后面的语句也是不确定的。父进程必须等到两个子进程终
止,才可以终止。
⑹可以用ps命令查看正在运行的进程(包括子进程)的方法:ps –a –m
其中,-a 参数表示要显示所有进程,-m 表示显示子进程。
⑺可以用getpid()输出父进程和子进程的pid号。方法是:
printf(“Process pid=%d”,getpid());
⑻关于进程的管道通信:管道是连接读写进程的一个特殊文件,允许进程按先进先出方式传送数据,也能使进程同步执行操作。发送进程以字符流形式把大量数据送入管道,接收进程从管道中接收数据。管道的实质是一个共享文件,基本上可借助于文件系统的机制实现,包括(管道)文件的创建、打开、关闭和读写.进程对通信机构的使用应该互斥,一个进程正在使用某个管道写入或读出数据时,另一个进程就必须等待(可以用lockf()函数实现进程间的读写互斥).发送者和接收者双方必须能够知道对方是否存在,如果对方已经不存在,就没有必要再发送信息.管道长度有限,发送信息和接收信息之间要实现正确的同步关系,当写进程把一定数量的数据写入管道,就去睡眠等待,直到读进程取走数据后,把它唤醒。
【程序清单】
(1)进程创建的C程序:
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
int pid1,pid2;//定义两个子进程的进程号
int main(){
while((pid1=fork())==-1);//创建子进程1
if(pid1>0){ //判断是否为父进程
wait(0); //等待子进程1终止
printf("Parent\n");
while((pid2=fork())==-1); //创建子进程2
if(pid2>0){ //判断是否为父进程
wait(0); //等待子进程2结束
printf("Parent\n");
}
else{
printf("Child2\n");
exit(0); //正常终止子进程2
}
}
else{
printf("Child1\n");
exit(0); //正常终止子进程1
}
return 0;
}
(2)进程间通信的C程序代码:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<signal.h>
void stop() //定义stop函数
{
}
int main( )
{
int pid1,pid2; //定义子进程1、2的进程号
while((pid1=fork( ))==-1); //创建子进程1
if(pid1>0) //判断是否为父进程
{
while((pid2=fork( ))==-1); //创建子进程2
if(pid2>0) //判断是否为父进程
{
signal(SIGINT,stop); //接收Ctrl+C信号,并转stop函数 pause(); //父进程挂起
kill(pid1,16); //给子进程1传递中断信号16
kill(pid2,17); //给子进程2传递中断信号17
wait(0); //等待子进程终止
wait(0); //等待子进程终止
sleep(1); //睡眠1s
printf("Parent process is killed!!\n");
exit(0); //父进程正常终止
}
else
{
signal(17,stop); //接收父进程传来的中断信号,并转stop函数 signal(SIGINT,SIG_IGN); //忽略中断信号
pause(); //子进程2挂起
sleep(1); //睡眠1秒
printf("Child process 2 is killed by parent !!\n"); exit(0); //正常终止
}
}
else
{
signal(16,stop); //接收父进程传来的终止信号,并转stop函数 signal(SIGINT,SIG_IGN); //忽略中断信号
pause(); //子进程1挂起
printf("Child process 1 is killed by parent !!\n");
exit(0); //正常终止
}
return 0;
}
(3)进程间的管道通信:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
char outpipe[50],inpipe1[50],inpipe2[50];
//定义进出管道的字符数组
int main()
{
int pid1,pid2,filedes[2];//定义子进程1、2的进程号和文件描述符 pipe(filedes); //建立管道
while((pid1=fork())==-1); //创建子进程1
if(pid1>0)
{
wait(0); //等待子进程1终止
read(filedes[0],outpipe,sizeof(outpipe));
//从管道读取数据
printf("%s\n",outpipe);
while((pid2=fork())==-1); //创建子进程2
if(pid2>0)
{
wait(0); //等待子进程2终止
lockf(filedes[0],1,0); //锁管道
read(filedes[0],outpipe,sizeof(outpipe));
//从管道读取数据
lockf(filedes[0],0,0); //解锁
printf("%s\n",outpipe);
close(filedes[0]); //关闭管道,系统回收资源
}
else
{
strcpy(inpipe2,"Child2 is sending a message!"); lockf(filedes[1],1,0); //锁管道
write(filedes[1],inpipe2,sizeof(inpipe2));
//向管道写数据
lockf(filedes[1],0,0);//解锁
close(filedes[1]);
exit(0);//终止进程2
}
}
else
{
strcpy(inpipe1,"Child1 is sending a message!"); lockf(filedes[1],1,0); //锁管道
write(filedes[1],inpipe1,sizeof(inpipe1)); //向管道写数据
lockf(filedes[1],0,0); //解锁
close(filedes[1]);
exit(0); //终止进程1
}
return 0;
}