Linux进程间通信(IPC)——管道和FIFO

发布时间 2023-08-20 00:37:32作者: 云的边界

管道和FIFO

一、 匿名管道

#include <unistd.h>
int pipe(int fd[2]); /* fd[0] 管道输出端FD, fd[1]管道输入端FD */

int pipe2(int pipefd[2], int flags); /* flags:O_NONBLOCK */

二、FIFO(有名管道,半双工)

支持同台主机内无亲缘关系的进程间建立管道。只能用在本地文件系统中,不适用NFS。

  1. 创建FIFO
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

#include <fcntl.h>           /* Definition of AT_* constants */
#include <sys/stat.h>
int mkfifoat(int dirfd, const char *pathname, mode_t mode);

若pathname已经存在,返回-1,errno为 EEXIST。创建FIFO后,可用open、或者标准IO,如fopen打开,一个fd或为读、或为写。
​注意:open为读时会阻塞(以O_NONBLOCK打开除外),直到有open为写;反之亦然。

举例:如下代码会引起死锁

/* Process 1 */
readfd  = open(FIFO_PATH1, O_RDONLY, 0); /* 阻塞直到有 open FIFO_PATH1为写 */
writefd = open(FIFO_PATH2, O_WRONLY, 0); 

/* Process 2*/
readfd  = open(FIFO_PATH2, O_RDONLY, 0); /* 阻塞直到有 open FIFO_PATH2为写 */
writefd = open(FIFO_PATH1, O_WRONLY, 0); 
  1. 销毁FIFO
    只能用unlink unlinkat 将FIFO名字从文件系统中删除。
#include <unistd.h>
int unlink(const char *pathname);

#include <fcntl.h>           /* Definition of AT_* constants */
#include <unistd.h>
int unlinkat(int dirfd, const char *pathname, int flags);
  1. FIFO 一读多写
/* 服务端 FIFO读端 */
iret = mkfifo("/tmp/fifo.server", 0777);
readfd  = open("/tmp/fifo.server", O_RDONLY, 0);
/* 始终保持一个写端,防止每次客户端关闭写端后,fifo读空后,read一直返回0,不会阻塞读其他客户端的写 */
dummyfd = open("/tmp/fifo.server", O_WRONLY, 0); 
while(1)
{
    iret = read(readfd, buf, 1024);
}

/* 客户端 FIFO写端 */
writefd  = open("/tmp/fifo.server", O_WRONLY, 0);
iret = write(writefd, buf, strlen(buf));
close(writefd); /* 写端关闭,客户端退出 */
exit(0); 

三、popen 和 pclose

#include <stdio.h>

FILE *popen(const char *command, const char *type);

int pclose(FILE *stream);

type 为 r,调用进程读进 command的标准输出;
type 为 w, 调用进程写到 command的标准输入。

四、Unix域套接字(d管道)

socketpair可以创建一对无命名的、相互连接的Unix域套接字,起到双向管道的作用,其中type:SOCK_STREAM、SOCK_DGRAM。因为无命名,所以只能用于有亲缘关系的进程间通信。

UNIX域数据报服务是可靠的,可以保持消息边界。

#include <sys/types.h>        
#include <sys/socket.h>
int socketpair(AF_UNIX, int type, int protocol, int fd[2]);

五、管道(pipe)和FIFO的属性

  1. 管道(含FIFO)的容量
    (1)管道容量的大小默认64KB,/proc/sys/fs/pipe-max-size since Linux 2.6.35,规定了可设置的最大容量大小;
    (2)设备/获取管道容量的大小:
#include <unistd.h>
#include <fcntl.h>
iret   = fcntl(pipefd[i], F_SETPIPE_SZ, lSize);

lSize = fcntl(pipefd[i], F_GETPIPE_SZ);
  1. 读写时另一端关闭
    (1) read一个写端已被关闭的管道时, 在所有数据都被读取后,read返回0。
    (2) write一个读端已被关闭的管道(或者socket),则产生信号SIGPIPE,如果忽略该信号或者捕捉该信号从其处理函数返回后,write返回-1,errno值为EPIPE。

  2. 管道和FIFO的原子性
    man 7 pipe查看如下:

     O_NONBLOCK disabled, n <= PIPE_BUF     空间不足写入时,阻塞。
               All n bytes are written atomically; write(2) may block if there is not room for n bytes to be  writ‐
               ten immediately
    
     O_NONBLOCK enabled, n <= PIPE_BUF      空间不足写入时,返回失败,1个字节都不会写入。
               If  there  is  room  to write n bytes to the pipe, then write(2) succeeds immediately, writing all n
               bytes; otherwise write(2) fails, with errno set to EAGAIN.
    
     O_NONBLOCK disabled, n > PIPE_BUF    不能保证全部数据原子性写入,write阻塞直到全部数据被写入
               The write is nonatomic: the data given to write(2)  may  be  interleaved  with  write(2)s  by  other
               process; the write(2) blocks until n bytes have been written.
    
     O_NONBLOCK enabled, n > PIPE_BUF     1个字节空间都没有时 write返回失败,否则返回实际能写入的字节数。
               If  the  pipe  is full, then write(2) fails, with errno set to EAGAIN.  Otherwise, from 1 to n bytes
               may be written (i.e., a "partial write" may occur; the caller should check  the  return  value  from
               write(2)  to  see  how  many  bytes  were actually written), and these bytes may be interleaved with
               writes by other processes.
    
  3. 获取PIPE_BUF
    (1)ulimit -a
    (2)动态获取

#include <unistd.h>
long pathconf(fifo_path, _PC_PIPE_BUF); /* 一般为4KB */

(3) shell命令

getconf   PIPE_BUF   fifo_path
  1. 获取OPEN_MAX(进程可打开的最大FD数量)
    (1) 动态获取
    (2) shell命令
getconf OPEN_MAX
  1. IO复用调度处理
    当发生读写错误时(一端关闭),select(poll、epoll同样)返回说相应的FD是可读的(或可写的),真正的错误则由read(或write)返回。
    另外,unlink后的路径再open会失败,但不影响在unlink前open获取的FD。