Linux 进程间通信之管道(pipe),(fifo)

时间:2022-05-23 07:11:46

 无名管道(pipe)

管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;

定义函数: int pipe(int filedes[2])

filedes[0]为管道里的读取端

filedes[1]则为管道的写入端。

实现机制:

管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

Linux 进程间通信之管道(pipe),(fifo)

从原理上,管道利用fork机制建立,从而让两个进程可以连接到同一个PIPE上。最开始的时候,上面的两个箭头都连接在同一个进程Process 1上(连接在Process 1上的两个箭头)。当fork复制进程的时候,会将这两个连接也复制到新的进程(Process 2)。随后,每个进程关闭自己不需要的一个连接 (两个黑色的箭头被关闭; Process 1关闭从PIPE来的输入连接,Process 2关闭输出到PIPE的连接),这样,剩下的红色连接就构成了如上图的PIPE

Linux 进程间通信之管道(pipe),(fifo)

一、管道读写注意点

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[];
pid_t pid;
char buf_r[];
char* p_wbuf;
int r_num;
memset(buf_r,,sizeof(buf_r));
if(pipe(pipe_fd)<)
{
printf("pipe create error\n");
return -;
}
if((pid=fork())==) //若是子进程
{
printf("\n");
/*关闭子进程管道写端。睡眠2秒,确保父进程已相应地关闭了管道读端*/
close(pipe_fd[]);
sleep();
/*子进程读取管道内容*/
if((r_num=read(pipe_fd[],buf_r,))>){
printf( "%d numbers read from the pipe is %s\n",r_num,buf_r);
}
/*关闭子进程读端*/
close(pipe_fd[]);
exit();
}
else if(pid>)
{ /*关闭父进程读端*/
close(pipe_fd[]);
/*分两次向管道写入数据*/
if(write(pipe_fd[],"Hello",)!=-)
printf("parent write1 success!\n");
if(write(pipe_fd[]," Pipe",)!=-)
printf("parent write2 success!\n");
/*关闭父进程写端并睡眠3秒,让子进程读数据*/
close(pipe_fd[]);
sleep();
/*收集子进程退出信息*/
waitpid(pid,NULL,);
exit();
}
}

运行结果如下:

[root@localhost ipc]# ./pipe_rw 
parent write1 success!
parent write2 success!
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) );

其中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[];
int nwrite; if(fd==-)
if(errno==ENXIO)
printf("open error; no reading process\n");
/*打开有名管道,并设置为非阻塞*/
fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,);
if(argc==)
printf("Please send something\n");
strcpy(w_buf,argv[]);
/*向管道写入字符串*/
if((nwrite=write(fd,w_buf,))==-)
{
if(errno==EAGAIN)
printf("The FIFO has not been read yet.Please try later\n");
}
else
printf("write %s to the FIFO\n",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[];
int fd;
int nread; /*创建有名管道,并设置相应的权限*/
if((mkfifo(FIFO,O_CREAT|O_EXCL)<)&&(errno!=EEXIST))
printf("cannot create fifoserver\n");
printf("Preparing for reading bytes...\n"); memset(buf_r,,sizeof(buf_r));
/*打开有名管道,并设置非阻塞标志*/
fd=open(FIFO,O_RDONLY|O_NONBLOCK,);
if(fd==-)
{
perror("open");
exit();
}
while()
{
memset(buf_r,,sizeof(buf_r));
/*读取管道中的字符串*/
if((nread=read(fd,buf_r,))==-){
if(errno==EAGAIN)
printf("no data yet\n");
}
printf("read %s from FIFO\n",buf_r);
sleep();
}
pause();
unlink(FIFO);//这个谁mkfifo创建谁释放
}

运行结果:

终端1:

[root@localhost ipc]# ./fifo_write
write 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 from FIFO
read from FIFO

总结:

1.read没有数据read阻塞,read读取后数据被删除

2.数据有序,安装写入的顺序

3.打开的描述符号可以读写(two-way)

4.管道文件关闭后,数据不持久

5.管道的数据在内核缓冲

6.有名管道的名字仅仅是内核识别是否放回同一个fd的标识