linux进程间通信–管道(PIPE & FIFO)
参考资料:
- overview of pipes and FIFOs:
man 7 pipe
- 《The Linux Programming inTerface》
管道一般分为无名管道pipe
和有名管道mkfifo
.都用于进程之间的通信。下面将一一介绍它们。
无名管道
无名管道
(pipe)一般用于关联进程(如父子进程)之间的通信。它的使用类似文件,但他不是普通文件,不属于某种文件系统。我们最常见的pipe使用是shell命令,如ls | wc -l
。pipe有如下特点:
半双工
:数据只能向一个方向流动.需要双方通信时,建立两个管道.关联进程
:只能用于关联进程之间。如父子进程或者兄弟进程之间通信。面向字节流
:从管道可读取任意大小的数据块。且只能顺序的读取,即不能使用lseek()随机访问管道中的数据。原子操作
:如果多个进程向一个管道写入数据,如果它们一次写入的字节不超过PIPE_BUF(在limits.h),就可以保证它们的数据不会混杂在一起。
管道在读写时的行为如下:
写入达到上限
:当写入管道的数据达到上限,则阻塞.直到管道数据被读走。然后在继续写入。上限默认为65536.可通过fcntl(fd, F_SETPIPE_SZ, size)
修改。读出为空
:从空的管道中读取数据,进程阻塞。直到至少有一个字节被写入该管道。如果管道的写端被关闭,那么读取数据的进程在读取完管道中剩余数据后将看到文件结束(end-of-file)。
使用函数pipe()创建管道,需要包含头文件unistd.h
.:
int pipe(int fildes[2])
- 创建一个管道,并将管道的读写端文件描述符(分别)放入filedes[0]和filedes[1]中。有了文件操作符后,我们就可以使用
write()
,read()
进行读写了。 - fildes:文件描述符数组,
fildes[0]
保存读文件描述符。fildes[0]
保存写文件描述符。 - return:if true:0,if false:-1.errno如下:
- EMFILE:进程打开文件过多
- ENFILE:整个系统中打开的文件太多了。
- 创建一个管道,并将管道的读写端文件描述符(分别)放入filedes[0]和filedes[1]中。有了文件操作符后,我们就可以使用
linux提供了非标准的pipe2()函数。功能比pipe()丰富。可自行查询了解。
利用管道进行进程间通信的原理如下图:
下面是一个pipe使用的例程,展示了父子进程之间的通信:
#include <unistd.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <error.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
void main(void)
{
int res;
int pipe_fd[2];
//1. 创建管道
res = pipe(pipe_fd);
if(res == -1){
printf("pipe error\n");
exit(1);
}
//2. 创建线程
pid_t pid = fork();
if(pid == 0){//子进程
char buf[24];
//3. 子进程用于读,所以关掉写文件描述符
if (close(pipe_fd[1]) == -1){
printf("pipe pipe_fd[1] close error\n");
exit(1);
}
//4. 等待接收父进程的数据
while (1){
ssize_t num = read(pipe_fd[0], buf, 24);
if(num == 0){
break;
}
else if (num == -1){
printf("read error\n");
exit(1);
}
write(STDOUT_FILENO, buf, num);//打印到控制台
}
write(STDOUT_FILENO, "\n", 1);
//5. 关掉读文件描述符
if (close(pipe_fd[0]) == -1){
printf("pipe pipe_fd[0] close error\n");
exit(1);
}
exit(0);
}
else{//父进程
//6. 父进程用于写,所以关掉读文件描述符
if (close(pipe_fd[0]) == -1){
printf("pipe pipe_fd[0] close error\n");
exit(1);
}
//7. 发送相关数据
char write_buff[] = "hello word";
write(pipe_fd[1], write_buff,sizeof(write_buff));
//8. 关掉写文件描述符
if (close(pipe_fd[1]) == -1){
printf("pipe pipe_fd[1] close error\n");
exit(1);
}
wait(NULL);
exit(0);
}
}
有名管道
有名管道
(FIFO)是无名管道(pipe)的一种升级版。主要区别如下:
- 有名管道有文件名称且在文件系统中可见。
- 由于有名称,所以可以在任意进程下建立通信。
有名管道在读写时的行为如下:
- 读取FIFO文件的进程只能以
RDONLY
方式打开FIFO文件. - 写FIFO文件的进程只能以
WRONLY
方式打开FIFO.
其他与无名管道相似,例如:
- 不允许文件定位,只能顺序访问。
- 面向字节流。
管道在进行读写时必须都处于打开状态
:这句话说的比较模糊需要解释一下。笔者测试后得到如下结论:在open()函数打开fifo文件阶段
:写方式的open()会阻塞,等待读方式的open()打开fifo文件。这个阶段后管道的读写端便处于同时打开状态。即满足上边的条件。使用read(),write()操作fifo阶段
:在fifo读写端都打开之后(即满足第一个条件后),便可以进行读写操作了。行为与pipe相同写入达到上限
:当写入管道的数据达到上限,则阻塞.直到管道数据被读走。然后在继续写入。上限默认为65536.读出为空
:从空的管道中读取数据,进程阻塞。直到至少有一个字节被写入该管道。如果管道的写端被关闭,那么读取数据的进程在读取完管道中剩余数据后将看到文件结束(end-of-file)。
在读写过程中,如果close()了读取管道的文件操作符
:此时进行写操作会触发SIGPIPE
信号,如果信号被处理或阻塞,它会以错误代码EPIPE失败。在读写过程中,如果close()了写入管道的文件操作符
:读取数据的进程在读取完管道中剩余数据后将看到文件结束(end-of-file)。
使用mkfifo()函数创建一个命名管道。使用时需要包含头文件sys/stat.h
。函数如下:
int mkfifo (const char *filename, mode t mode)
- 创建一个命名管道.
- filename:文件名
- mode:文件权限。如
0666
- return:if true:0,if false:-1.errno如下:
- EEXIST:名称已经存在
其余的函数和文件操作函数相同。
演示例程如下:
#include <unistd.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <error.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
void main(void)
{
int res;
//1. 创建fifo
res = mkfifo("/tmp/fifo", 0666);
if(res == -1 && errno != EEXIST){
printf("mkfifo error\n");
exit(1);
}
//2. 进程之间使用fifo通信,为了方便直接使用父子进程
pid_t pid = fork();
if(pid == 0){//子进程
//3. 打开fifo
int fd = open("/tmp/fifo", O_RDONLY);
if(fd == -1){
printf("(%d)open error\n",getpid());
exit(1);
}
char buff[32];
while(1){
//4. 读取fifo的内容
ssize_t num = read(fd, buff, sizeof(buff));
if(num == 0){
break;
}
else if (num == -1){
printf("read error\n");
exit(1);
}
write(STDOUT_FILENO,buff,num);
}
write(STDOUT_FILENO, "\n", 1);
//5. 关掉读文件描述符
if (close(fd) == -1){
printf("(%d) close error\n",getpid());
exit(1);
}
}
else{
//6. 父进程以只写方式打开
int fd = open("/tmp/fifo", O_WRONLY);
if (fd == -1){
printf("(%d)open error\n",getpid());
exit(1);
}
//7. 发送相关数据
char write_buff[] = "hello word";
write(fd, write_buff,sizeof(write_buff));
//8. 关掉写文件描述符
if (close(fd) == -1){
printf("(%d) close error\n",getpid());
exit(1);
}
wait(NULL);
exit(0);
}
}
buff,sizeof(write_buff));
//8. 关掉写文件描述符
if (close(fd) == -1){
printf("(%d) close error\n",getpid());
exit(1);
}
wait(NULL);
exit(0);
}
}
关于技术交流
此处后的文字已经和题目内容无关,可以不看。
qq群:825695030
微信公众号:嵌入式的日常
如果上面的文章对你有用,欢迎打赏、点赞、评论。