Android進程間通信機制-管道

PIPE和FIFO的使用及原理

PIPE和FIFO都是指管道,只是PIPE獨指匿名管道,F(xiàn)IFO獨指有名管道,我們先看一下管道的數(shù)據(jù)結構以及他們的使用方式:

//匿名管道(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虛擬文件,并返回這兩個虛擬文件描述符,有了這兩個文件描述,我們就能進行跨進

11fb2bc5d6b94423ac6413adcf35dbec_tplv-k3u1fbpfcp-zoom-1.png

匿名管道是單向半雙工的通信方式,單向即意味著只能一端讀另一端寫,半雙工意味著不能同時讀和寫,其中文件描述符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的文件描述符進行通信了。我們通過下圖看一下通信的流程。


2fabf865e77f450ea9f48d71be8c789a_tplv-k3u1fbpfcp-zoom-1.png

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。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • ?通信是Android開發(fā)必不可少的一部分,不管是我們做應用App開發(fā),還是Android系統(tǒng),都使用了大量的通信...
    子者不語閱讀 537評論 0 1
  • 一.管道機制(pipe) 1.Linux的fork操作 在計算機領域中,尤其是Unix及類Unix系統(tǒng)操作系統(tǒng)中,...
    Geeks_Liu閱讀 3,808評論 1 9
  • 由于Android系統(tǒng)是基于Linux系統(tǒng)的,所以有必要簡單的介紹下Linux的跨進程通信,對大家后續(xù)了解Andr...
    Sophia_dd35閱讀 867評論 0 4
  • 進程間通信在兩個進程之間,每個進程各自有不同的用戶地址空間,任何一個進程的全局變量在另一個進程中都看不到。比如,在...
    basetree閱讀 896評論 0 0
  • 前言 管道是UNIX環(huán)境中歷史最悠久的進程間通信方式,也是最簡單的進程間通信方式,一般用來作為IPC的入門,最合適...
    GeekerLou閱讀 1,567評論 0 6

友情鏈接更多精彩內容