概述
在 Linux進程間通信—管道(無名管道、pipe)一文中介紹了匿名管道的使用,但是其中有一個明顯的缺陷,匿名管道只能用於有親緣關係的進程之間通信,命名管道則解決了這個缺陷,可以在沒有親緣關係的2個進程之間進行通信。
命名管道(named pipe) 也被成爲FIFO文件,是一種特殊類型的文件。在文件系統中可以找到實在的文件與之對應,這樣進程可以像訪問文件一樣對管道進行訪問,大部分文件訪問的API(open、read、write)也可以用於命名管道的訪問。
命名管道的行爲和匿名管道基本一致,也存在如下特點:
- 阻塞方式下,read會因管道空阻塞,和write會因管道滿阻塞,直到可讀或者可寫;
- 阻塞方式下,open操作也會阻塞,直到另一端也能夠打開的時候,open才能正常返回;
- 非阻塞方式下,以O_RDONLY打開管道,可以正常打開;以O_WRONLY打開管道,會返回失敗,並設置ENXIO(6)錯誤碼;
- 在所有讀端關閉的情況下,進行寫入時會拋出SIGPIPE信號;
- 以O_RDWR方式打開命名管道,其行爲是未定義的;(參考POSIX)
API
#include <sys/types.h>
#include <sys/stat.h>
/* 創建fifo文件
* @filename: 文件名
* @mode: 文件的讀寫權限
* return: 成功返回0, 失敗返回-1, 並正確設置errno
*/
int mkfifo(const char *filename, mode_t mode);
#include <sys/stat.h>
#include <fcntl.h>
/* 打開管道文件
* @filename: 文件名
* @oflag: 打開方式,O_WRONLY, O_RDONLY,O_NONBLOCK等
* return: 成功返回非負的文件描述符, 失敗返回-1, 並正確設置errno
*/
int open(const char *path, int oflag, ... );
#include <unistd.h>
/* 讀取管道文件
* @fildes: 文件描述符
* @buf: 存儲結果的內存
* @nbyte: 最大讀取的字節數
* return: 成功返回非負的讀取的字節數, 失敗返回-1, 並正確設置errno
*/
ssize_t read(int fildes, void *buf, size_t nbyte);
#include <unistd.h>
/* 向管道文件寫入數據
* @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 <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#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 <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#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時才能同時返回,否則會被阻塞:
- 如以O_WRONLY打開時,會阻塞,直到另外有以O_RDONLY、O_REWR方式open的操作時纔會返回;
- 如以O_RDWR打開時, open不會阻塞;
非阻塞方式時:
- 以O_WRONLY打開,會直接返回負值,並設置錯誤爲ENXIO(6);
- 以O_RDONLY和O_RDWR可以正常打開;
以阻塞方式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
...
參考資料