awakeBird Back-end Dev Engineer

Linux基础(八)管道与FIFO

2018-12-24

该系列文章为《Linux/Unix系统编程手册》的学习笔记,由于该书太过冗长,属于工具书的类别,这里对书中的一些核心内容加以提炼和整理。 书中的编程练习这里不做展示和说明。

管道是一个单向、容量有限的字节流,用来在相关进程之间传递数据。FIFO是管道的变体,他们之间的一个重要区别在于FIFO可以用于任意进程间的通信。

管道

管道有以下几个特性:

  • 是一个字节流:即拥有字节流的几个特点:
    • 管道中是不存在消息或者消息边界的概念的,可以从管道中读取任意大小的数据块,而不管写入管道的数据大小。
    • 通过管道传输的数据是有序的,顺序为写入管道的数据顺序。
    • 在管道中无法通过lseek()随机访问数据。
  • 是单向的:一端用于写入,另一端用于读取。
  • 是容量有限的:管道是一个维护在内核内存中的缓冲器,一旦管道被填满,后续的写入操作会被阻塞直到读者从管道中移除了一些数据。

管道中的读取写入行为:

  • 从管道中读取数据字节为n:
    • 若管道内数据字节p=0:若写入端没有全部关闭,读取操作将被阻塞;若写入端全部关闭,将会读到文件结束(即read()调用返回0)。
    • 若管道内数据字节p<n:读取p字节后阻塞。
    • 若管道内数据字节p>n:读取n字节。
  • 向管道内写入数据:
    • 管道存在一个PIPE_BUF,如果写入的字节数不超过这个值,管道可以保证原子写入。如果超过这个值,内核可能会将数据分成几段来传输,如果存在多个进程写入,那么写入的数据可能会交叉。
    • 如果写入时已经没有读取端,写入端进程将受到SIGPIPE信号,默认终止进程,write()调用也将返回EPIPE错误。

创建及使用管道

int pipe(int filedes[2]);

调用pipe()系统调用会在数组filedes中返回两个打开的文件描述符,filedes[0]用于管道的读取端filedes[1]用于管道的写入端

管道用于父子进程或兄弟进程间的通信,方法是在调用pipe()之后调用fork(),子进程会继承父进程的文件描述符的副本,之后一个进程应立即关闭管道写入端的文件描述符,另一个则应关闭读取端的文件描述符。

使用管道连接过滤器

ls | grep filename

shell中常见的过滤器即采用管道实现:将前一个进程的标准输出连接到管道的写入端,将后一个进程的标准输入连接到管道的读取端,可以采用dup2()调用关闭标准输入(1)或标准输出描述符(2),并将管道的响应文件描述符复制到1和2上。

这里复习一下文件I/O的知识,dup2(oldfd, newfd)系统调用会确保oldfd复制到newfd上,如果newfd已经打开,调用会关闭newfd所指的文件描述符,并忽略关闭过程中的所有错误。

多数情况下进程会默认打开0、1、2的文件描述符,但为了不依赖这个前提,复制的过程应该如下所示:

int pfd[2];
pipe(pfd);

if (pfd[1] != STDOUT_FILENO) {
    dup2(pfd[1], STDOUT_FILENO);
    close(pfd[1]);
}

FIFO

FIFO的作用与管道类似,不过其存在于文件系统中,打开方式与打开一个普通文件一致,这样就实现了非相关进程间的通信。

创建及使用

int mkfifo(const char *pathname, mode_t mode);

mkfifo()调用会创建一个FIFO文件,并制定其权限掩码mode。可以通过open()系统调用打开FIFO:

  • 设置O_RDONLY标记打开一个FIFO以便读取数据,open()调用将会阻塞直到另一个进程打开FIFO以写入数据。
  • 设置O_WRONLY标记打开一个FIFO以便写入数据,open()调用将会阻塞直到另一个进程打开FIFO以读取数据。

为了防止进程打开多个FIFO时产生死锁,在打开FIFO时应设置O_NONBLOCK标记来实现非阻塞I/O。如果采用这种方式调用write()向FIFO中写入数据时,若没有相应进程打开FIFO用以读取,这个调用将会失败。

(End)


Comments

Content