PIPE和FIFO的使用及原理
PIPE和FIFO都是指管道,只是PIPE獨指匿名管道,F(xiàn)IFO獨指有名管道,我們先看一下管道的數(shù)據(jù)結構以及他們的使用方式:
1、匿名管道(pipe)
Linux下進程通信 匿名管道pipe2、命名管道(FIFO)
Linux下進程通信 命名管道FIFO
//匿名管道(PIPE)
#include <unistd.h>
int pipe (int fd[2]); //創(chuàng)建pipe
ssize_t write(int fd, const void *buf, size_t count); //寫數(shù)據(jù)
ssize_t read(int fd, void *buf, size_t count); //讀數(shù)據(jù)
?
//有名管道(FIFO)
#include<sys/stat.h>
#include <unistd.h>
int mkfifo(const char *path, mode_t mode); //創(chuàng)建fifo文件
int open(const char *pathname, int flags); //打開fifo文件
ssize_t write(int fd, const void *buf, size_t count); //寫數(shù)據(jù)
ssize_t read(int fd, void *buf, size_t count); //讀數(shù)據(jù)
匿名管道
可以看到,匿名管道通過pipe函數(shù)創(chuàng)建,pipe函數(shù)通過在內核的虛擬文件系統(tǒng)中創(chuàng)建兩個pipefs虛擬文件,并返回這兩個虛擬文件描述符,有了這兩個文件描述,我們就能進行跨進

匿名管道是單向半雙工的通信方式,單向即意味著只能一端讀另一端寫,半雙工意味著不能同時讀和寫,其中文件描述符fd[1]只能用來寫,文件描述符f[0]只能用來讀,pipe創(chuàng)建好后,我們就可以用Linux標準的文件讀取函數(shù)read和寫入函數(shù)write來對pipe進行讀寫了。
為什么pipe是匿名管道呢?因為pipefs文件是特殊的虛擬文件系統(tǒng),并不會顯示在VFS的目錄中,所以用戶不可見,既然用戶不可見,那么又怎么能進行進程間通信呢?
因為pipe是匿名的,所以它只支持父子和兄弟進程之間的通信。通過fork創(chuàng)建父子進程或者通過clone創(chuàng)建兄弟進程的時候,會共享內存拷貝,
這時,子進程或兄弟進程就能在共享拷貝中拿到pipe的文件描述符進行通信了。我們通過下圖看一下通信的流程。

FIFO
FIFO,F(xiàn)IFO是半雙工雙向通信,所以通過FIFO創(chuàng)建的管道既能讀也能寫,但是不能同時讀和寫,F(xiàn)IFO本質上是一個先進先出的隊列數(shù)據(jù)結構,最早放入的數(shù)據(jù)被最先讀出來,這樣的數(shù)據(jù)結構能保證信息交流的順序。
FIFO使用也很簡單,通過mkfifo函數(shù)創(chuàng)建管道,它同樣會在內核的虛擬文件系統(tǒng)中創(chuàng)建一個FIFO虛擬文件,F(xiàn)IFO文件在在VFS目錄中可見,所以他是有名管道,F(xiàn)IFO創(chuàng)建后,我們需要調用open函數(shù)打開這個fifo文件,然后才能通過write和read函數(shù)進行讀寫
pipe和fifo的異同點
相同點
- IPC的本質都是通過在內核創(chuàng)建虛擬文件,并且調用文件讀寫函數(shù)來進行數(shù)據(jù)通信
- 都只能接收字節(jié)流數(shù)據(jù)
- 都是半雙工通信
不同點 - pipe是單向通信,fifo可以雙向通信
- pipe只能在父子,兄弟進程間通信,fifo沒有這個限制
管道的使用場景
匿名管道只能用在親屬進程之間通信,而且傳輸?shù)臄?shù)量小,一般只支持4K,不適合大數(shù)據(jù)的交換數(shù)據(jù)和不同進程間的通信,但是使用簡單方便,因為是單向通信,所以不存在并發(fā)問題。
雖然FIFO能在任意兩個進程間進行通信,但是因為FIFO是可以雙向通信的,這樣也不可避免的帶來了并發(fā)的問題,我們需要花費比較大的精力用來控制并發(fā)問題。
管道在Android系統(tǒng)中的使用場景
在Android 6.0 以下版本中,主線程Looper的喚醒就使用到了管道。
//文件-->/system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
int wakeFds[2];
int result = pipe(wakeFds); //創(chuàng)建pipe
?
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
?
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
errno);
?
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
errno);
?
mIdling = false;
?
// Allocate the epoll instance and register the wake pipe.
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
?
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeReadPipeFd;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
errno);
}
從第上面代碼可以看到,native層的Looper的構造函數(shù)就使用了pipe來創(chuàng)建管道,通過mWakeReadPipeFd,mWakeWritePipeFd這兩個文件描述符的命名也看出,它是用來做喚醒的,我們就來看一下具體的喚醒的實現(xiàn)吧。
//文件-->/system/core/libutils/Looper.cpp
void Looper::wake() {
ssize_t nWrite;
do {
nWrite = write(mWakeWritePipeFd, "W", 1);
} while (nWrite == -1 && errno == EINTR);
if (nWrite != 1) {
if (errno != EAGAIN) {
ALOGW("Could not write wake signal, errno=%d", errno);
}
}
}
可以看到,喚醒函數(shù)其實就是往管道m(xù)WakeWritePipeFd里寫入一個字母“W”,mWakeReadPipeFd接收到數(shù)據(jù)后,就會喚醒Looper。