Analysis of Binder Mechanism of Android Process Communication

IBinder

Binder Inherited from  IBinder, so let's understand it first.


IBinder is an interface that represents a capability to transfer across processes. As long as this interface is implemented, this object can be passed across processes.

IBinder is the core part of the high-performance, lightweight remote invocation mechanism, which defines the basic interface of remote operation objects.


The most critical of these methods is  transact():

public boolean transact(int code, Parcel data, Parcel reply, int flags)
    throws RemoteException;

corresponding to it is Binder.onTransact()

protected boolean onTransact(int code, Parcel data, Parcel reply,
        int flags) throws RemoteException {...}

You can see that these two methods are very similar. Let's introduce the various parameters in the method:

  • code: Action to be executed, similar to Handler's msg.what, the following codes are defined in IBinder 
    • PING_TRANSACTIONpingBinder() , indicating that the method is to be called 
    • DUMP_TRANSACTION, indicating that you want to get the internal state of Binder
    • SHELL_COMMAND_TRANSACTION, which executes a shell command
    • INTERFACE_TRANSACTION, which asks the callee for the interface descriptor
    • TWEET_TRANSACTION
    • LIKE_TRANSACTION
    • If we need custom code, the value range of code needs to be between  FIRST_CALL_TRANSACTION(0x00000001) and  LAST_CALL_TRANSACTION(0x00ffffff)
  • datareply: incoming parameters and returned values
  • flags: Indicates whether to block and wait for the return value, there are two values 
    • 0
    • FLAG_ONEWAY (0x00000001), indicating that the Client  transact() is a one-way call, and returns immediately after execution

①A common scenario is that we call  IBinder.transact() an IBinder object to send a request, and then Binder.onTransact() get the call through the Binder, and then the target of the remote operation gets the corresponding call.

This process can be done not only within the same process, but also across processes (IPC).

IBinder.transact() The method is synchronous, after it is called, it  Binder.onTransact() does not return until the call is complete.

②The  IBinder.transact() data transmitted by the method is saved as an  Parcel object, Parcel in which the data and the metadata describing the data are stored, and the metadata keeps the reference of the IBinder object in the cache area, so that different processes can access the same data.

Therefore, when an IBinder object is written into a Parcel object and then sent to another process, when the other process sends the IBinder object back, the IBinder object received by the original process and the one that was sent out are the same reference.

This is a very critical point that the reference does not change after being transferred across processes, which allows the  IBinder/Binder object to be used as a unique identifier (such as a token or something) when communicating across processes.

③ The system has a thread pool for processing things in each process, and these threads are used to schedule cross-process access of other processes to the current process.

For example, when process A initiates IPC to process B,  transact() the thread called in A will block. The transaction thread pool in B receives A's IPC, calls the target object's  Binder.onTransact() method, and returns a Parcel with the result. Once the result is received, the thread blocked in A can continue execution.

This process is very similar to thread communication.

④The Binder mechanism also supports recursive calls between processes .

For example, process A initiates IPC to process B, and process Binder.onTransact() B  uses it transact() to initiate IPC to process A, then process A will respond to B's call while waiting for the call to return, and execute Binder on the called object. .onTransact() method.

This mechanism can make us feel that the call across the process is no different from the call within the process, which is very important.

⑤ When communicating across processes, we often want to know whether another process is available. IBinder provides three methods for checking:

  1. transact() 
    • When the process of the IBinder you called does not exist, an exception will be  RemoteException thrown
  2. pingBinder() 
    • This method returns false when the remote process does not exist
  3. linkToDeath() 
    • This method can be registered with IBinder  IBinder.DeathRecipient, it will be called when the process where IBinder is located exits
/**
 * Check whether the remote Binder object exists
 *
 * Returns false if it doesn't exist
 */
public boolean pingBinder();
/**
 * Register a listener for Binder destruction
   If a Binder is destroyed (usually the process it is in is closed), the BinderDied method of DeathRecipient will be called back
 * Note that only the remote Binder will be monitored, and the local Binder will generally not be destroyed unless the current process exits
 *
 * If the Binder process to be registered has been destroyed, a RemoteException is thrown
 */
public void linkToDeath(DeathRecipient recipient, int flags)
        throws RemoteException;

/**
 * linkToDeath registers the interface for listening for callbacks
 */
public interface DeathRecipient {
    public void binderDied();
}

Binder

The official documentation suggests:

In daily development, we generally do not need to implement IBinder, and we can directly use the Binder provided by the system .

Binder implements the operations defined by IBinder, which is the basis of Android IPC. It is used for cross-process operations in various Managers (ActivityManager, ServiceManager, etc.) that are usually contacted, and when binding Services.

Its existence does not affect the life cycle of an application, it is available as long as the process that created it is running.

Usually we need to use it in top-level components (Service, Activity, ContentProvider), so that the system knows that your process should always be preserved.

The following introduces several key methods of Binder:

Implemented   method IBinder : transact()

public final boolean transact(int code, Parcel data, Parcel reply,
        int flags) throws RemoteException {
    if (false) Log.v("Binder", "Transact: " + code + " to " + this);

    if (data != null) {
        data.setDataPosition(0);
    }
    boolean r = onTransact(code, data, reply, flags);
    if (reply != null) {
        reply.setDataPosition(0);
    }
    return r;
}

As you can see, this method is called  onTransact() , and then the returned result is returned.

Then look at the  onTransact() method:

protected boolean onTransact(int code, Parcel data, Parcel reply,
        int flags) throws RemoteException {
    if (code == INTERFACE_TRANSACTION) {    //获取接口描述
        reply.writeString(getInterfaceDescriptor());
        return true;
    } else if (code == DUMP_TRANSACTION) {    //获取当前状态
        ParcelFileDescriptor fd = data.readFileDescriptor();
        String[] args = data.readStringArray();
        if (fd != null) {
            try {
                dump(fd.getFileDescriptor(), args);
            } finally {
                IoUtils.closeQuietly(fd);
            }
        }
        // Write the StrictMode header.
        if (reply != null) {
            reply.writeNoException();
        } else {
            StrictMode.clearGatheredViolations();
        }
        return true;
    } else if (code == SHELL_COMMAND_TRANSACTION) {    //执行 shell 脚本
        ParcelFileDescriptor in = data.readFileDescriptor();
        ParcelFileDescriptor out = data.readFileDescriptor();
        ParcelFileDescriptor err = data.readFileDescriptor();
        String[] args = data.readStringArray();
        ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);
        try {
            if (out != null) {
                shellCommand(in != null ? in.getFileDescriptor() : null,
                        out.getFileDescriptor(),
                        err != null ? err.getFileDescriptor() : out.getFileDescriptor(),
                        args, resultReceiver);
            }
        } finally {
            IoUtils.closeQuietly(in);
            IoUtils.closeQuietly(out);
            IoUtils.closeQuietly(err);
            // Write the StrictMode header.
            if (reply != null) {
                reply.writeNoException();
            } else {
                StrictMode.clearGatheredViolations();
            }
        }
        return true;
    }
    return false;
}

也没看出什么特别的,系统的 Binder.onTransact() 方法只定义了系统要进行的操作,我们如果创建自己的 Binder 时,就需要重写这个方法,根据 code 对传入的参数 data 做相应的处理,然后写入 reply,这样就能返回操作后的数据。

另外一个关键的方法 attachInterface

public void attachInterface(IInterface owner, String descriptor) {
    mOwner = owner;
    mDescriptor = descriptor;
}
/* mObject is used by native code, do not remove or rename */
private long mObject;
private IInterface mOwner;
private String mDescriptor;

这个方法的作用是将一个描述符、特定的 IInterface 与当前 Binder 绑定起来,这样后续调用 queryLocalInterface 就可以拿到这个 IInterface,那 IInterface 又是什么呢?

public interface IInterface
{
    /**
     * Retrieve the Binder object associated with this interface.
     * You must use this instead of a plain cast, so that proxy objects
     * can return the correct result.
     */
    public IBinder asBinder();
}

其实看名字就可以大概猜出来,IInterface 应该就是进程间通信定义的通用接口,我们通过定义接口,然后再服务端实现接口、客户端调用接口,就可以实现跨进程通信。

IInterface 里只定义了一个 asBinder() 方法,这个方法可以返回当前接口关联的 Binder 对象。

Binder 通信机制

上面介绍了 Binder 类以及相关的方法,但是这只是 Binder 机制的最基础部分。

我们平常看的文章或者面试时,说的 Binder 其实是范围更大的整个 “Binder 消息通信机制”。


在 Android 系统的 Binder 机制中,由四个组件组成,分别是:

  • Client
  • Server
  • ServiceManager:提供辅助管理 Server 的功能
  • Binder 驱动程序:整个机制的核心

Binder 驱动

驱动程序一般指的是设备驱动程序(Device Driver),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作。

我们知道,在 Linux 系统中,内存空间分为两部分:

  • 用户空间:运行着应用程序
  • 内核空间:运行着系统内核和驱动


 
 

用户空间中的进程无法直接访问内核空间,需要通过上图中的 System Call Interface (系统调用接口),通过这个统一入口,所有资源访问都是在内核的控制下执行,这样可以避免用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。

同样的,用户空间中的进程直接也不可以直接访问数据,需要通过内核空间进行中转。

在 Binder 机制中,由 Binder 驱动负责完成这个中转操作,主要过程如下:

  • 当 Client 向 Server 发起 IPC 请求时,Client 会先将请求数据从用户空间拷贝到内核空间
  • 数据被拷贝到内核空间之后,驱动程序将内核空间中的数据拷贝到 Server 位于用户空间的缓存中

这样,就成功的将 Client 进程中的请求数据传递到了 Server 进程中。

实际上,Binder 驱动是整个 Binder 机制的核心。除了实现数据传输之外,Binder 驱动还是实现线程控制(通过中断等待队列实现线程的等待/唤醒),以及 UID/PID 等安全机制的保证。 

Service Manager

ServiceManager 运行在用户空间,它负责管理 Service 注册与查询。

看下 ServiceManager 的代码:

public final class ServiceManager {
    private static final String TAG = "ServiceManager";

    private static IServiceManager sServiceManager;
    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }

    /**
     * 根据 Service 名称获取 Service
     */
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                //如果不存在就去 IServiceManager 中找,这时可能会阻塞
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

    /**
     * 添加一个 Service 到 Manager 中
     */
    public static void addService(String name, IBinder service) {
        try {
            getIServiceManager().addService(name, service, false);
        } catch (RemoteException e) {
            Log.e(TAG, "error in addService", e);
        }
    }

    /**
     * 添加一个 Service 到 Manager 中,如果 allowIsolated 为 true 表示运行在沙盒中的进程也可以访问这个 Service

     */
    public static void addService(String name, IBinder service, boolean allowIsolated) {
        try {
            getIServiceManager().addService(name, service, allowIsolated);
        } catch (RemoteException e) {
            Log.e(TAG, "error in addService", e);
        }
    }

    /**
     * Retrieve an existing service called @a name from the
     * service manager.  Non-blocking.
     */
    public static IBinder checkService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().checkService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in checkService", e);
            return null;
        }
    }

    /**
     * 获取 Service 列表
     */
    public static String[] listServices() {
        try {
            return getIServiceManager().listServices();
        } catch (RemoteException e) {
            Log.e(TAG, "error in listServices", e);
            return null;
        }
    }

    /**
     * 当前进程首次被 activity manager 创建时调用这个方法 
     */
    public static void initServiceCache(Map<String, IBinder> cache) {
        if (sCache.size() != 0) {
            throw new IllegalStateException("setServiceCache may only be called once");
        }
        sCache.putAll(cache);
    }
}

可以看到 ServiceManager 提供了 Service 的添加和查询,其中主要操作都是通过 IServiceManager,它是何方神圣?

public interface IServiceManager extends IInterface
{
    /**
     * 获取一个 Service,不存在就会阻塞几秒
     */
    public IBinder getService(String name) throws RemoteException;

    /**
     * 不阻塞的获取 Service
     */
    public IBinder checkService(String name) throws RemoteException;

    public void addService(String name, IBinder service, boolean allowIsolated)
                throws RemoteException;

    public String[] listServices() throws RemoteException;

    /**
     * 为 Service Manager 添加权限,具体作用暂不追究
     */
    public void setPermissionController(IPermissionController controller)
            throws RemoteException;

    static final String descriptor = "android.os.IServiceManager";

    //定义了一些用于调用 transact() 方法的 code
    int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
    int CHECK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;
    int ADD_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;
    int LIST_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
    int CHECK_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
    int SET_PERMISSION_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;
}

可以看到 IServiceManager 是一个接口,它定义了管理 Service 的一些方法,同时继承了 IInterface。最常见的实现是 BnServiceManager.getDefault()

Binder 机制跨进程通信流程

上面两节简单介绍了 Binder 机制中非常重要的两部分,ServiceManager 和 Binder 驱动。

在 Binder 机制的四个部分中, Client、Server 和 ServiceManager 运行在用户空间,Binder 驱动程序运行内核空间。

Binder 就是一种把这四个组件粘合在一起的粘结剂。

这个流程是如何进行的呢?

  • Binder实体 
    • Binder 实体实际上是内核中 binder_node 结构体的对象,它的作用是在内核中保存 Server 和ServiceManager 的信息(例如,Binder 实体中保存了 Server 对象在用户空间的地址)
    • Binder 实体是 Server 在 Binder 驱动中的存在形式,内核通过 Binder 实体可以找到用户空间的Server对象
    • 在上图中,Server 和 ServiceManager 在 Binder 驱动中都对应的存在一个 Binder 实体
  • Binder 引用 
    • Binder引用实际上是内核中 binder_ref 结构体的对象,是某一个 Binder 实体的引用,通过Binder 引用可以在内核中找到对应的 Binder 实体
    • 如果将 Server 看作是 Binder 实体的话,那么 Client 就好比 Binder 引用,Client 通过保存一个Server 对象的 Binder 引用,再通过该 Binder 引用在内核中找到对应的 Binder 实体,进而找到Server 对象,然后将通信内容发送给 Server 对象
  • 远程服务 
    • 本地服务的代理,通过调用远程服务可以间接调用本地服务

1.Client、Server 和 ServiceManager 处于用户空间的不同进程。 
2.Binder 实体和 Binder 引用都是内核(即 Binder 驱动)中的数据结构。

它们的关系如下:

每一个 Server 在内核中就表现为一个 Binder 实体,而每一个 Client 则表现为一个 Binder 引用。这样,每个 Binder 引用都对应一个 Binder 实体,而每个 Binder 实体则可以多个 Binder 引用。

Binder 跨进程通讯流程主要为如下 4 步:

  1. ServiceManager 初始化 
    • 当该应用程序启动时,ServiceManager 会和 Binder 驱动进行通信,告诉 Binder 驱动它是服务管理者
    • Binder 驱动新建 ServiceManager 对应的 Binder 实体
  2. Server 向 ServiceManager 注册自己 
    • Server 向 Binder 驱动发起注册请求,Binder 为它创建 Binder 实体
    • 然后如果 ServiceManager 中没有这个 Server 时就添加 Server 名称与 Binder 引用到它的 Binder 引用表
  3. Client 获取远程服务 
    • Client 首先会向 Binder 驱动发起获取服务的请求,传递要获取的服务名称
    • Binder 驱动将该请求转发给 ServiceManager 进程
    • ServiceManager 查找到 Client 需要的 Server 对应的 Binder 实体的 Binder 引用信息,然后通过 Binder 驱动反馈给 Client
    • Client 收到 Server 对应的 Binder 引用后,会创建一个 Server 对应的远程服务(即 Server 在当前进程的代理)
  4. Client 通过代理调用 Server 
    • Client 调用远程服务,远程服务收到 Client 请求之后,会和 Binder 驱动通信
    • 因为远程服务中有 Server 的 Binder 引用信息,因此驱动就能轻易的找到对应的 Server,进而将Client 的请求内容发送 Server

Binder 机制的优点

对比 Linux 上的其他进程通信方式(管道/消息队列/共享内存/信号量/Socket),Binder 机制的优点有以下几点:

  1. 高效简单

    • 通过驱动在内核空间拷贝数据,不需要额外的同步处理
    • 对比 Socket 等传输效率高
  2. 安全

    • Binder 机制为每个进程分配了 UID/PID 来作为鉴别身份的标示,并且在 Binder 通信时会根据UID/PID 进行有效性检测
  3. Client/Server 架构

    • 这种架构使得通讯更为简单

总结

Binder 机制的学习过程是痛苦的 T.T,即使光看 Java 层也费了不少功夫,这可能是由于我实战中没有遇到,光看源码和抽象概念掌握太慢的原因吧。

本文简单介绍了 Binder 机制,参考了很多优秀的文章,真佩服他们!

借用《Android 开发艺术探索》对 Binder 的概括:

  • 从代码角度来看,Binder 是一个类,实现了 IBinder 接口;
  • 从来源看,Binder 来自于 OpenBinder,是 Android IPC 机制中的一种,Binder 还可以理解成一个虚拟物理设备,设备驱动是dev/binder;
  • 从 Framework 层看,Binder 是 Service Manager 连接各种Manager(ActivityManager,PackageManager…) 和相应Service (ActivityManagerService, PackageManagerService…) 的桥梁;
  • 从客户端看,Binder 是客户端服务器通讯的媒介。



Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326559984&siteId=291194637