在 Linux进程间通信—管道(无名管道、pipe)一文中介绍了匿名管道的使用,但是其中有一个明显的缺陷,匿名管道只能用于有亲缘关系的进程之间通信,命名管道则解决了这个缺陷,可以在没有亲缘关系的2个进程之间进行通信。
命名管道(named pipe) 也被成为FIFO文件,是一种特殊类型的文件。在文件系统中可以找到实在的文件与之对应,这样进程可以像访问文件一样对管道进行访问,大部分文件访问的API(open、read、write)也可以用于命名管道的访问。
命名管道的行为和匿名管道基本一致,也存在如下特点:
#include
#include
/* 创建fifo文件
* @filename: 文件名
* @mode: 文件的读写权限
* return: 成功返回0, 失败返回-1, 并正确设置errno
*/
int mkfifo(const char *filename, mode_t mode);
#include
#include
/* 打开管道文件
* @filename: 文件名
* @oflag: 打开方式,O_WRONLY, O_RDONLY,O_NONBLOCK等
* return: 成功返回非负的文件描述符, 失败返回-1, 并正确设置errno
*/
int open(const char *path, int oflag, ... );
#include
/* 读取管道文件
* @fildes: 文件描述符
* @buf: 存储结果的内存
* @nbyte: 最大读取的字节数
* return: 成功返回非负的读取的字节数, 失败返回-1, 并正确设置errno
*/
ssize_t read(int fildes, void *buf, size_t nbyte);
#include
/* 向管道文件写入数据
* @fildes: 文件描述符
* @buf: 需要写入的数据
* @nbyte: 最大写入的字节数
* return: 成功返回非负的读取的字节数, 失败返回-1, 并正确设置errno
*/
ssize_t write(int fildes, void *buf, size_t nbyte);
mkfifo
创建一个FIFO文件,值得注意的是,这是一个***真实的文件系统中的文件***。用open
打开一个管道,并通过read
和write
进行文件读写。这些操作和直接操作文件基本没有任何区别。
同匿名管道相比,访问命名管道多了一个open
的操作。直接看例子:
pipe_read.c: 每隔2s,从管道中读取128个字节,pipe_write.c:每隔1s从管道中读取128字节。
// pipe_read.c
#include
#include
#include
#include
#include
#include
#include
#include
#define err_print(fmt, args...) do { print_now(); printf("[E_NO: %d, %s]", errno, strerror(errno)); printf(fmt, ##args ); printf("\n");} while(0)
#define dbg_print(fmt, args...) do { print_now(); printf("[file: %s, line: %d]", __FILE__, __LINE__); printf(fmt, ##args ); printf("\n"); } while(0)
void print_now()
{
time_t rawtime;
struct tm * timeinfo;
char *t;
time( &rawtime );
timeinfo = localtime( &rawtime );
t = asctime(timeinfo);
t[strlen(t) - 1] = 0;
printf("[%s] ", t);
}
int main(int argc, char *argv[])
{
int fd;
int ret;
int len;
ret = mkfifo("my_fifo", 0666);
if(ret != 0)
{
err_print("mkfifo");
if(EEXIST != errno)
exit(-1);
}
// 只读并指定阻塞方式打开(默认)
fd = open("my_fifo", O_RDONLY);
if(fd<0)
{
err_print("open fifo");
exit(-1);
}
dbg_print("open fifo ok.");
while(1)
{
char recv[129] = {0};
// 缓冲区空的情况下,会阻塞
len = read(fd, recv, sizeof(recv) - 1);
if (len <= 0)
{
dbg_print("read from my_fifo buf=%d [%s]",len, recv);
continue;
}
recv[len] = '\0';
dbg_print("read from my_fifo buf=%d [%s]",len, recv);
sleep(2);
}
return 0;
}
// pipe_write.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define err_print(fmt, args...) do { print_now(); printf("[E_NO: %d, %s]", errno, strerror(errno)); printf(fmt, ##args ); printf("\n");} while(0)
#define dbg_print(fmt, args...) do { print_now(); printf("[file: %s, line: %d]", __FILE__, __LINE__); printf(fmt, ##args ); printf("\n"); } while(0)
static void sig_handler(int signo)
{
dbg_print("get signal %d", signo);
}
void print_now()
{
time_t rawtime;
struct tm * timeinfo;
char *t;
time( &rawtime );
timeinfo = localtime( &rawtime );
t = asctime(timeinfo);
t[strlen(t) - 1] = 0;
printf("[%s] ", t);
}
int main(int argc, char *argv[])
{
int fd;
int ret;
int len;
int times;
struct sigaction psa;
psa.sa_handler = sig_handler;
// 默认情况下,SIGPIPE会导致进程退出,在这里我们捕获并输出该信号
sigaction(SIGPIPE, &psa, NULL);
// 创建命名管道
ret = mkfifo("my_fifo", 0666);
if(ret != 0)
{
err_print("mkfifo");
if(EEXIST != errno)
exit(-1);
}
// 只写并指定阻塞方式打开(默认)
fd = open("my_fifo", O_WRONLY);
if(fd<0)
{
err_print("open fifo");
exit(-1);
}
dbg_print("open fifo ok.");
char send[129];
send[sizeof(send) - 1] = '\0';
times = 0;
len = 0;
while(1)
{
// 缓冲区满的情况下,会阻塞
len = write(fd, send, sizeof(send) - 1);
if (len < 0)
{
err_print("write error");
}
dbg_print("%d write to my_fifo buf len: %d",times, len);
sleep(1);
times ++;
}
return 0;
}
# 分别编译
$ gcc pipe_read.c -o pipe_read
$ gcc pipe_read.c -o pipe_read
# 在终端1中执行write动作
$ date; ./pipe_write
2019年 03月 15日 星期五 10:57:08 CST
[Fri Mar 15 10:57:08 2019] [E_NO: 17, File exists]mkfifo
# 等待6s以后才有open成功的输出,这个时间恰好是pipe_read执行的时间,说明open阻塞到了现在;
# 先执行pipe_read再执行pipe_write的效果是一样的
[Fri Mar 15 10:57:14 2019] [file: pipe_write.c, line: 61]open fifo ok.
[Fri Mar 15 10:57:14 2019] [file: pipe_write.c, line: 75]0 write to my_fifo buf len: 128
[Fri Mar 15 10:57:15 2019] [file: pipe_write.c, line: 75]1 write to my_fifo buf len: 128
[Fri Mar 15 10:57:16 2019] [file: pipe_write.c, line: 75]2 write to my_fifo buf len: 128
[Fri Mar 15 10:57:17 2019] [file: pipe_write.c, line: 75]3 write to my_fifo buf len: 128
[Fri Mar 15 10:57:18 2019] [file: pipe_write.c, line: 75]4 write to my_fifo buf len: 128
[Fri Mar 15 10:57:19 2019] [file: pipe_write.c, line: 75]5 write to my_fifo buf len: 128
# 因pipe_read关闭, 继续写入时收到了SIGPIPE(13)信号,此处如果我们不捕获该信号,默认进程退出
[Fri Mar 15 10:57:20 2019] [file: pipe_write.c, line: 16]get signal 13
# 此时write函数返回了-1,并设置了EPIPE(32)错误
[Fri Mar 15 10:57:20 2019] [E_NO: 32, Broken pipe]write error
[Fri Mar 15 10:57:20 2019] [file: pipe_write.c, line: 75]6 write to my_fifo buf len: -1
[Fri Mar 15 10:57:21 2019] [file: pipe_write.c, line: 16]get signal 13
[Fri Mar 15 10:57:21 2019] [E_NO: 32, Broken pipe]write error
[Fri Mar 15 10:57:21 2019] [file: pipe_write.c, line: 75]7 write to my_fifo buf len: -1
# 在终端2中执行read动作
$ date; ./pipe_read
# 执行时间比pipe_read晚6s
2019年 03月 15日 星期五 10:57:14 CST
[Fri Mar 15 10:57:14 2019] [E_NO: 17, File exists]mkfifo
[Fri Mar 15 10:57:14 2019] [file: pipe_read.c, line: 48]open fifo ok.
# 此时已经有数据可读,read会立马返回
[Fri Mar 15 10:57:14 2019] [file: pipe_read.c, line: 58]read from my_fifo buf=128 []
[Fri Mar 15 10:57:16 2019] [file: pipe_read.c, line: 58]read from my_fifo buf=128 []
[Fri Mar 15 10:57:18 2019] [file: pipe_read.c, line: 58]read from my_fifo buf=128 []
# 此时通过Ctrl + C 强制结束进程
^C
# 此时可以发现有个文件存在于文件夹下,首字符p表示这是一个管道文件
$ ls -l my_fifo
prw-rw-r-- 1 qingru.meng qingru.meng 0 3月 15 11:43 my_fifo
以阻塞方式open时,读、写必须都在open时才能同时返回,否则会被阻塞:
非阻塞方式时:
以阻塞方式read和write时,如果不可读或者不可写时,会阻塞,直到可读或可写;以非阻塞方式read和write时,如果不可读或不可写,则直接返回-1。
命名管道的设计是为了方便不同进程之间的信息传递,一个进程向管道中写入信息,另外一个进程从中读取信息,假设有多个进程同时向管道中写入信息,是否会发生信息的交错?
在POSIX.1中有关于pipe的描述,通过man指令看一下:
$ man 7 pipe
PIPE_BUF
POSIX.1 says that write(2)s of less than PIPE_BUF bytes must be atomic: the output data is written to the pipe as a contiguous sequence. Writes of more than PIPE_BUF bytes may be nonatomic: the kernel may interleave the data with data written by other processes. POSIX.1 requires PIPE_BUF to be at least 512 bytes. (On Linux, PIPE_BUF is 4096 bytes.) The precise semantics depend on whether the file descriptor is nonblocking (O_NONBLOCK), whether there are multiple writers to the pipe, and on n, the number of bytes to be written:
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 written immediately
O_NONBLOCK enabled, n <= PIPE_BUF
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
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
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.
意思就是规定了对pipe的实现时需要依据PIPE_BUF的大小来决定pipe的写入行为,而写入小于PIPE_BUF大小的数据必须是原子的:
阻塞时,写入n <= PIPE_BUF字节
写入n字节是原子的,不会发生交错,如果没有n个字节的空间,write会阻塞;
非阻塞时,写入n <= PIPE_BUF字节
如果空间足够,则成功写入,否则返回-1,并设置EAGAIN;
阻塞时,写入n > PIPE_BUF字节
写入是非原子的,数据可能发生交错,直到n字节全部写完,write才会返回;
非阻塞时,写入n > PIPE_BUF字节
如果管道满,write返回-1,并设置EAGAIN,否则可能写入任意1到n字节数据,调用者需要自己检查write的返回值,以确定是否全部写完。
PIPE_BUF的大小依据Linux内核的不同而有所不同(最小512字节,一般为4K),最好事先确定:
$ ulimit -a
...
# 可以发现PIPE_BUF为512*8=4K
pipe size (512 bytes, -p) 8
...
参考资料