概述
Android作爲一個移動端的操作系統,就必須提供一種可靠的跨進程通信方案。
我們來想想,移動端有哪些特性,IPC跨進程通信需要滿足哪些特性?
高效
首先,移動端資源比較有限,尤其是在早期的時候,硬件設備跟PC相比差距很大,這就要求Android系統在設計時,尤其要注意資源的使用效率,所以,IPC首先要滿足 ”高效” 這一點。
安全性
其次,在Android系統上,運行着各種應用,有系統自帶的,也有開發者提交的,這裏面不乏各種惡意應用。所以Android系統在設計上必須要保證每個App的 “安全性”,爲此,Android系統基於Linux內核,也同時繼承了Linux的各種安全模型。例如,Android中分爲各種用戶組,用戶組裏面運行着各種進程,每個用戶組/用戶都會分別賦予不同的權限,並且每個進程都會運行在獨立的沙箱中,這樣保證了進程間的隔離,大大提高了系統的安全性。爲了進一步增強了系統的安全性,在Android 4.4之後,Android系統加入了SELinux強制訪問控制策略。那麼進程之間的IPC通信,同樣要保障其安全性!如何來保障呢?Binder的設計實現,恰好滿足了這一點。
Binder是什麼?
Android系統是基於Linux內核開發的,那麼它自然就繼承了Linux在IPC使用上的方式,例如,傳統的管道(Pipe)、信號(Signal)和跟蹤(Trace)、命令管道(Named Pipe)、報文隊列(Message)、共享內存(Share Memory)和信號量(Semaphore)、插口(Socket)等。但是Android系統並沒有使用這些同學方式,具體的原因我們在上文中已經分析過了。
那麼,Binder是什麼?Binder是一套IPC通信方案,但Binder的方案並不是Android原創的,它是基於OpenBinder來實現的。從英文字面上意思看,Binder具有粘結劑的意思,那麼它把什麼東西粘結在一起呢?
Android系統中,IPC通信包括哪幾個部分呢?
- 內核層:Binder驅動
- 用戶空間:ServiceManager
- 用戶空間:Client
- 用戶空間:Server
Android系統IPC通信的四大組成部分包括了,Binder驅動提供了IPC的核心能力、ServiceManager提供了輔助管理功能、Client是調用端、Server服務提供者,Binder把這四大部件“粘合”在一起,共同爲Android系統提供了IPC通信能力。
Binder特性有哪些?
Binder滿足了移動端IPC通信對效率和安全性的要求。具體它有哪些特點以及實現原理是怎麼樣的呢?
簡單、高效
首先我們簡單來看下其他的IPC方案。
- Socket作爲一款通用接口,主要用於跨網絡的Client端和Server端的低速通信,其傳輸效率低,開銷大,不滿足Android對性能和傳輸效率的要求。
- 消息隊列和管道採用存儲-轉發方式,即數據先從發送方緩存區拷貝到內核開闢的緩存區中,然後再從內核緩存區拷貝到接收方緩存區,整個過程經過了2次內存拷貝過程,效率較低。
- 共享內存的方式,雖然無需拷貝,但實現和控制複雜,難以使用。
對比IPC方式數據拷貝次數:
IPC類型 | 數據拷貝次數(次) |
---|---|
各種IPC方式數據拷貝次數 | 2 |
共享內存 | 0 |
共享內存 | 1 |
安全性
Android作爲移動端的操作系統,是一個打的開放式平臺,任何人或組織都可以發佈自己的App,並且運行在Android系統上,而用戶同樣希望自己安裝的App,不會對手機和其他App帶來一些安全問題,例如數據隱私、訪問限制、以及硬件相關的數據網絡、耗電等問題。傳統IPC的安全性完全依賴上層協議,並且無法獲得訪問進程的可靠UID、PID等信息,無法對用戶身份進行鑑別,不能滿足Android對IPC通信安全的要求。
Binder基於Client-Server的通信模式,同時支持非匿名和匿名兩種通信方式,增加了各種安全校驗機制,在低層保障了系統對安全性的要求。
Binder特性的實現分析
傳統IPC方案通信原理
數據傳遞過程:
- 首先,發送方進程通過系統調用,將要發送的數據存拷貝到內核緩存區中。
- 然後,接收方開闢一段內存空間,內核通過系統調用將內核緩存區中的數據拷貝到接收方的內存緩存區中,即完成了數據的傳遞過程。
缺點
- 數據需要進行2次拷貝過程,第1次是從發送方用戶空間拷貝到內核緩存區,第2次是從內核緩存區拷貝到接收方用戶空間,效率低。
- 接收方進程並不知道事先要分配多大的空間來接收數據,可能存在空間上的浪費。
Binder的通信原理
Binder是如何只用1次內存拷貝就實現數據的傳遞呢?
Android是基於Linux內核實現的,而Linux操作系統採用虛擬內存管理技術,使得每個進程都有各自互不干涉的進程地址空間。物理內存通過映射到用戶空間的虛擬地址中,操作系統通過虛擬內存來操作物理內存。在Linux中,虛擬空間映射可以通過mmap()來實現。
Binder實現1次內存拷貝的祕密就是:它藉助了虛擬內存映射機制,在數據接收方和內核空間的數據緩存區做了內存映射。這樣一來,從發送方發送的數據,拷貝到內核空間時,直接映射拷貝到了接收方的內存空間中了,也就實現了一次拷貝就完成了數據從發送方、到內核、再到接收方的數據傳輸過程。
Binder通信過程解析
我們已經回答了Binder是什麼,爲什麼Android會使用Binder等問題,接下來我們來看下Binder的源碼實現。
Binder的組成可以分爲Binder驅動、ServiceManager、Client端、Server端四個組成部分,這四個部分分別代表了各自的角色,發揮着不同的作用。
類比網絡通信,Client代表了普通用戶,它通過ServiceManager(相當於DNS服務)來查詢系統服務的地址,然後經過Binder驅動(相當於路由器),在內核層實現了IPC跨進程通信的能力,把消息傳遞到指定的Service端,訪問Service端提供的服務。我們日常開發過程中接觸較多的部分是Client和Server這兩個,但是實現IPC的核心能力的其實是Binder驅動和ServiceManager服務。
Client端
用戶端要訪問Server端進程提供的服務,第一步要做什麼呢?
沒錯,就是拿到Server端的地址,然後發起對該服務的請求。
地址怎麼拿到呢?
我們以最常用的一個系統服務ActivityManagerService(AMS)爲例來分析獲取AMS的過程。
獲取AMS過程解析
獲取ActivityManager
回想一下我們怎麼獲取AMS的?啊,對了,是在Activity(Service中)直接調用getSystemService()方法,傳遞一個字符串常亮即可。
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE) activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);//ACTIVITY_SERVICE在Context中定義,值爲字符串”activity"
不過這裏返回的是一個ActivityManager啊,不是我們想要的AMS啊,這是什麼原因?
ActivityManager是AMS在應用層的一個工具類,代理提供一些AMS服務接口的訪問,所以在使用時,我們通常獲得的是ActivityManager對象。
getSystemService方法實現
getSystemService方法的實現實在ContextImpl類中:
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
調用了SystemServiceRegistry.getSystemService方法
android.app.SystemServiceRegistry.java
private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new ArrayMap<String, ServiceFetcher<?>>();
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
這裏的ServiceFetcher是一個接口,只有一個方法getService。
SYSTEM_SERVICE_FETCHERS是一個Map,存儲了系統所有的服務對象。它的賦值是在SystemServiceRegistry靜態代碼塊中:
static {
……
registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
new CachedServiceFetcher<ActivityManager>() {
@Override
public ActivityManager createService(ContextImpl ctx) {
return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
}});
……
}
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
我們在這裏看到了ActivityManager的創建及註冊過程。Map中存儲的是AMS的在Client的代理工具類ActivityManger。
那麼我們如何通過ActivityManager來訪問AMS呢?
AMS的獲取
我們隨意找一個AMS提供的服務接口,例如isUserRunning:
public boolean isUserRunning(int userId) {
try {
return getService().isUserRunning(userId, 0);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
邏輯解析
- isUserRunning調用了getService()方法。
- getService()返回IActivityManagerSingleton.get(),也就是一個IActivityManager對象。
- 進一步看,IActivityManager對象是通過ServiceManager.getService獲取一個IBinder對象,然後作爲參數傳遞給IActivityManager.Stub.asInterface方法,該方法的返回值又是什麼呢?沒錯!它就是我們想得到的AMS對象。
小結
我們來總結一下獲取AMS的過程:
- ActivityManager是AMS在應用層的一個工具類,代理提供一些AMS服務接口的訪問,所以在使用時,我們通常獲得的是ActivityManager對象。
- Client端通過ContextImpl的getSystemService(ACTIVITY_SERVICE)來獲取AMS。
- SystemServiceRegistry負責註冊和管理系統服務。
- SYSTEM_SERVICE_FETCHERS是一個Map,存儲了ActivityManager對象。
- 當使用AMS服務時,ActivityManager對象會調用它的getService方法來進行調用轉發。
- 最終通過IActivityManager.Stub.asInterface(b)來獲取真正的服務對象IActivityManager(也就是AMS在Java層的實例)。
Service端
Service端想要提供服務,首先要把服務註冊到ServiceManager中,以方便管理和查詢。我們繼續以AMS爲例進行分析。
SystemServer的概述
SystemServer是Zygote進程fork的第一個進程,它是Android系統的核心之一,大多數的服務都運行在這個進程中。應用層想要訪問設備的資源,都要通過SystemServer進程來代理訪問。
SystemServer進程在Zygote中被創建,最後會調用caller.run來運行SystemServer的main方法。它會註冊系統的大多數的服務包括了BootstrapService、CoreService和OtherService三種類型的服務,我們的AMS屬於BootstrapService類型服務。
這裏我們對SystemServer的創建過程不做過多的分析,之後會單獨寫一章來介紹,這裏簡單介紹清楚即可。
SystemServer的main方法
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
// Start services.
try {
traceBeginAndSlog("StartServices");
startBootstrapServices(); //AMS註冊位置
startCoreServices();
startOtherServices();
SystemServerInitThreadPool.shutdown();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
} finally {
traceEnd();
}
}
繼續看startBootstrapServices方法
private void startBootstrapServices() {
ActivityTaskManagerService atm = mSystemServiceManager.startService(
ActivityTaskManagerService.Lifecycle.class).getService();
mActivityManagerService = ActivityManagerService.Lifecycle.startService(
mSystemServiceManager, atm);
mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
mActivityManagerService.setInstaller(installer);
mActivityManagerService.setSystemProcess();
這裏調用了ActivityManagerService的setSystemProcess方法,我們繼續。
ActivityManagerService的setSystemProcess方法
public void setSystemProcess() {
try {
ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
……
}
這裏調用了ServiceManager.addService方法來向系統註冊服務,註冊之後,系統運行的其他進程就可以訪問AMS服務了。
小結
本節分析了AMS的註冊過程:
- Service端想要提供服務,首先要把服務註冊到ServiceManager中,以方便管理和查詢。
- AMS的註冊是在SystemServer的創建過程中進行的。
- SystemServer是Zygote進程fork的第一個進程,它是Android系統的核心之一,大多數的服務都運行在這個進程中。
- AMS屬於BootstrapService類型服務。
- 經過一系列調用,最終調用了ServiceManager.addService方法來向系統註冊服務,註冊之後,系統運行的其他進程就可以訪問AMS服務了。
ServiceManager
ServiceManager在整個Binder IPC通信中,相當於網絡訪問中的DNS,它負責把服務註冊到自己的內部列表中,當Client訪問某個服務時,它通過查詢操作,把特定的服務返回給調用者。
那麼,現在就有一個問題了,ServiceManger在Android系統中,也是一個服務,要想訪問它,也是要通過Binder IPC通信的,那麼我們如何來訪問ServiceManager的呢?
Android系統非常巧妙的實現了ServiceManger的訪問,系統預留了固定的句柄0,來代表ServiceManager服務,客戶端通過0,就能訪問到ServiceManager了。
ServiceManager服務的註冊和獲取實現細節都在native層,我們來簡單分析獲取ServiceManager的過程。
ProcessState
ProcessState是負責打開Binder驅動,並做mmap映射,IPCThreadState是負責與Binder驅動進行具體的命令交互。一進程只有一個ProcessState實例,且只有在ProcessState對象建立時纔打開Binder設備以及做內存映射。
我們首先來看它的構造函數:
native/libs/binder/ProcessState.cpp
ProcessState::ProcessState(const char *driver)
: mDriverName(String8(driver))
, mDriverFD(open_driver(driver))
, mVMStart(MAP_FAILED)
, mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
, mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
, mExecutingThreadsCount(0)
, mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
, mStarvationStartTimeMs(0)
, mManagesContexts(false)
, mBinderContextCheckFunc(nullptr)
, mBinderContextUserData(nullptr)
, mThreadPoolStarted(false)
, mThreadPoolSeq(1)
, mCallRestriction(CallRestriction::NONE)
{
if (mDriverFD >= 0) {
// mmap the binder, providing a chunk of virtual address space to receive transactions.
mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
if (mVMStart == MAP_FAILED) {
// *sigh*
ALOGE("Using %s failed: unable to mmap transaction memory.\n", mDriverName.c_str());
close(mDriverFD);
mDriverFD = -1;
mDriverName.clear();
}
}
LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened. Terminating.");
}
根據參數傳遞的binder驅動文件地址,打開驅動,並mmap進內存中。
參數定義:
#ifdef __ANDROID_VNDK__
const char* kDefaultDriver = "/dev/vndbinder";
#else
const char* kDefaultDriver = "/dev/binder";
#endif
- DEFAULT_MAX_BINDER_THREADS
我們注意這個變量,定義了Binder服務端進程所允許的最大線程數量,這裏的默認值是15,也就是一個服務最多能有15個線程同時提供服務,如果同一時間,訪問該服務的Client端超過了15個,系統就會拋出錯誤。
ServiceManager的獲取
當我們需要通過ServiceManager獲取某個服務時,就會調用IServiceManager.cpp中的defaultServiceManager方法:
sp<IServiceManager> defaultServiceManager()
{
if (gDefaultServiceManager != nullptr) return gDefaultServiceManager;
{
AutoMutex _l(gDefaultServiceManagerLock);
while (gDefaultServiceManager == nullptr) {
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(nullptr));
if (gDefaultServiceManager == nullptr)
sleep(1);
}
}
return gDefaultServiceManager;
}
該方法調用了ProcessState::self()->getContextObject(nullptr))獲取ServiceManager服務。
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
return getStrongProxyForHandle(0);
}
這裏,我們可以通過0句柄來獲取ServiceManager服務了。
Binder驅動
Binder驅動就如同路由器一樣,是整個通信的核心;驅動負責進程之間Binder通信的建立,Binder在進程之間的傳遞,Binder引用計數管理,數據包在進程之間的傳遞和交互等一系列底層支持。Binder驅動是運行在內核態的,數據在使用Binder驅動傳輸時,需要在內核內存空間與用戶內存空間進行拷貝操作。
當2個進程C和S進行跨進程通信時,C傳遞給SM(ServiceManager)一個S的名稱,SM查找已註冊的列表,從列表中解析出S在客戶端C中的Binder引用,C就可以使用這個Binder引用進行通信了。Binder引用會把數據轉化成可序列化的對象,經過Binder驅動程序,把數據拷貝到S進程中,然後再反序列化,最終獲得C傳遞過來的數據,並調用S端的本地方法來實現服務的調用。
每個需要通過Binder通信的進程都需要打開/dev/binder驅動一次。Binder驅動的啓動是在ProcessState中進行的,並做了mmap映射。在mmap調用之後,內核會調用驅動,爲進程創建內存緩衝區(只讀,防止用戶空間對其修改),用於接收數據。這塊內存緩衝區對應了2個虛擬內存地址空間,一個是內核的虛擬空間,另一個是進程用戶的虛擬空間。
在Server端進程角度來看,內核與用戶內存空間,通過mmap映射到了同一塊物理內存上了。Client端發送數據時,只是將數據從Client的用戶內存空間,複製到了內核內存空間,由於Server端和內核使用了內存映射,對應了同一塊物理內存,所以經過一次拷貝過程,Server端就可以獲取到數據了。
我們在Java層使用的各種Binder對象,實際上只是native層的一個代理。各種服務的實體對象,都是在native層聲明的,並且都需要註冊到Binder驅動中。Binder驅動同時維護了各個Client中的引用於Binder實體之間的映射關係。
總結
- Android IPC方式使用了Binder IPC方案。
- Binder具有高效、安全等優點。
- Binder在執行IPC數據傳遞過程中只執行了1次數據拷貝。
- Binder機制包括了4個組成部分:Client、Server、ServiceManager、Binder驅動。
- ServiceManager維護了系統所有的服務索引,負責註冊和查找已註冊服務。
- ServiceManager在系統中的引用id是0,系統通過0即可訪問到ServiceManager服務了。
- Service服務默認最多隻能提供15個線程供Client提供訪問支持。
- Binder驅動的啓動是在ProcessState中進行的,並做了mmap映射。
- Binder驅動運行在內核態中。
- Binder驅動是實現IPC通信的核心。