IPC之FIFO(有名管道)详解

时间:2021-08-19 15:12:45


基本概念:

管道没有名字,因此它们的最大劣势是只能用于有一个共同祖先进程的各个进程之间。我们无法在无亲缘关系的进程间创建一个管道并将它用作IPC管道(不考虑描述符传递)。

FIFO指先进先出(first in,first out),它是一个单向(半双工)数据流。不同于管道的是,每个FIFO有一个路径名与之关联,从而允许无亲缘关系的进程访问同一个FIFO。FIFO也称为有名管道(named pipe)。


函数说明:

FIFO由mkfifo函数创建

MKFIFO(3)                  Linux Programmer's Manual                 MKFIFO(3)

NAME
mkfifo - make a FIFO special file (a named pipe)

SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
返回:若成功则为0,若出错则为-1


其中pathname是一个普通的路径名,它是该FIFO的名字。

mode参数指定文件权限位,类似于open的第三个参数。

mkfifo函数已隐含指定O_CREAT | O_EXCL。也就是说,它要么创建一个新的FIFO,要么返回一个EEXIST错误(如果所指定名字的FIFO已经存在)。要打开一个已存在的FIFO或创建一个新的FIFO,应先调用mkfifo,再检查它是否返回EEXIST错误,若返回该错误则改为调用open。

在创建出一个FIFO后,它必须打开来读,或者打开来写,所用的可以是open函数,也可以是某个标准I/O打开函数,例如open。FIFO不能打开来既读又写,因为它是半双工的。


如果对管道或FIFO调用lseek,那就返回ESPIPE错误。


注意:如果当前尚没有任何进程写打开某个FIFO,那么读打开该FIFO的进程将阻塞。

同理,如果当前尚没有任何进程读打开某个FIFO,那么写打开该FIFO的进程将阻塞。


例子:

启动两个不同的进程,通过两个管道来通信,需要注意到两个管道的读写顺序在两个进程中是不一样的。

这里的open打开的文件描述符默认是阻塞模式。

gcc fifo_server.c -o server

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#define MAXLINE 1024
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"

void Perror(const char *s)
{
perror(s);
exit(EXIT_FAILURE);
}

void server(int readfd, int writefd)
{
/* send msg */
int i = 0;
for (i; i<3; i++) {
char buff[MAXLINE] = {0};
sprintf(buff, "hello world %d", i);
int n = write(writefd, buff, strlen(buff));
sleep(1);
}
char buff[MAXLINE] = {0};
int n = read(readfd, buff, MAXLINE);
if (n > 0) {
printf("read from client:%s\n", buff);
}
}

int main()
{
int readfd, writefd;

/* create two FIFO; OK if they already exist */
if ((mkfifo(FIFO1, 0777) < 0) && (errno != EEXIST))
Perror("can't create FIFO1");
if ((mkfifo(FIFO2, 0777) < 0) && (errno != EEXIST)) {
unlink(FIFO1); /* rm FIFO1 */
Perror("can't create FIFO2");
}
printf("create fifo success\n");

/* 要注意open的顺序 */
readfd = open(FIFO2, O_RDONLY, 0);
writefd = open(FIFO1, O_WRONLY, 0);
printf("open fifo success\n");

/* 让FIFO在进程结束后自动删除 */
unlink(FIFO1);
unlink(FIFO2);

server(readfd, writefd);

return 0;
}

gcc fifo_client.c -o client

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#define MAXLINE 1024
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"

void Perror(const char *s)
{
perror(s);
exit(EXIT_FAILURE);
}

void client(int readfd, int writefd)
{
/* read msg */
int i = 0;
for (i; i<3; i++) {
char buff[MAXLINE] = {0};
int n = read(readfd, buff, MAXLINE);
if (n > 0) {
printf("read from server:%s\n", buff);
}
}
char *buff = "goodby server";
write(writefd, buff, strlen(buff));
}

int main()
{
int readfd, writefd;

/* create two FIFO; OK if they already exist */
if ((mkfifo(FIFO1, 0777) < 0) && (errno != EEXIST))
Perror("can't create FIFO1");
if ((mkfifo(FIFO2, 0777) < 0) && (errno != EEXIST)) {
unlink(FIFO1); /* rm FIFO1 */
Perror("can't create FIFO2");
}

/* 要注意open的顺序 */
writefd = open(FIFO2, O_WRONLY);
readfd = open(FIFO1, O_RDONLY);

client(readfd, writefd);

return 0;
}

运行结果:

IPC之FIFO(有名管道)详解


关于FIFO的大小:
经过测试,unubtu 14下的FIFO大小是64KB,与管道的大小一致。

IPC之FIFO(有名管道)详解


如果请求写入的数据的字节数小于或等于PIPE_BUF,那么write的操作保证的原子的。这意味着,如果有两个进程差不多同时往同一个管道或FIFO写,那么或者先写入来自第一个进程的所有数据,再写入来自第二个进程的所有数据,或者颠倒过来。系统不会互相混杂来自这两个进程的数据。然而,如果请求写入的数据的字节数大于PIPE_BUF,那么write操作不能保证是原子的。

可以通过getconf命令查看PIPE_BUF的大小

IPC之FIFO(有名管道)详解


关于数据持久性:当一个管道或FIFO的最后一次关闭发生时,仍在该管道或FIFO上的数据将被丢弃。


关于更多IPC之管道和FIFO的额外属性


参考:《unix网络编程》·卷2


End;