Android Framework層學習——爲什麼SystemServer進程與Zygote進程通訊採用Socket而不是Binder

 

目錄

一.Activity的啓動流程

二.提出問題

三.提出假設

         假設1.是不是在這種應用場景下,Socket使用的性能比Binder更佳?

假設2.是不是由於某種限制,在這個場景下不適合使用Binder?

假設2.1:會不會是由於先後順序問題,Zygote中無法使用Binder?

假設2.2是不是在這個流程中fork函數出於某些原因不能使用Binder?

四.結論總結    

 


前言:最近在學習activity啓動流程的過程中,發現一個有趣的問題。SystemServer進程與Zygote進程通訊採用Socket而不是Binder,而其他進程之間的通訊採用的都是Binder通訊。作爲爲android系統專門定製的IPC方式,Binder在android本地的通訊中有着其他IPC無可比擬的優勢,不僅體現在系統的調用上,作爲開發者在本地進程間的通訊,我們首選也是Binder,那麼爲什麼作爲系統進程中咖位如此大的Zygote和SystemService之間採用的卻是Socket?

有點人會說LocalSocket本來就是用來本地進程間通訊的,這沒錯,但知其然更要知其所以然。

 

如果對activity啓動流程不太熟悉的可以自己去學習一下,這裏只簡單地瞭解一下這個過程涉及的進程。

 

一.Activity的啓動流程

初次啓動一個app(也就是從我們點擊桌面app圖標到進入應用程序),要涉及四個進程

Launcher進程:手機的桌面進程,這裏佈置着各種app應用的圖標。

SystemService進程:AMS與WMS等都在此進程,負責通知Zygote去生成應用程序進程

Zygote進程:通過fork生成應用程序進程

應用程序進程:就是我們點擊應用圖標進入的app所在進程。


由圖中我們看出,他們之間的通訊過程大致是:

1.Launcher通過AIDL(Binder的一種)通知SystemService

2.SystemService通過Socket通知Zygote

3.Zygote 收到SystemService請求,fork自身生成應用程序

4.應用程序進程與SystemService通過Binder與AMS,WMS進行交互

 

二.提出問題

以上就是點擊桌面到生成應用程序的過程。那麼下面就引出本文的主題。在這個過程中,我們可以看到這幾個進程之間的通訊都是採用Binder,只有Zygote進程與SystemService之間是Socket。這是爲什麼呢?

Binder作爲android端定製的一套IPC,爲何在Zygote和SystemService這兩個這麼重要的進程的通訊過程被棄用呢?

 

 

三.提出假設

探究一個問題要通過正反兩面。

那麼先從正面出發:

假設1.是不是在這種應用場景下,Socket使用的性能比Binder更佳?

傳輸性能:

  socket作爲一款通用接口,其傳輸效率低,開銷大,主要用在跨網絡的進程間通信和本機上進程間的低速通信。

     消息隊列和管道採用存儲-轉發方式,即數據先從發送方緩存區拷貝到內核開闢的緩存區中,然後再從內核緩存區拷貝到接收方緩存區,至少有兩次拷貝過程。而Binder只需要一次,開銷也更小。

安全性:

Android作爲一個開放式,擁有衆多開發者的平臺,應用程序的來源廣泛,確保智能終端的安全是非常重要的。

終端用戶不希望從網上下載的程序在不知情的情況下偷窺隱私數據,連接無線網絡,長期操作底層設備導致電池很快耗盡等等。傳統IPC沒有任何

安全措施,完全依賴上層協議來確保。首先傳統IPC的接收方無法獲得對方進程可靠的UID/PID(用戶ID/進程ID),從而無法鑑別對方身份。

  Android爲每個安裝好的應用程序分配了自己的UID,故進程的UID是鑑別進程身份的重要標誌。使用傳統IPC只能由用戶在數據包裏填入UID/PID,

但這樣不可靠,容易被惡意程序利用。可靠的身份標記只有由IPC機制本身在內核中添加。其次傳統IPC訪問接入點是開放的,無法建立私有通道。

比如命名管道的名稱、system V的鍵值、socket的ip地址或文件名都是開放的,只要知道這些接入點的程序都可以和對端建立連接,不管怎樣都無法

阻止惡意程序通過猜測接收方地址獲得連接。

  基於以上原因,Android需要建立一套新的IPC機制來滿足系統對通信方式,傳輸性能和安全性的要求,這就是Binder。

Binder基於 Client-Server通信模式,傳輸過程只需一次拷貝,爲發送發添加UID/PID身份,既支持實名Binder也支持匿名Binder,安全性高。

 

基於Binder的這些特性,從性能的角度看,無論怎麼看都應該選擇Binder而不是Socket。所以假設1不成立。

正面行不通,我們從反面出發:

假設2.是不是由於某種限制,在這個場景下不適合使用Binder?

在這裏,我想到了Zygote是Linux層就有的,而Binder是android 纔有的IPC,

假設2.1:會不會是由於先後順序問題,Zygote中無法使用Binder?

首先我知道,ServiceManager是一個守護進程,它維護着系統服務和客戶端的binder通信。Binder這套機制是基於ServiceManager的,只要驗證ServiceManager是在Zygote進程生成之後才創建的,那就可以驗證假設2.1成立。

果不其然。。。

ServiceManager在init進程啓動後啓動,用來管理系統中的service,Zygote進程在init之後,而且ServiceManager進程的啓動,遠比zygote要早,因爲在啓動zygote進程時需要用到ServiceManager進程的服務

所以假設2.1不成立.。。。

 

既然2.1不成立,而且在啓動zygote進程時需要用到ServiceManager進程的服務,那麼顯然Zygote是可以使用Binder的。

那麼假設2是不是也不成立?

探究一件事的結果還要從過程出發。整個流程中,Zygote的最終目的,顯然就是通過fork去創建一個應用進程,過程就是沒有使用到Binder

假設2.2是不是在這個流程中fork函數出於某些原因不能使用Binder?

所以,我們要了解fork存在哪些限制。

查閱資料發現,fork確實還真有這麼一條限制:


UNIX上C++程序設計守則3

準則3:多線程程序裏不準使用fork

在多線程程序裏,在”自身以外的線程存在的狀態”下一使用fork的話,就可能引起各種各樣的問題.比較典型的例子就是,fork出來的子進程可能會死鎖.請不要,在不能把握問題的原委的情況下就在多線程程序裏fork子進程.

以下是說明死鎖的理由:
一般的,fork做如下事情
   1. 父進程的內存數據會原封不動的拷貝到子進程中
   2. 子進程在單線程狀態下被生成

在內存區域裏,靜態變量mutex的內存會被拷貝到子進程裏.而且,父進程裏即使存在多個線程,但它們也不會被繼承到子進程裏. fork的這兩個特徵就是造成死鎖的原因.
譯者注: 死鎖原因的詳細解釋 ---
   1. 線程裏的doit()先執行.
  2. doit執行的時候會給互斥體變量mutex加鎖.
   3. mutex變量的內容會原樣拷貝到fork出來的子進程中(在此之前,mutex變量的內容已經被線程改寫成鎖定狀態).
   4.子進程再次調用doit的時候,在鎖定互斥體mutex的時候會發現它已經被加鎖,所以就一直等待,直到擁有該互斥體的進程釋放它(實際上沒有人擁有這個mutex鎖).
    5.線程的doit執行完成之前會把自己的mutex釋放,但這是的mutex和子進程裏的mutex已經是兩份內存.所以即使釋放了mutex鎖也不會對子進程裏的mutex造成什麼影響.
    
    

到此,真相終於水落石出了!

是不是看了這麼長的通篇大論,有點小夥伴已經暈了。還沒反應過來問題怎麼就解決了???

Binder通訊是需要多線程操作的,代理對象對Binder的調用是在Binder線程,需要再通過Handler調用主線程來操作。

比如AMS與應用進程通訊,AMS的本地代理IApplicationThread通過調用ScheduleLaunchActivity,調用到的應用進程ApplicationThread的ScheduleLaunchActivity是在Binder線程,需要再把參數封裝爲一個ActivityClientRecord,sendMessage發送給H類(主線程Handler,ActivityThread內部類)

 

四.結論總結
    


    總結起來就是怕父進程binder線程有鎖,然後子進程的主線程一直在等其子線程(從父進程拷貝過來的子進程)的資源,但是其實父進程的子進程並沒有被拷貝過來,造成死鎖,所以fork不允許存在多線程。而非常巧的是Binder通訊偏偏就是多線程,所以乾脆父進程(Zgote)這個時候就不使用binder線程

 

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