无名管道(pipe)
管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
定义函数: int pipe(int filedes[2])
filedes[0]为管道里的读取端
filedes[1]则为管道的写入端。
实现机制:
管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。
从原理上,管道利用fork机制建立,从而让两个进程可以连接到同一个PIPE上。最开始的时候,上面的两个箭头都连接在同一个进程Process 1上(连接在Process 1上的两个箭头)。当fork复制进程的时候,会将这两个连接也复制到新的进程(Process 2)。随后,每个进程关闭自己不需要的一个连接 (两个黑色的箭头被关闭; Process 1关闭从PIPE来的输入连接,Process 2关闭输出到PIPE的连接),这样,剩下的红色连接就构成了如上图的PIPE
一、管道读写注意点
1.必须在系统调用fork之前调用pipe,否则子进程不会继承文件描述符
2.只有在管道读端存在时,向管道写入才有意义;否则,会收到内核中的出错信号:SIFPIPE只有在管道读端存在时,向管道写入才有意义;否则,会收到内核中的出错信号:SIFPIPE
3.向管道写入数据时不保证写入的原子性,管道缓冲区一有空闲区域,写进程就试图向其写入内容。若读进程不读取管道中的内容,则写进程会一直阻塞。
4.父子进程在运行时,它们的先后顺序得不到保证。因此在这里,为保证父进程关闭读描述符,可向子进程加入sleep(2)。
二、实例
1.无名管道
/*pipe_rw.c*/ #include <unistd.h> #include <sys/types.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> int main() { int pipe_fd[2]; pid_t pid; char buf_r[100]; char* p_wbuf; int r_num; memset(buf_r,0,sizeof(buf_r)); if(pipe(pipe_fd)<0) { printf("pipe create error "); return -1; } if((pid=fork())==0) //若是子进程 { printf(" "); /*关闭子进程管道写端。睡眠2秒,确保父进程已相应地关闭了管道读端*/ close(pipe_fd[1]); sleep(2); /*子进程读取管道内容*/ if((r_num=read(pipe_fd[0],buf_r,100))>0){ printf( "%d numbers read from the pipe is %s ",r_num,buf_r); } /*关闭子进程读端*/ close(pipe_fd[0]); exit(0); } else if(pid>0) { /*关闭父进程读端*/ close(pipe_fd[0]); /*分两次向管道写入数据*/ if(write(pipe_fd[1],"Hello",5)!=-1) printf("parent write1 success! "); if(write(pipe_fd[1]," Pipe",5)!=-1) printf("parent write2 success! "); /*关闭父进程写端并睡眠3秒,让子进程读数据*/ close(pipe_fd[1]); sleep(3); /*收集子进程退出信息*/ waitpid(pid,NULL,0); exit(0); } }
运行结果如下:
[root@localhost ipc]# ./pipe_rw
parent write1 success! parent write2 success! 10 numbers read from the pipe is Hello Pipe
2.命名管道(named PIPE)
由于基于fork机制,所以管道只能用于父进程和子进程之间,或者拥有相同祖先的两个子进程之间 (有亲缘关系的进程之间)。为了解决这一问题,Linux提供了FIFO方式连接进程。FIFO又叫做命名管道(named PIPE)。
FIFO (First in, First out)为一种特殊的文件类型,它在文件系统中有对应的路径。当一个进程以读(r)的方式打开该文件,而另一个进程以写(w)的方式打开该文件,那么内核就会在这两个进程之间建立管道,所以FIFO实际上也由内核管理,不与硬盘打交道。之所以叫FIFO,是因为管道本质上是一个先进先出的队列数据结构,最早放入的数据被最先读出来,从而保证信息交流的顺序。FIFO只是借用了文件系统(file system,命名管道是一种特殊类型的文件,因为Linux中所有事物都是文件,它在文件系统中以文件名的形式存在。)来为管道命名。写模式的进程向FIFO文件中写入,而读模式的进程从FIFO文件中读出。当删除FIFO文件时,管道连接也随之消失。FIFO的好处在于我们可以通过文件的路径来识别管道,从而让没有亲缘关系的进程之间建立连接
函数原型:
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *filename, mode_t mode); int mknode(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0 );
其中pathname是被创建的文件名称,mode表示将在该文件上设置的权限位和将被创建的文件类型(在此情况下为S_IFIFO),dev是当创建设备特殊文件时使用的一个值。因此,对于先进先出文件它的值为0。
FIFO读写规则
1.从FIFO中读取数据: 约定:如果一个进程为了从FIFO中读取数据而阻塞打开了FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作
2.从FIFO中写入数据: 约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。
一旦创建了了FIFO,就可open去打开它,可以使用open,read,close等去操作FIFO 当打开FIFO时,非阻塞标志(O_NONBLOCK)将会对读写产生如下影响:
1、没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞。如试图读取空的FIFO,将导致进程阻塞;
2、使用O_NONBLOCK:访问要求无法满足时不阻塞,立即出错返回,errno是ENXIO;
/*fifo_write.c*/ #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define FIFO_SERVER "/tmp/myfifo" main(int argc,char** argv) { int fd; char w_buf[100]; int nwrite; if(fd==-1) if(errno==ENXIO) printf("open error; no reading process "); /*打开有名管道,并设置为非阻塞*/ fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0); if(argc==1) printf("Please send something "); strcpy(w_buf,argv[1]); /*向管道写入字符串*/ if((nwrite=write(fd,w_buf,100))==-1) { if(errno==EAGAIN) printf("The FIFO has not been read yet.Please try later "); } else printf("write %s to the FIFO ",w_buf); } /*fifo_read.c*/ #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define FIFO "/tmp/myfifo" main(int argc,char** argv) { char buf_r[100]; int fd; int nread; /*创建有名管道,并设置相应的权限*/ if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST)) printf("cannot create fifoserver "); printf("Preparing for reading bytes... "); memset(buf_r,0,sizeof(buf_r)); /*打开有名管道,并设置非阻塞标志*/ fd=open(FIFO,O_RDONLY|O_NONBLOCK,0); if(fd==-1) { perror("open"); exit(1); } while(1) { memset(buf_r,0,sizeof(buf_r)); /*读取管道中的字符串*/ if((nread=read(fd,buf_r,100))==-1){ if(errno==EAGAIN) printf("no data yet "); } printf("read %s from FIFO ",buf_r); sleep(1); } pause(); unlink(FIFO);//这个谁mkfifo创建谁释放
}
运行结果:
终端1:
[root@localhost ipc]# ./fifo_write 123123 write 123123 to the FIFO
终端2:
[root@localhost ipc]# ./fifo_read Preparing for reading bytes... read from FIFO read from FIFO read from FIFO read from FIFO read from FIFO read 123123 from FIFO read from FIFO
总结:
1.read没有数据read阻塞,read读取后数据被删除
2.数据有序,安装写入的顺序
3.打开的描述符号可以读写(two-way)
4.管道文件关闭后,数据不持久
5.管道的数据在内核缓冲
6.有名管道的名字仅仅是内核识别是否放回同一个fd的标识