首页 > 代码库 > IPC通信之管道

IPC通信之管道

管道

Linux管道(pipe)提供一种单向(半双工)的进程间通讯机制。管道有一个读端和一个写端。从写端写入的数据可以重读端读出来。函数pipe()创建一个管道,返回两个文件描述符,fd[0]是读端,f[1]是写端。如下图


管道的读写可以使用Linux标准IO操作接口进行,例如read、write等。从图1不难看出,数据一直缓存在内核中直到被读取出来。

单个进程的管道几乎没有任何用处。通常调用pipe的进程接着调用fork,这样就创建了从父进程到子进程的IPC通道。如下图



缺省情况下,管道工作在阻塞方式下。进程读取管道时,如果管道为空,则读操作一直阻塞直到数据到达;进程写管道时,如果管道已满,写操作会阻塞进程直到足够数据被读出。

可以使用fcntl(fd, F_SETFL, O_NONBLOCK)设置管道工作为非阻塞IO方式。管道为空时,read接口会立即返回-1,errno设置为EAGAIN;管道满时,write接口会立即返回-1;errno设置为EAGAIN。

如果所有写端关闭,读端read操作会返回0(EOF)。如果所有读端关闭,write操作会给调用进程发送SIGPIPE信号,如果调用进程忽略SIGPIPE信号,则write操作会返回失败,并设置errno为EPIPE。使用pipe和fork的进程需要正确使用close关闭多于的fd,以确保EOF和SIGPIPE/EPIPE传递正确。

其他注意事项:

1、管道是字节流通信,没有消息边界的概念;

2、管道只能在具有共同祖先的进程间使用;

3、不能对管道进行lseek操作;

实际使用中,还有两个概念对理解管道至关重要。一个是管道容量。另一个是管道操作原子性。

管道容量有限。如果管道满,阻塞方式下write操作会阻塞,非阻塞方式下会返回失败。不同的系统有不同的管道容量限制。应用模块不应该依赖特定的容量限制,正确的设计是:一旦数据到达进程应尽快消费数据,避免写进程长时间阻塞。从linux 2.6.11版本开始,管道容量是65536字节。

POSIX 1-2001规定向管道写小于PIPE_BUF字节长度的数据时原子操作;写超过PIPE_BUF字节长度的数据不是原子操作。Linux上PIPE_BUF是4096字节,更细致的描述:

1、阻塞方式,n<=PIPE_BUF(n为写入的字节数,下同):写操作是原子操作,如果pipe空间不足则阻塞。

2、非阻塞方式,n<=PIPE_BUF:写操作是原子操作,如果pipe空间不足,则失败,errno设置为EAGAIN。

3、阻塞方式,n>PIPE_BUF:写操作不是原子操作,写入的数据可能与其他进程写入的交叉排列,写操作阻塞直到所有数据写完。

4、非阻塞方式,n>PIPE_BUF:写操作不是原子操作,如果pipe空间不足,则失败,errno设置为EAGAIN。写入的数据可能与其他进程写入的数据交叉排列。同时实际写入可能小于n(部分写入);调用者应该检查write实际写入的长度。

命名管道

FIFO又称命名管道。它使用mkfifo创建,使用open打开。只要有权限任何进程都可以打开一个管道,读端使用O_RDONLY标准打开FIFO,写端使用O_WRONLY标准打开FIFO。

FIFO与管道的唯一差别是创建和打开方式不同,在它们之上的IO操作时完全相同。一个给定的FIFO有多个写进程是很常见的。这就意味着如果不希望多进程所写的的数据互相穿插,则需要考虑原子写操作(如管道相同)。

应用场景

管道的有点是编程简单易用。

管道也存在一些不足:

1、需要通过内核传递数据

2、管道容量(64k)和PIPE_BUF不能修改

3、管道传递的是流。同步方式下编程比较复杂

因此管道适用于那些数据量少,性能要求不高的场合。

举例:

管道

#include <stdio.h>
#include <unistd.h>
int
main(void)
{
	int		n;
	int		fd[2];
	pid_t	pid;
	char	line[MAXLINE];

	if (pipe(fd) < 0)
		printf("pipe error");
	if ((pid = fork()) < 0) {
		printf("fork error");
	} else if (pid > 0) {		/* parent */
		close(fd[0]);
		write(fd[1], "hello world\n", 12);
	} else {					/* child */
		close(fd[1]);
		n = read(fd[0], line, MAXLINE);
		write(STDOUT_FILENO, line, n);
	}
	exit(0);
}
命名管道
读端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>

#define FIFO_NAME "myfifo"
#define BUF_SIZE 1024

int main(void)
{
	int fd;
	char buf[BUF_SIZE];

	umask(0);    //设置允许当前进程创建文件或者目录最大可操作的权限
	fd = open(FIFO_NAME,O_RDONLY);
	read(fd,buf,BUF_SIZE);
	printf("Read content: %s\n",buf);
	close(fd);
	
	return 0;
}
写端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "myfifo"
#define BUF_SIZE 1024

int main(void)
{
	int fd;
	char buf[BUF_SIZE] = "Hello Reader,I come from process named Writer!";

	umask(0);             //设置允许当前进程创建文件或者目录最大可操作的权限

	if (mkfifo(FIFO_NAME,S_IFIFO|0666) == -1)
    {
      perror("mkfifo error!");
      exit(1);
    }

	if ((fd = open(FIFO_NAME,O_WRONLY)) == -1)
    {
      perror("fopen error!");
      exit(1);
    }
	write(fd,buf,strlen(buf)+1);
	close(fd);
	
	return 0;
}

IPC通信之管道