匿名管道只能用于拥有亲缘关系的两个进程,比如父子进程和兄弟进程,不相关的进程需要使用命名管道进行通讯。
mkfifo my_fifo
ls -l
当前文件夹下多了一个文件my_fifo
prw-rw-r-- 1 heng heng 0 1月 18 10:51 my_fifo|
打开第一个终端,输入下面命令打印文件内容,但进程进入了阻塞:
cat my_fifo
打开第二个终端,输入下面命令将字符串写入文件:
echo "hello" > my_fifo
最终的结果是第一个终端cat命令输出了hello字符串,两个进程进行了一次进程间通讯。
如果没有打开第一个终端,直接执行第二个终端,echo会阻塞。
两个进程之间使用了FIFO文件,也即是命名管道进行通讯。
Linux下一切皆文件,用1个文件实现了进程间通讯,挺神奇。
open/read/write管道文件
命名管道是一个文件,它可以用open、write、read等系统调用进行操作。通过系统调用open,可以获取文件的描述符。
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
首先关心flags参数,描述打开文件所采用的动作
- O_RDONLY 只读
- O_WRONLY 只写
- O_RDWR 读写
除此之外,可以组合一些可选的模式(按位或),比如:
- O_APPEND 写入追加文件末尾
- O_NONBLOCK 不阻塞
- O_CREAT 创建文件
如果open成功,返回一个可以用于read/write的文件描述符。如果失败,返回-1,用perror打印一下错误原因。
管道使用的目的是让数据从一个进程传递到另一个进程,它是单向的,所以O_RDWR没有意义。
read一个管道,如果它没有write数据,read进程会阻塞,直到有另一个进程write。同理,只有write没有read也会阻塞。因此,可以叠加使用O_NONBLOCK添加不阻塞这个特性。
代码附在文末,可以通过输入命令行参数,验证O_RDONLY、O_WRONLY、O_NONBLOCK单独使用或者组合使用的效果。
- 测试1:单独使用O_RDONLY或者O_WRONLY,程序阻塞在open,直到另一个程序执行管道读或者写
- 测试2:组合使用O_RDONLY和O_NONBLOCK,open会马上返回成功,尽管没有其他进程写管道
- 测试3:组合使用O_WRONLY和O_NONBLOCK,分为两种情况:一是没有进程读管道,open会返回错误;二是有进程读管道,open返回文件描述符,可以正常进行操作
得到命名管道文件的fd后,接下来的read/write操作和普通文件一样,不说了。
管道有多大
ls -l查看命名管道文件的大小永远是0,因为它本质是内存上的一块空间,它有多大呢?
man 7 pipe
帮助文件里描述了管道的大小,不同版本有些区别,现在最新版默认65536。PIPE_BUF定义了每个缓冲区大小是4K,内核最大分配16个PIPE_BUF,因此管道最大容量是64K。
参考网上的例子,文末附带一个程序向管道逐字符写入,直到写满,写入的数量就是管道的最大容量。最终,执行程序输出结果是capacity=65536。
使用管道不仅要考虑一次性写入管道的最大数据量,也需要考虑多个进程同时向管道写入,这时候,怎样保证数据不错乱。
man里说得很清楚,当写入数据量不大于PIPE_BUF时,将保证写入的原子性,程序有机会再写。
代码1:验证打开管道文件的几种模式
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "my_fifo"
int main(int argc, char *argv[])
{
// printf("argc:%d\n", argc);
// char j = argv;
// for (int i = 0; i < argc; i++)
// {
// printf("argv[%d]=%s\n", i, j);
// j++;
// }
if (argc < 2)
{
fprintf(stderr, "use O_RDONLY/O_WRONLY/O_NONBLOCK\n");
exit(EXIT_FAILURE);
}
int open_mode = 0;
for (int i = 1; i < argc; i++)
{
if (strncmp(*++argv, "O_RDONLY", 8) == 0)
{
open_mode |= O_RDONLY;
printf("index %d get O_RDONLY\n", i);
}
if (strncmp(*argv, "O_WRONLY", 8) == 0)
{
open_mode |= O_WRONLY;
printf("index %d get O_WRONLY\n", i);
}
if (strncmp(*argv, "O_NONBLOCK", 10) == 0)
{
open_mode |= O_NONBLOCK;
printf("index %d get O_NONBLOCK\n", i);
}
}
//check fifo file exited
if (access(FIFO_NAME, F_OK) == -1)
{
int res = mkfifo(FIFO_NAME, 0777);
if (res != 0)
{
fprintf(stderr, "create fifo failed %s\n", FIFO_NAME);
exit(EXIT_FAILURE);
}
}
printf("process %d opening FIFO, open_mode:%d\n", getpid(), open_mode);
int res = open(FIFO_NAME, open_mode);
printf("process %d result %d\n", getpid(), res);
sleep(5);
if (res != -1)
{
close(res);
}
printf("finished\n");
exit(EXIT_SUCCESS);
}
代码2:验证管道的最大容量
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int count = 0;
int fd[2];
if (pipe(fd) == -1)
{
perror("pipe error");
exit(EXIT_FAILURE);
}
//修改文件描述符的特性为非阻塞
int flags = fcntl(fd[1], F_GETFL);
fcntl(fd[1], F_SETFL, flags | O_NONBLOCK);
while (1)
{
int ret = write(fd[1], "a", 1);
if (ret == -1)
{
printf("write error\n");
break;
}
count++;
}
printf("capacity=%d\n", count);
return 0;
}