日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Android10?Binder原理概述深入解析_Android

作者:格子里的夢 ? 更新時間: 2022-11-23 編程語言

IPC工具介紹

Binder作為Android 眾多的IPC通訊手段之一,在Framework的數據傳輸中起到極為關鍵的作用。為什么Google需要重新創造Binder這么一個IPC工具,使用linux默認提供的Pipe、Socket、共享內存、信號、消息隊列等IPC工具不行嗎?

答案是 這些傳統的linux IPC工具有一部分android也在使用,只是在某些場合下它們無法滿足需求,所以才創造了Binder這么一個工具。 為了更好地向各位讀者解釋為什么需要Binder,我們先來簡單地認識一下linux傳統的IPC工具,讓大家對它們的優勢和劣勢有一個更為直觀的認識。

Pipe

管道是一種最基本的IPC工具,作用于有血緣關系的進程之間,完成數據傳遞。調用pipe系統函數即可創建一個管道。它有如下特質:

  • 其本質是一個偽文件(實為內核緩沖區)
  • 由兩個文件描述符引用,一個表示讀端,一個表示寫端。
  • 規定數據從管道的寫端流入管道,從讀端流出,一般只能單向通信,雙向通信需建立兩個管道。
  • 只能用于父子、兄弟進程(有共同祖先)間通信。
  • 數據一旦被讀走,便不在管道中存在,不可反復讀取。 因此,管道的局限性表現得非常明顯,它并不適合一對多的方式建立通訊(盡管技術上能夠實現),原因在于第5條,管道中的數據無法反復讀取。類似的還有FIFO(命名管道),它在管道的基礎上做了升級,擺脫了第4條的共同祖先的限制,但仍要面臨一對多通訊的困境。

framework中有沒有使用Pipe進行通訊?答案是有,但是用的很少,相比之下用的更多的是FIFO!!各位讀者如果感興趣的話,可以在源碼中搜一下 TransferPipe這個類,在其中可以找到Pipe的痕跡。

Sign

信號是由用戶、系統或者進程發送給目標進程的信息,以通知目標進程某個狀態的改變或系統異常。linux系統已經預置了一部分信號標識,它們都有著特殊的含義,部分信號如下所示:

  • SIGHUP:本信號在用戶終端結束時發出,通常是在終端的控制進程結束時,通知同一會話期內的各個作業,這時他們與控制終端不在關聯。比如,登錄Linux時,系統會自動分配給登錄用戶一個控制終端,在這個終端運行的所有程序,包括前臺和后臺進程組,一般都屬于同一個會話。當用戶退出時,所有進程組都將收到該信號,這個信號的默認操作是終止進程。此外對于與終端脫離關系的守護進程,這個信號用于通知它重新讀取配置文件。
  • SIGINT:程序終止信號。當用戶按下CRTL+C時通知前臺進程組終止進程。
  • SIGQUIT:Ctrl+\控制,進程收到該信號退出時會產生core文件,類似于程序錯誤信號。
  • SIGILL:執行了非法指令。通常是因為可執行文件本身出現錯誤,或者數據段、堆棧溢出時也有可能產生這個信號。
  • SIGTRAP:由斷點指令或其他陷進指令產生,由調試器使用。
  • SIGABRT:調用abort函數產生,將會使程序非正常結束。
  • SIGBUS:非法地址。包括內存地址對齊出錯。比如訪問一個4個字長的整數,但其地址不是4的倍數。它與SIGSEGV的區別在于后者是由于對合法地址的非法訪問觸發。
  • SIGFPE:發生致命的算術運算錯誤。
  • SIGKILL:用來立即結束程序的運行。不能被捕捉、阻塞或忽略,只能執行默認動作。

信號只能起到對進程的通知作用,它無法發送復雜的數據類型,不適合用于進程間的數據交換。

信號在整個framework中也扮演了極為重要的角色,各位讀者可以通過搜索sigemptysetsigaddset等關鍵字,在源碼中找到它們的身影。

message queue

消息隊列,Unix的通信機制之一,可以理解為是一個存放消息(數據)容器。將消息寫入消息隊列,然后再從消息隊列中取消息,一般來說是先進先出的順序。消息隊列本質上是位于內核空間的鏈表,鏈表的每個節點都是一條消息。每一條消息都有自己的消息類型,消息類型用整數來表示,而且必須大于 0。每種類型的消息都被對應的鏈表所維護。

其中數字 1 表示類型為 1 的消息,數字2、3、4 類似。彩色塊表示消息數據,它們被掛在對應類型的鏈表上。

消息隊列的缺陷在于:容量受到系統限制;消息隊列的發送方與接收方沒有強關聯性,容易造成發送方往消息隊列中存放了消息,沒有接收方來取消息或接收方沒有及時取消息的問題,消息的及時性無法保障。

目前在Android 10的非內核源碼范圍內,沒有發現使用消息隊列。

shared memory

共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號量,配合使用,來實現進程間的同步和通信。

共享內存利用內存緩沖區直接交換信息,無須復制,快捷、信息量大是其優點。但是共享內存的通信方式是通過將共享的內存緩沖區直接附加到進程的虛擬地址空間中來實現的,因此,這些進程之間的讀寫操作的同步問題操作系統無法實現。必須由各進程利用其他同步工具解決,開發上手難度較高,容易出錯,且存在數據安全隱患。

目前在Android 10的非內核源碼范圍內,沒有發現使用共享內存。

Socket

Socket這個不需要特別介紹了,不管是做C++開發還是Java開發,都會涉及到套接字編程。相對其他的IPC方式,Socket是最適合做一對多這種通訊需求的。它的問題在于數據需要經過兩次拷貝,通訊效率相對低下。這個問題在電腦等設備上都不是什么特別大的問題,但考慮到Android搭載的移動設備,尤其是早期的移動設備,這個問題就很致命了。

framework中當然也存在Socket的使用痕跡,比如 system/core/init/init.cpp這個文件中就采用epoll機制,實現init進程與其子進程的通訊。

Android更看重的是效率和一對多通訊的問題,無法采用傳統的IPC工具實現,所以只能考慮自己另起爐灶。除此之外,傳統的IPC無法獲得對方進程的PID\UID,從而無法鑒別對象的身份,從而會使Android系統的安全性無法得到保證(ps:無法獲得對方進程的身份指的是Linux默認沒有提供獲取通訊進程的身份的接口,并不是說采用傳統IPC沒有辦法實現這樣的安全管控需求,只是谷歌在綜合考慮了上述所有的因素的情況下,在共享內存的基礎上做了一套新的解決方案)。

這里給各位讀者留個思考題,有興趣的讀者可以自己動手去實驗一下:

在一對多通訊的場景下,Binder的傳輸效率一定會比Socket高嗎?(提示:Socket包括BIO、NIO、NIO2、epoll等,請不要局限在BIO的通訊方式)

AIDL

AIDL 是 Android interface definition Language 的英文縮寫, 意思Android 接口定義語言,它與Binder有著千絲萬縷的聯系。

AIDL是谷歌使用Java 編程語言的語法定義的專門服務于Binder IPC通訊的腳本語言,推出的根本原因是為了避免Binder通訊中大量模板代碼的書寫。AIDL腳本會在編譯期間,由Android SDK 工具生成基于該 .aidl 文件的 IBinder 接口,并將其保存到項目的 generated/ 目錄中。

我們來看一個簡單的aidl文件:

packageackage com.example.commonservice;
// Declare any non-default types here with import statements
interface ITtsService {
   void showTts(in String uid,in int textId,in boolean toPlayTts,in int type);
   boolean isShowing();
}

它生成的java文件如下所示:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.example.commonservice;
// Declare any non-default types here with import statements
public interface ITtsService extends android.os.IInterface
{
  /** Default implementation for ITtsService. */
  public static class Default implements com.example.commonservice.ITtsService
  {
    @Override public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException
    {
    }
    @Override public boolean isShowing() throws android.os.RemoteException
    {
      return false;
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.example.commonservice.ITtsService
  {
    private static final java.lang.String DESCRIPTOR = "com.example.commonservice.ITtsService";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.example.commonservice.ITtsService interface,
     * generating a proxy if needed.
     */
    public static com.example.commonservice.ITtsService asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.commonservice.ITtsService))) {
        return ((com.example.commonservice.ITtsService)iin);
      }
      return new com.example.commonservice.ITtsService.Stub.Proxy(obj);
    }
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_showTts:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          int _arg1;
          _arg1 = data.readInt();
          boolean _arg2;
          _arg2 = (0!=data.readInt());
          int _arg3;
          _arg3 = data.readInt();
          this.showTts(_arg0, _arg1, _arg2, _arg3);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_isShowing:
        {
          data.enforceInterface(descriptor);
          boolean _result = this.isShowing();
          reply.writeNoException();
          reply.writeInt(((_result)?(1):(0)));
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.example.commonservice.ITtsService
    {
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }
      @Override public android.os.IBinder asBinder()
      {
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor()
      {
        return DESCRIPTOR;
      }
      @Override public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeString(uid);
          _data.writeInt(textId);
          _data.writeInt(((toPlayTts)?(1):(0)));
          _data.writeInt(type);
          boolean _status = mRemote.transact(Stub.TRANSACTION_showTts, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().showTts(uid, textId, toPlayTts, type);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override public boolean isShowing() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        boolean _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_isShowing, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().isShowing();
          }
          _reply.readException();
          _result = (0!=_reply.readInt());
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      public static com.example.commonservice.ITtsService sDefaultImpl;
    }
    static final int TRANSACTION_showTts = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_isShowing = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(com.example.commonservice.ITtsService impl) {
      if (Stub.Proxy.sDefaultImpl == null && impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.example.commonservice.ITtsService getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException;
  public boolean isShowing() throws android.os.RemoteException;
}

雖然是簡短的一個aidl文件,但生成的模板代碼卻極為復雜,為了整理清楚這段代碼的結構,筆者先隱藏其部分內容:

public interface ITtsService extends android.os.IInterface
{
    /** Default implementation for ITtsService. */
    public static class Default implements com.example.commonservice.ITtsService
    {
    }
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.example.commonservice.ITtsService
    {
        private static class Proxy implements com.example.commonservice.ITtsService
        {
        }
        static final int TRANSACTION_showTts = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_isShowing = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
    public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException;
    public boolean isShowing() throws android.os.RemoteException;
}

可以看到,代碼的結構如同套娃一樣,一層接一層。各位讀者不妨思考一下,如果不這樣套娃,把ITtsService里的DefaultStub類移到外面來,這樣做可不可以?

答案是,可以的,不過類的命名方式可能要稍微做一下修改,如ITtsService_DefalutITtsService_Stub,以便于引用上的區分。當然,代碼的結構不是重點,雖然谷歌的方式可讀性會差一點,但開發人員不需要直接和這些源碼打交道,也不是不可以接受。

Binder不一定都是跨進程通訊,同樣也支持同進程通訊,比如 Activity綁定Service,通過Binder實現數據傳輸。在同一進程通訊的情況下,Stub類身兼兩職,因其implements了ITtsService,它可以作為客戶端的調用方;同時,它也是服務端的實現方。而在跨進程通訊的情況下,則由Proxy來擔任客戶端的調用方。

至于Default這個類的作用,暫時不明,無法找到相關的資料得知為什么谷歌要生成這么一個類。

在此,AIDL的介紹先告一段落,其中更多的細節將放到后續的文章中再做補充。

HIDL

HIDL的生命周期及其短暫,它從Android 8引入,然后在Android 10 立馬被 Stable AIDL 所取代,雖然沒啥存在感,但還是簡單地提及一下吧。

HAL 接口定義語言(簡稱 HIDL,發音為“hide-l”)是用于指定 HAL 和其用戶之間的接口的一種接口描述語言 (IDL)。HIDL 允許指定類型和方法調用(會匯集到接口和軟件包中)。從更廣泛的意義上來說,HIDL 是指用于在可以獨立編譯的代碼庫之間進行通信的系統。

HIDL 旨在用于進程間通信 (IPC)。進程之間的通信采用 Binder 機制。對于必須與進程相關聯的代碼庫,還可以使用直通模式(在 Java 中不受支持)。

更多HIDL相關的資料,可以參考 source.android.google.cn/docs/core/a…

原文鏈接:https://juejin.cn/post/7153135284207484959

欄目分類
最近更新