Android的進程間通信機制之Binder初探

在瞭解Android的Binder通信機制之前,我們來看下Linux現有的進程間通信的方式,然後簡要分析Android爲什麼要另起爐竈,設計一套新的通信機制Binder,以及Binder通信機制在Android這種嵌入式平臺具有何種優越性等,首先看Linux下的進程間通信方式,有管道、消息隊列、共享內存、套接字、信號量、信號,起初我們一一回顧下這幾種通信方式的實現原理和適用場景

1.管道

比較好理解概念的就是進程間通信就是在不同進程之間傳播或交換信息。管道通信方式是Linux繼承了早期的Unix進程間通信的方式,管道又分爲無名管道和有名管道,一般沒有刻意描述稱管道爲無名管道

1.1管道的特點

Ø  管道是半雙工的,數據只能向一個方向流動,一端輸入,另一端輸出。需要雙方通信時,需要建立起兩個管道。

Ø  管道分爲普通管道和命名管道。普通管道位於內存,只能用於父子進程或者兄弟進程之間(具有親緣關係的進程)。命名管道位於文件系統,沒有親緣關係的進程間只要知道管道名也可以通訊。

Ø  單獨構成一種獨立的文件系統:管道對於管道兩端的進程而言,就是一個文件,但它不是普通的文件,它不屬於某種文件系統,而是自立門戶,單獨構成一種文件系統,並且只存在於內存中

Ø  一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩衝區的末尾,並且每次都是從緩衝區的頭部讀出數據。

Ø  管道滿時,寫阻塞;管道空時,讀阻塞。

1.2 管道實現機制

管道是由內核管理的一個緩衝區,管道的一端連接着進程的輸出,一端連接進程的輸入,一個緩衝區不需要很大,它被設計成爲環形的數據結構,類似於數據結構中的循環隊列;以便管道可以被循環利用。當管道中沒有信息的話,從管道中讀取的進程會等待,直到另一端的進程放入信息。當管道被放滿信息的時候,嘗試放入信息的進程會等待,直到另一端的進程取出信息。

管道是基於文件描述符的通信方式,當一個管道建立時,它會創建兩個文件描述符fd[0]和fd[1],其中fd[0]用於讀取,fd[1]用於寫

管道的通信模型圖如下所示:

管道的創建過程這裏以父進程創建子進程的方式爲例,當父進程調用fork()函數時候,子進程也擁有一對描述符(複製父進程的)鏈接到管道上,如圖所示:

隨後,當兩個進程進行通信時關閉不需要的一個鏈接,圖中所示,process1關閉寫,process2關閉讀描述符

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main(int argc, char *argv[]){

    int pfd[2]; //保存進程間通信的管道文件描述符
    pid_t pid;  //保存子進程 描述符
    char buf;

    if(argc != 2)//判斷命令行參數是否符合
    {
        fprintf(stderr,"Usage: %s <string>\n",argv[0]);
        exit(0);
    }
    // 先建立管道文件描述符
    if (pipe(pfd) == -1)
    {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
    if((pid = fork()) == 0) {
        // 關閉子進程的管道寫描述符
        close(pfd[1]);
        printf("get message from parent\n");
        while (read(pfd[0], &buf, 1) > 0)   //從管道循環讀取數據
            write(STDOUT_FILENO, &buf, 1);  //輸出讀到的數據
        write(STDOUT_FILENO, "\n", 1);      //輸出從管道讀取的數據
        close(pfd[0]);         //關閉管道讀
        exit(0);
    }else if(pid > 0){
        //關閉管道讀
        close(pfd[0]);
        printf("send message to child\n");
        //向管道寫入命令行參數1
        write(pfd[1], argv[1], strlen(argv[1]));
        close(pfd[1]);
        //等待子進程退出
        wait(NULL);
        exit(0);
    }else{
         perror("fork");//fork()失敗
        exit(EXIT_FAILURE);
    }
}

1.3 命名管道

由於基於fork機制,所以管道只能用於父進程和子進程之間,或者擁有相同祖先的兩個子進程之間 (有親緣關係的進程之間)。爲了解決這一問題,Linux提供了FIFO方式連接進程。FIFO又叫做命名管道(namedPIPE)。

FIFO (First in,First out)爲一種特殊的文件類型,它在文件系統中有對應的路徑。當一個進程以讀(r)的方式打開該文件,而另一個進程以寫(w)的方式打開該文件,那麼內核就會在這兩個進程之間建立管道,所以FIFO實際上也由內核管理,不與硬盤打交道。之所以叫FIFO,是因爲管道本質上是一個先進先出的隊列數據結構,最早放入的數據被最先讀出來,從而保證信息交流的順序。FIFO只是借用了文件系統(file system,命名管道是一種特殊類型的文件,因爲Linux中所有事物都是文件,它在文件系統中以文件名的形式存在。)來爲管道命名。寫模式的進程向FIFO文件中寫入,而讀模式的進程從FIFO文件中讀出。當刪除FIFO文件時,管道連接也隨之消失。FIFO的好處在於我們可以通過文件的路徑來識別管道,從而讓沒有親緣關係的進程之間建立連接


2.共享內存


共享內存可以說是進程間通信最快的方式,兩個不同進程A、B共享內存的意思是,同一塊物理內存被映射到進程A、B各自的進程地址空間。進程A可以即時看到進程B對共享內存中數據的更新,反之亦然。由於多個進程共享同一塊內存區域,必然需要某種同步機制,互斥鎖和信號量都可以來保證數據的一致性。

採用共享內存通信的一個顯而易見的好處是效率高,因爲進程可以直接讀寫內存,而不需要任何數據的拷貝。對於像管道和消息隊列等通信方式,則需要在內核和用戶空間進行四次的數據拷貝,而共享內存則只拷貝兩次數據

這四次數據拷貝是指:

寫的時候

1)用戶空間的進程將buf中將數據拷貝到內核空間中

2)內核將數據拷貝到內存中

讀的時候

1)讀數據從內存到內核空間

2)內核空間到用戶空間的buf

共享內存的2次是指:

一次從輸入文件到共享內存區,另一次從共享內存區到輸出文件



Linux的2.2.x內核支持多種共享內存方式,如mmap()系統調用,Posix共享內存,以及系統V共享內存。這裏以system V爲例子

 

1.創建共享內存,這裏用到的函數是shmget,也就是從內存中獲得一段共享內存區域;

2.映射共享內存,也就是把這段創建的共享內存映射到具體的進程空間中去,這裏使用的函數是shmat;

3.使用不帶緩衝的I/O讀寫命令對其進行操作

4.撤銷映射的操作,其函數爲shmdt。

具體的實現參見博客http://blog.csdn.net/lzjsqn/article/details/53863283


3.信號量


主要作爲進程間以及同一進程不同線程之間的同步手段。信號量是用來解決進程之間的同步與互斥問題的一種進程之間通信機制,包括一個稱爲信號量的變量和在該信號量下等待資源的進程等待隊列,以及對信號量進行的兩個原子操作(PV操作)。其中信號量對應於某一種資源,取一個非負的整型值。信號量值指的是當前可用的該資源的數量,若它等於0則意味着目前沒有可用的資源。

什麼是PV操作

P操作:如果有可用的資源(信號量值>0),則佔用一個資源(給信號量值減去一,進入臨界區代碼);如果沒有可用的資源   (信號量值等於0),則被阻塞到,直到系統將資源分配給該進程(進入等待隊列,一直等到資源輪到該進程)。

V操作:如果在該信號量的等待隊列中有進程在等待資源,則喚醒一個阻塞進程。如果沒有進程等待它,則釋放一個資源(給信號量值加一)。

 

4.信號


信號(Signal):信號是比較複雜的通信方式,用於通知接受進程有某種事件發生,除了用於進程間通信外,進程還可以發送信號給進程本身;信號是在軟件層次上對中斷機制的一種模擬,是一種異步通信方式,信號可以直接進行用戶空間進程和內核進程之間的交互,內核進程也可以利用它來通知用戶空間進程發生了哪些系統事件。它可以在任何時候發給某一進程,而無需知道該進程的狀態。如果該進程當前並未處於執行態,則該信號就由內核保存起來,直到該進程恢復執行再傳遞給它;如果一個信號被進程設置爲阻塞,則該信號的傳遞被延遲,直到其阻塞被取消時才被傳遞給進程。

 

5.消息隊列


消息隊列是消息的鏈表,存放在內核中並由消息隊列標識符標識。在某個進程往一個隊列寫入消息之前,並不需要另外某個進程在該隊列上等待消息的到達。這跟管道和FIFO是相反的,對後兩者來說,除非讀出者已存在,否則先有寫入者是沒有意義的。包括Posix消息隊列systemV消息隊列。有足夠權限的進程可以向隊列中添加消息,被賦予讀權限的進程則可以讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩衝區大小受限等缺點。


6.套接字


套接口(Socket):更爲一般的進程間通信機制,可用於不同機器之間的進程間通信。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix系統上:Linux和System V的變種都支持套接字。

 

7. Binder的機制


Android的內核也是基於Linux內核,爲何不直接採用Linux現有的進程IPC方案呢,這個問題衆說紛紜,下面我們從Binder的基本特性和已有Linux進程間通信的方式進行對比,說明採用Binder機制的合理性

Ø  通信效率

Android作爲嵌入設備的代表,性能異常重要,不會像PC那樣擁有更多的內存和CPU資源,因此設計一套高效的通信方式是必須的,前面講的管道、消息隊列、Socket通信的時候都需要進行2次內存拷貝數據(注意是內存拷貝,不是總的拷貝次數),共享內存不需要內存拷貝,Binder機制需要一次,從性能上講Binder機制在通信效率上僅此於內存共享。

Ø  安全性

Binder機制對於通信雙方的身份是由內核進行校檢支持的,傳統Linux IPC的接收方無法獲得對方進程可靠的UID/PID,無法鑑別對方身份。Android系統爲每個應用程序都分配相應的UID,Binder機制是基於C/S架構,Android系統中對外只暴露Client端,Client端將任務發送給Server端,Server端會根據權限控制策略,判斷相應的通信雙方是否合法。使用用戶空間無法訪問的內存空間來交換數據,保證了IPC的安全性

Ø  易用性

共享內存通信不需要copy,性能夠高,可是使用複雜,要使用複雜的同步互斥方式實現進程間通信,保證共享內存的數據一致有效,Binder採用面向對象的設計實現,將進程間通信的過程好比爲調用對象的方法一樣.


Binder機制的具體實現原理,,,未完待續

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章