实验七管道通信实验目的加深对进程概念的理解,明确进程和程序的区别、进一步认识并发执行的实质了解并熟悉Linux系统中利用管道实现进程通信的基本概念及方法熟悉Linux提供的有关系统调用函数/库函数,并能使用这些函数实验准备及预习阅读讲义《附件8-管道通信》,了解Linux系统中利用管道实现进程通信的基本概念及方法熟悉Linux提供的有关系统调用函数/库函数:pipe()、mkfifo、close()、read()、write()、lockf()管道是Linux支持的最初UnixIPC形式之一,也是一种使用非常频繁的通信机制逻辑上被看作管道文件,只存在于内存中管道是单向的、先进先出的、无结构的、固定大小的字节流,把一个进程的标准输出和另一个进程的标准输入连接在一起写进程在管道的尾端写入数据,读进程在管道的首端读出数据数据读出后将从管道中移走,其它读进程都不能再读到这些数据管道提供了简单的流控制机制进程试图读空管道时,在有数据写入管道前,进程将一直阻塞管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞管道克服使用文件通信的两个问题限制管道的大小管道是一个固定大小的缓冲区Linux中,该缓冲区的大小为1页,即4K字节,因此不像文件那样不加检验地增长写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写读取进程可能工作得比写进程快当所有当前进程数据已被读取时,管道变空随后的read()调用将默认地被阻塞,等待某些数据被写入例1:管道可用于输入输出重定向,它将一个命令的输出直接定向到另一个命令的输入。比如,当在某个shell程序(Bourneshell或Cshell等)键入who│wc-l后,相应shell程序将创建who以及wc两个进程和这两个进程间的管道。考虑下面的命令行:$kill-l显示当前系统支持的所有信号$kill-l|grepSIGRTMIN#includeunistd.hintpipe(intfd[2])功能:创建一个管道,管道两端可分别用描述字fd[0]以及fd[1]来描述注意管道的两端是固定任务的,一端只能用于读,由描述字fd[0]表示,称为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称为管道写端如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生一般文件的I/O函数都可以用于管道,如close、read、write等等管道的创建例2:使用系统调用pipe()建立一条管道线,两个子进程p1和p2分别向管道各写一句话:child1issendingamessage!和child2issendingamessage!,父进程则从管道中读出来自子进程的信息,并显示在屏幕上。#includeunistd.h#includestdio.hmain(){intfd[2];intpid1,pid2;charOutPipe[100],InPipe[100];pipe(fd);while((pid1=fork())==-1);if(pid1==0){printf(“childprocess1%d\n”,getpid());lockf(fd[1],1,0);/*加锁锁定写入端*/sprintf(OutPipe,“child1issendingamessage!”);write(fd[1],OutPipe,50);/*将buf中的50个字符写入管道*/sleep(5);/*睡眠5秒,暂时放弃CPU*/lockf(fd[1],0,0);/*解锁释放写入端*/exit(0);/*结束进程pid1*/}else{//elseifpid1while((pid2=fork())==-1);if(pid2==0){printf(“childprocess2%d\n”,getpid()”);lockf(fd[1],1,0);sprintf(OutPipe,“child2issendingamessage!”);write(fd[1],OutPipe,50);sleep(5);lockf(fd[1],0,0);exit(0);}}else{/*elseifpid2*/printf(“parentprocess%d\n”,getpid());wait(0);read(fd[0],InPipe,50);printf(“%s\n”,InPipe);wait(0);read(fd[0],InPipe,50);printf(“%s\n”,InPipe);exit(0);}/*endifpid2*/}/*endifpid1*/}使用管道通信时,可关闭某些不需要的读或写描述符,建立起单向的读或写管道,然后用read和write像操作文件一样去操作它:close(pipe_fd[0]);/*关闭读管道*/close(pipe_fd[1]);/*关闭写管道*/发送进程利用文件系统的系统调用write(fd[1],buf,size),把buf中长度为size字节的字符消息送入管道入口(即写入端)fd[1]接收进程则使用系统调用read(fd[0],buf,size)从管道出口(即读出端)fd[0]读出size字节的字符消息放到buf中例3:两个进程,如子进程向父进程发送数据,即使用子进程的fd[1]和父进程的fd[0],同时关闭子进程的fd[0]和父进程的fd[1]#includesys/types.h#includeunistd.h#includestdio.h#includestdlib.h#includeerrno.h#includememory.hintmain(){char*msg=Iamchildprocess!;/*子进程发送的数据*/pid_tpid;charbuf[100];/*用于读取*/intpi;/*创建管道时的返回值*/intfd[2];/*创建管道的参数*/memset(buf,0,sizeof(buf));/*设置buf数组全为0,需*/pi=pipe(fd);/*要引入#includememory.h*/if(pi0){perror(pipe()error!);exit(0);}if((pid=fork())==0){/*childprocess*/close(fd[0]);/*关闭读管道*/if(write(fd[1],msg,20)!=-1)/*写入管道*/printf(childprocesswritesuccess!\n);close(fd[1]);/*关闭写管道*/}elseif(pid0){/*parentprocess*/close(fd[1]);/*关闭写管道*/sleep(2);/*休眠一下等待数据写入*/if(read(fd[0],buf,100)0)/*写入管道*/printf(Messagefromthepipeis:%s\n,buf);close(fd[0]);/*关闭读管道*/waitpid(pid,NULL,0);/*待子进程退出*/exit(0);}else{perror(fork()error!);exit(0);}}管道的局限主要局限性正体现在它的特点上只支持单向数据流只能用于具有亲缘关系的进程之间没有名字;管道的缓冲区是有限的所传送的是无格式字节流管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等多少字节算作一个消息(命令、记录)普通管道只能用于一个进程家族之间的通信,如父子,兄弟之间命名管道是有“名字”的管道,另外的进程可以看到并使用普通管道在内存中,随着进程的结束而消失,命名管道在磁盘上,作为一个特殊的设备文件而存在,进程结束不消失值得注意的是,管道严格遵循先进先出(firstinfirstout),对管道及命名管道的读总是从开始处返回数据,对它们的写则把数据添加到末尾有名管道有名管道的创建#includesys/types.h#includesys/stat.hintmkfifo(constchar*filmname,mode_tmode)功能:创建一个名为filename的管道参数:mode模式设置管道的权限,如O_CREAT、O_EXCL、O_NONBLOCK等返回:成功0,出错返回-1错误存储在errno中有名管道的打开有名管道比管道多了一个open操作intopen(constchar*pathname,intflags,mode_tmode);返回:若所有核查的权限都通过了检查则返回0,表示成功;只要有一个权限被禁止则返回-1指向打开文件的路径打开文件的方式一般为0参数flags所能使用的旗标O_RDONLY;O_WRONLY;O_RDWR三种旗标互斥,但可与下列的旗标利用OR(|)组合O_CREAT:文件不存在则自动建立文件O_EXCL:与O_CREAT合用,检查文件是否存在O_APPEND:所写入的数据以附加方式加入到文件后O_NONBLOCK:以不可阻塞的方式打开文件intfp=open(FIFO,O_RDWR|O_NONBLOCK,0);read(fp,buf,20);下面的实例演示了mkfifo的使用请先以超级用户身份登录系统,然后编辑/编译源程序(两个*.c程序),在图形终端上执行读程序readfifo.c,读程序执行后将陷入循环切换到字符终端1(ctrl+alt+f1),以超级用户身份登录并执行写程序writefifo.c然后回到图形终端,观察读程序的输出变化#includesys/types.h#includeunistd.h#includestdio.h#includestdlib.h#includeerrno.h#includefcntl.h#includesys/stat.h#includememory.h#defineFIFO/home/jkx/myfifo/*使用宏命名有名管道文件的路径*//*readfifo.c*/intmain(){intfd;/*指向命名管道*/charbuf[100];/*存储数据*/if(mkfifo(FIFO,O_CREAT|O_EXCL)0){/*创建管道*/perror(Createerror!\n);unlink(FIFO);/*清除管道*/exit(0);}/*endifmkfifo*/fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);/*打开管道*/if(fd0){perror(Createerror!\n);unlink(FIFO);exit(0);}/*endiffd*/while(1){memset(buf,0,sizeof(buf));/*清空buf数组*/if(read(fd,buf,100)0){/*读取管道*/printf(Getmessage:%s\n,buf);}else{printf(Notacceptanymessage!\n);}sleep(1);/*休眠*/}/*endwhile*/}/*endwhile*//*writefifo.c*/intmain(){char*msg=Somemessage!;/*发送数据*/intfd;fd=open(FIFO,O_WRONLY|O_NONBLOCK,0);if(write(fd,msg,20)!=-1)/*发送信息*/printf(MessagehavebeensendtoFIFO\n);exit(0);}