Android BINDER详解

1.   进程间通信的本质(2个进程)

用户空间的进程如果想相互通信, 必须经过内核, 因为不同进程的用户地址空间是独立的, 但是共享同一个内核空间.

内核为了支持进程间通信, 一般会有一个驱动, 以字符设备的形式存在(也可以是其它形式, 这个驱动的本质就是在不同的进程间传递数据). 两个进程间通信一般以client/server的形式进行, 大致流程如下:

         server进程打开内核设备节点, 并开始监听该节点是否有数据写入;

         client进程打开内核设备节点, 往这个节点里面写入一个数据(copy_from_user);

         内核驱动唤醒阻塞在该节点上的server进程, 并把数据传递给它(copy_to_user);

         server进程收到数据, 解析数据, 然后执行相应的动作.

在Android中, binder驱动充当上述流程中的内核驱动, 它是一个字符设备, 设备节点是/dev/binder.

为了便于理解, 我们先假设整个系统只有2个进程, 一个是Client进程, 另外一个是Server进程. 它们通信的图示如下:

1.1 ProcessState & IPCThreadState

为了方便进程间通信, Android封装了两个类: frameworks/native/libs/binder/ProcessState.cpp 和 frameworks/native/libs/binder/IPCThreadState.cpp.

这两个cpp文件里面既包含了Server进程需要用到的一些接口, 也包含了Client进程需要用到的一些接口(额.., 不清楚Android为啥不分开?). 当这两个文件被编译到Server进程时, 它们就是Server进程的一部分; 当被编译到Client进程时, 就是Client进程的一部分.

ProcessState提供的接口主要有:

         ProcessState::self() : 主要目的是open(/dev/binder), Server/Client都会用到, 因为它们都得打开内核设备节点, 读写数据

         ProcessState::startThreadPool() : Server进程会用到, 主要目的是创建一个线程, 在线程里面while循环, 监听是否有数据写入设备节点.

IPCThreadState的主要目的是通过ioctl与/dev/binder交互, 也就是说它负责读写设备节点,完成用户空间与内核空间的数据交互. 主要接口有:

         IPCThreadState::transact : Client进程会用到, 目的是往设备节点写入数据.

         transact里面会分waitForResponse(reply)和waitForResponse(NULL)这两种情况, 前者的意思是Client发送一个数据(请求/命令…)给Server后, 需要等待Server的执行结果, 结果存储在reply中; 后者的意思是Client只是发送个请求, 不需要等待对应的执行结果.

         waitForResponse会调用talkWithDriver, 后者会调用ioctl与/dev/binder交互

         IPCThreadState::joinThreadPool : Server进程会用到, ProcessState::startThreadPool()最终就是调用的IPCThreadState::joinThreadPool, 目的就是监听设备节点. joinThreadPool会调用getAndExecuteCommand(), 后者会调用talkWithDriver()监听设备节点, 当收到一个数据后, 就解析并执行相应的操作(ExecuteCommand).

加上ProcessState & IPCThreadState后, 图示变成了下面这个样子:

843c8e35-8d7a-450e-9da2-e69fc120452e.002.pnguploading.4e448015.gif转存失败重新上传取消

1.2 IBinder, BBinder, BpBinder, BpRefBase

为了更进一步封装, Android系统抽象出了IBinder, BBinder, BpBinder,BpRefBase这4个类. 抽象出这4个类的原因不仅仅是为了进一步封装, 更是为了多个进程间的通信, 由于本章暂时只讨论2个进程, 所以关于多进程的细节后文在说, 本章只考虑双进程的情况下, 这4个类扮演什么样的角色.

这3个类的头文件以及实现文件如下:

         IBinder : frameworks/native/include/binder/IBinder.h

IBinder是一个接口类, 也就是C++中的纯虚基类. 它是BBinder和BpBinder的基类, 它里面定义的一个重要接口函数是transact.

         BpBinder : frameworks/native/include/binder/BpBinder.h

实现文件 : frameworks/native/libs/binder/BpBinder.cpp

BpBinder继承了IBinder, 实现了IBinder中定义的transact函数.

BpBinder用于Client端, BpBinder::transact会调用IPCThreadState::transact, 继而往/dev/binder节点写入数据, 完成封装动作.

Client端的代码会继承BpBinder, 当Client想往Server发送数据时, 直接调用BpBinder:: transact函数即可? 但实际上并不是这样, Client端的代码实际上会继承BpRefBase, 通过BpRefBase调用BpBinder:: transact函数. 至于Android系统为什么要这么设计, 我也不清楚原因.

         BpRefBase : frameworks/native/include/binder/Binder.h

实现文件 : frameworks/native/libs/binder/Binder.cpp

BpRefBase里面定义了一个变量 : IBinder* const mRemote; 这个mRemote实际上指的就是BpBinder.

BpRefBase用于Client端, Client端的代码会继承BpRefBase, 当Client端想往Server发送数据时, 会使用mRemote->transact()的形式, 实际上也就是调用BpBinder:: transact

         BBinder : frameworks/native/include/binder/Binder.h

实现文件 : frameworks/native/libs/binder/Binder.cpp

BBinder继承了IBinder, 也实现了IBinder中定义的transact函数.前文我们说过Server端会开启一个线程, 监听/dev/binder节点. 当收到数据时, Server端的这个线程会调用BBinder::transact.

BBinder用于Server端, BBinder除了继承了IBinder中的transact接口, 它自己也定义了一个onTransact接口, Server端的代码一般会实现onTransact接口. BBinder::transact里面会调用onTransact函数, 继而把Client端通过内核传递过来的数据交给Server端的代码进行处理, 从而完成一次进程间通信.

加上IBinder, BBinder, BpBinder, BpRefBase后, 图示变成了下面这个样子:

1.3 Ixxx, BpInterface, BnInterface

前文我们看到了Android系统提供的很多封装, 通过这些封装, 进程与进程之间的数据管道已经建立完毕了, 类似于网络传输中的数据链路层已经准备好了, 此时Client和Server已经可以尽情的传递数据了.

但依旧还有一个问题需要考虑: 类似于网络传输中的应用层协议, Server/Client端也需要约定相应的协议, 例如Client端和Server端事先约定”消息打印”服务的cmd id为0, 当Client端想要Server端printf一个消息时, 就会发送数字0给Server, Server收到0后知道应该去执行打印服务.

这种”应用”层的协议Android系统没法帮你封装, 需要Server/Client编写者自己去约定, 不过Android(更确切的说是C++)提供了一种机制, 让程序员可以更加方便的完成这种”约定”.

这种机制就是程序员事先定义一个接口类Ixxx, 在Ixxx中定义一个枚举类型, 代表cmd id; 然后定义相应的接口函数, 每个接口函数会对应一个cmd id. 例如如下这个接口类:

//frameworks/native/include/binder/IPermissionController.h

class IPermissionController : public IInterface

{

public:

    DECLARE_META_INTERFACE(PermissionController);  //暂时忽略, 这个地方也很重要, 不过主要是跟多进程间的通信相关

    virtual bool checkPermission(const String16& permission, int32_t pid, int32_t uid) = 0;

    virtual void getPackagesForUid(const uid_t uid, Vector<String16> &packages) = 0;

    virtual bool isRuntimePermission(const String16& permission) = 0;

    enum {

        CHECK_PERMISSION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,

        GET_PACKAGES_FOR_UID_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 1,

        IS_RUNTIME_PERMISSION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 2

    };

};

然后Client端的代码会继承Ixxx & BpRefBase, 实现Ixxx中定义的接口函数, 在接口函数中会调用mRemote->transact函数, 继而把数据写往/dev/binder节点.

在Android系统中, 几乎所有Client端的代码都会双继承Ixxx & BpRefBase, 因此Android系统定义了一个模板类BpInterface, 方便这种双继承的动作.

下面我们看看代码中BpInterface的定义以及IPermissionController的客户端代码:

//frameworks/native/include/binder/IInterface.h

template<typename INTERFACE>

class BpInterface : public INTERFACE, public BpRefBase

{

public:

                                BpInterface(const sp<IBinder>& remote);

protected:

    virtual IBinder*            onAsBinder();

};

//frameworks/native/libs/binder/IPermissionController.cpp

class BpPermissionController : public BpInterface<IPermissionController>

{

public:

    BpPermissionController(const sp<IBinder>& impl)

        : BpInterface<IPermissionController>(impl)

    {

    }

    virtual bool checkPermission(const String16& permission, int32_t pid, int32_t uid)

    {

        Parcel data, reply;

        data.writeInterfaceToken(IPermissionController::getInterfaceDescriptor());

        data.writeString16(permission);

        data.writeInt32(pid);

        data.writeInt32(uid);

        //remote()调用会返回mRemote这个变量

        remote()->transact(CHECK_PERMISSION_TRANSACTION, data, &reply);

        // fail on exception

        if (reply.readExceptionCode() != 0) return 0;

        return reply.readInt32() != 0;

    }

    ……

}

再来看看Server端, Server端的代码会继承Ixxx & BBinder. 首先会实现BBinder中定义的onTransact函数, 在onTransact函数会中解析/dev/binder节点传上来的数据, 并根据不同的数据调用Ixxx中定义的不同的接口函数. 而这些接口函数的实现都是在Server端的代码中, Server端的代码会实现Ixxx中定义的所有的接口函数.

类似Client端, Server端的代码几乎都会双继承Ixxx & BBinder, 为了方便这种双继承动作, Android系统定义了一个模板类BnInterface.

相关实例代码如下:

//frameworks/native/include/binder/IInterface.h

template<typename INTERFACE>

class BnInterface : public INTERFACE, public BBinder

{

public:

    virtual sp<IInterface>      queryLocalInterface(const String16& _descriptor);

    virtual const String16&     getInterfaceDescriptor() const;

protected:

    virtual IBinder*            onAsBinder();

};

//frameworks/native/include/binder/IPermissionController.h

class BnPermissionController : public BnInterface<IPermissionController>

{

public:

    virtual status_t    onTransact( uint32_t code,

                                    const Parcel& data,

                                    Parcel* reply,

                                    uint32_t flags = 0);

};

//frameworks/native/libs/binder/IPermissionController.cpp

status_t BnPermissionController::onTransact(

    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)

{

    switch(code) {

        case CHECK_PERMISSION_TRANSACTION: {

            CHECK_INTERFACE(IPermissionController, data, reply);

            String16 permission = data.readString16();

            int32_t pid = data.readInt32();

            int32_t uid = data.readInt32();

            bool res = checkPermission(permission, pid, uid);

            reply->writeNoException();

            reply->writeInt32(res ? 1 : 0);

            return NO_ERROR;

        } break;

    ……

}

        注意, BnPermissionController不能被实例化哦, 因此它继承了IPermissionController, 但是IPermissionController中定义的checkPermission这个存虚函数在BnPermissionController这个类中还是没有定义

        一般情况下, 我们还需要写一个Server端的cpp文件, 定义一个类, 继承BnPermissionController, 然后实现Ixxx中所有的存虚函数, 然后就万事大吉了.

前述的这些封装是为了实现面向对象的通信方式而对协议层的封装, 至此管道,协议都已经准备好, Client端和Server端就可以用面向对象的方式进行通信了.

2.   多进程通信

在第一章我们假设系统中只有2个进程, 这种情况下很多事情都变得简单了, 例如: client端只管打开/dev/binder节点, 然后往节点传送数据; server端只管监听/dev/binder节点, 收到数据后就开始解析数据, 执行相应的服务.

但是真实系统中会有很多个进程, client端在往/dev/binder节点写入数据时, 必须告诉binder驱动这些数据该送往哪个server进程; 同样, 所有的server进程都会监听/dev/binder节点, 这种监听动作其实就是block在/dev/binder节点上, 理想的情况应该是只有收到与本server相关的数据时, server进程才会被un-block并开始解析数据.

如何解决这个问题? Binder系统采用的办法简单来说就是“handle”! 这个”handle”其实是一个int32型的整数, 类似于linux中的文件描述符”fd”.

在linux中, 当我们想与一个文件交互时, 调用open得到此文件的fd, 后面操作此fd即可, 也就是说fd就代表这个文件.

在binder中, 当client想与某个service通信时, 它首先会得到此service的handle, 后面操作此handle即可, 也就是说handle就代表这个service.

fd是用open得到的一个随机整数, handle也是一个随机整数, 只不过它不是用open得到的, 而是从ServiceManager得到的; 另外handle >=1. 为什么0不行? 因为0号handle有特殊用途, 它是专门代表ServiceManager这个service的.

还不是很明白? 没关系, 接下来看看这个神秘的ServiceManger!

2.1  0号handle/ServiceManager

servicemanager在Android中是一个独立的进程, 它的实现文件是frameworks/native/cmds/servicemanager/service_manager.c , 它会被编译成一个可执行文件servicemanager, 在init.rc中把它作为一个service运行.

servicemanager负责管理Android中所有Server进程, 它主要提供了两个接口: addSerivce和getService.

Server进程会调用addSerivce, 把自己向servicemanager注册. 注册的时候需要提供service的name和Service的基类(BBinder)

Client进程会调用getService(name), 通过name向servicemanager查询Server, 最终的结果就是会获取到name对应的Server线程的编号(handle).

    从这里可以看出, name在servicemanager中必须是唯一的, 否则servicemanager就无法区分不同个Server了. 随便提一下, 这个name是字符串.

注意, 从servicemanager的角度来说, Server进程和Client进程都是自己的client. 它们在调用addSerivce/getService时, 必须跨进程通信, 因为servicemanager / Server进程 / Client进程 这三者是不同的进程.

回想一下前述内容, 如果要跨进程通信, 本质上来说, 就是client进程得拿到Server的编号. 但Server进程/Client进程此时没有任何手段能”查询”到servicemanager这个进程的编号(因为不知道向谁去查). 如何解决? 很简单, 把servicemanager这个特殊进程的编号写死, 置为0. 这样当Server进程/Client进程想与servicemanager通信时, 直接通过0号即可.

既然servicemanager作为所有Server进程/Client进程的服务端, 那是不是应该提供相应的客户端的封装, 以便客户端能方便与servicemanager通信呢? Android系统确实提供了这样的封装, 它就是frameworks/native/libs/binder/IServiceManager.cpp. 这个文件的细节现在就不看了, 等读完本文的所有内容, 你自然就能理解了.

接下来, 我们详细看看Server端是如何向ServiceManger注册的以及Client端是如何获取handle的.

为了能更详细的说明整个过程, 我们必须先介绍内核Binder驱动中的几个重要的数据结构, 它们是 : binder_proc; binder_node; binder_ref; 在此之后, 在介绍注册服务(addService)和获取服务(getService).

2.2 Binder驱动

代码路径

kernel/drivers/ (不同Linux分支路径略有不同)

  – staging/android/binder.c

  或

  – android/binder.c

Binder驱动是Android专用的,但底层的驱动架构与Linux驱动一样。binder驱动在以misc设备进行注册,作为虚拟字符设备,没有直接操作硬件,只是对设备内存的处理。主要是驱动设备的初始化(binder_init),打开 (binder_open),映射(binder_mmap),数据操作(binder_ioctl).

2.2.1        binder_proc

《1.1》        节中有提到, 不管是Server进程还是Client进程, 都会调用ProcessState::self().

ProcessState::self()会open(/dev/binder), 这个open动作会导致驱动层创建一个binder_proc结构. 如下:

843c8e35-8d7a-450e-9da2-e69fc120452e.005.pnguploading.4e448015.gif转存失败重新上传取消

也就是说, 每个用户空间的进程, 如果想通过binder通信, 不管是Server进程还是Client进程, 在内核空间都会有一个与之对应的数据结构binder_proc.

另外, 由于ProcessState被设计为单例模式, 因此每个进程只会open一次/dev/binder, 也就是说每个进程只会存在唯一一个对应的binder_proc.

如果说我们把binder通信想象成电力传输: 电从一端传到另外一端需要通过电线, binder_open就相当于在内核空间创建了这样一根”电线”, 通过这根”电线”, Client/Server就可以交换数据了. binder_proc相当于这个电线的最外层包皮.

但是单有这个最外层包皮是无法传输的, 我们还需要电线里面的“铜芯线”, 这个“铜芯线”就是下面介绍的binder_node.

2.2.2        binder_node

一根电线里面会有多跟“铜芯线”, 典型的是3根(三相交流). 同样的, 一个Server进程可以提供一个或者多个Service, 例如main_mediaserver.cpp这个进程就注册了MediaPlayerService、CameraService等多个服务. 每个服务就相当于一根“铜芯线”.

在binder驱动层面, 每个服务都对应一个binder_node. 类似于“铜芯线”之与“外层包皮”, 每个binder_node都是隶属于对应进程的binder_proc, 通过红黑色进行管理, 代码片段如下:

nodes就是本binder_proc下所有binder_node的根节点.

《2.2.1》我们提到Server进程和Client进程都会创建对应的binder_proc, 那是不是它俩也都会创建binder_node呢? 这个问题的根本在于进程中是否有注册服务, 一般来讲Server进程肯定会注册服务的, 因此Server进程的binder_proc下会挂有一个或多个binder_node; Client进程如果是纯粹的只获取服务, 那么它的binder_proc下不会挂有binder_node, 但有的Client进程即需要获取服务, 也会想它人提供自己的服务, 此时它的binder_proc下任然会挂有binder_node.

想一下binder_node里面会主要会存储什么东西? 前文说过在服务的注册过程中, 会传递service的name和BBinder, 这里的BBinder其实就是Service实现代码的基类指针, 通过它我们可以访问Service的所有实现函数(因为C++规定, 基类指针可以指向它的任何派生类的对象). binder_node里面会存储这个BBinder的指针, 代码如下:

843c8e35-8d7a-450e-9da2-e69fc120452e.007.pnguploading.4e448015.gif转存失败重新上传取消

ptr和cookie都是用于存储BBinder的, 两个稍微有点区别(ptr == BBinder->getWeakRefs(); cookie == BBinder), 这个区别暂时不用去管它. 主要用到的是cookie.

存储BBinder是很有意义的. 设想一下, 服务端的代码派生了BBinder, 然后实现了很多自己的功能函数, 客户端与服务端通信的主要目的就是想调用服务端自己实现的这些功能函数, 只不过两者属于不同进程, 不可直接调用, 所以Android才有了Binder这套通信机制.

前面我们说过, 客户端要想与服务端通信, 首先会拿到一个handle, 然后操作这个handle即可. 更进一步来说, 客户端会把这个handle传给内核, 然后跟内核说”我要调用这个handle对应的服务的函数”, 内核拿到这个handle之后, 如果能通过某种机制, 找到它对应的binder_node(handle代表一个服务, 而每个服务在内核层都会创建一个binder_node, 所以肯定能找到), 继而就能知道对应的binder_node->cookie, 也就是服务的BBinder, 然后通过BBinder就能调用服务端的相应函数了.

实事上目前的Binder机制确实是这样做的, handle与binder_node的对应关系是靠下一节介绍的binder_ref维护的.

还有一个值得思考的问题, binder_proc是在open时创建的, 那binder_node是在何时创建的呢? 答案是在注册服务(addService)的时候.

2.2.3        binder_ref

《2.2.2》说到当Server进程注册服务的时候, 驱动层会创建对应的binder_node; 对应的, Client进程在获取服务的时候, 驱动层会创建对应的binder_ref.

binder_ref的主要目的是维护handle和binder_node之间的对应关系, 代码如下:

desc就是handle, node指向对应的服务的binder_node.

binder_ref是在获取服务(getService)的时候创建的, 当Client想获取某个服务时, 它会把服务的name传给ServiceManager, 在这个过程中, 驱动层会创建对应的binder_ref, 依靠ServiceManager通过name找到对应的binder_node, 赋值给binder_ref->node; 然后驱动层会分配一个整型数, 也就是desc; 然后这个desc会反馈给Client进程, 也就是Client拿到的handle.

反过来在看看Client与Server通信的过程. 假设Client已经拿到了Server的handle, 当Client想与Server交互时, 它会把这个handle写入内核. Binder驱动收到这个handle之后, 会从当前进程(此时是Client)的binder_proc中找到与handle对应的binder_ref, 继而就找到了binder_ref->node, 这个node就是代指对应的服务, 从而完成Client与Server的一次通信.

2.3 注册服务(addService)

注册服务是指的Server进程把自己的某个服务注册到ServiceManager里面, 此时Server进程是客户端, ServiceManager进程是服务端. 两者通过0号handle通信.

首先必须意识到, ServiceManger也是一个进程, 因此它在驱动层存在一个与之对应的binder_proc; 另外这个进程也提供了自己的服务(addService/getService), 因此在驱动层存在一个与之对应的binder_node, 这个node的名字叫binder_context_mgr_node. 它是一个特殊的node, 整个binder驱动里面有且只能有一个binder_context_mgr_node. 这个node固定的与0号handle对应, 也就是说任何时候, 只要驱动收到的handle是0, 那就一定会对应到binder_context_mgr_node, 这就是为什么任何进程在与ServiceManager交互的时候直接使用0号handle即可的根本原因.

我们在从需要注册服务的Server进程侧看一下, 它首先会得到ServiceManager的代理IServiceManager, 这个过程其实就是把0号handle层层封装, 最后变成IServiceManager, 具体代码细节请参考这里. 然后它会把自己的”Service name”和”Service BBinder指针”通过IServiceManager写入内核驱动, 意思就是说: 嘿, ServiceManger进程, 请把我的”name” 和 “BBinder指针”保存起来.

IServiceManager收到”Service name”和”Service BBinder指针”后, 会把它俩层层封装, 变成一个数据包, 然后通过ioctl把这个数据包写入内核驱动. 注意IServiceManager是ServiceManger进程在客户端的代理, 也就是上述动作都发生在”注册服务的Server进程”中.

当驱动收到数据包之后, 会把数据包拆分: 首先得到handle == 0, 得知这是一次与ServiceManger通信的请求; 然后拆分出具体动作, 得知这是一次服务添加过程(在数据包中有特定的数据代表是添加还是获取服务). 对于首次添加的服务, 会在驱动层创建一个binder_node结构体, 并把”Service BBinder指针”保存到这个结构体里面, 然后把这个结构体挂载到该服务隶属的进程的binder_proc下. 此时的图示类似于下面这样:

然后, 驱动层会在ServiceManger进程对应的binder_proc下创建一个binder_ref, 这个binder_ref指向刚刚创建的binder_node, 同时驱动层会计算出对应的handle值(计算的规则详见后文的代码分析), 此时图示变成了这样:

这里有一个疑问, 上面计算出来的handle值有何意义? 前文说道客户端想与服务端通信时, 首先会向ServiceManager查询, 最终得到一个handle值, 难道这里创建的handle值就是以后给客户端使用的? 答案是No. 这里的handle值只是给ServiceManager内部使用的(具体用途2.4节说明), 与客户端拿到的handle没有什么关系(客户端如何获取handle, 请查看2.4节: 获取服务).

之后, 驱动层会把”Service name”(从数据包中拆解出来)和handle1(刚刚计算出来的)封装成数据包, 然后传递给用户空间的ServiceManager进程. ServiceManager进程在收到数据包之后, 会拆解出”Service name”和“handle1值”, 然后把它俩存储起来. “Service name”和“handle1”在ServiceManager中是一一对应的关系, 并且都是唯一的. 图示如下:

上述就是注册服务的宏观逻辑, 如果想配合源代码仔细研究, 网上的这篇文章写的很好, 可以参考 : Binder系列5—注册服务(addService)

2.4  获取服务(getService)

理解了注册服务的过程, 再来看获取服务相当就比较容易了.

获取服务的起点是Client知道服务的Service name, 终点是拿到与这个Service相对应的handle值. 下面细看一下这个流程.

Client端首先也是获取到ServiceManger的代理IServiceManager, 然后通过IServiceManager把”Service name”写入驱动层, 驱动收到这个请求后, 会把”Service name”传给用户空间的ServiceManager进程.

在ServiceManager进程中, 由于已经存在着”Service name”和“handle”的一一对应关系, 因此ServiceManager进程可以通过“Service name”找到对应的”handle”, 然后把”handle”回传给驱动层.

驱动层收到传回的”handle”之后, 会从ServiceManager进程对应的binder_proc的红黑树中找到对应的binder_ref, 关键字就是“handle”.

找到binder_ref之后, 就能找到它所指向的binder_node. 注意这个binder_node其实就是Client端想要找的那个服务端, 也就是与“Service name”对应的那个服务端.

说道这里大家可能又有一个疑问, 难道这里描述的handle不是最终返回给Client端的那个吗? 答案是不是的. 这里的handle是ServiceManager内部使用的, 它是在注册服务的过程中创建的(前文已经介绍了是如何创建的), 这个handle的主要作用是为了通过”Service name”最终找到对应的binder_node.

那Client端需要的handle是怎么来的呢?

当我们找到与“Service name”对应的binder_node之后, 驱动层会创建一个binder_ref, 这个binder_ref指向刚刚那个binder_node, 在创建binder_ref的过程中会计算出一个handle值(图中的handle2), 并最终会把这个handle2反馈给Client端. 图示如下:

843c8e35-8d7a-450e-9da2-e69fc120452e.012.pnguploading.4e448015.gif转存失败重新上传取消

至此, Client端就完成了通过”Service name”获取到handle的整个过程, 之后Client就可以通过这个handle2与服务端通信了.

这里同样只是宏观描述了整个过程, 如果对代码细节有兴趣, 可以参考这篇文章: Binder系列6—获取服务(getService)

关于handle值的计算规则, 文中有一段总结比较经典:

handle值计算方法规律:

         每个进程binder_proc所记录的binder_ref的handle值是从1开始递增的;

         所有进程binder_proc所记录的handle=0的binder_ref都指向service manager;

         同一个服务的binder_node在不同进程的binder_ref的handle值可以不同;

3.   Binder数据传输与解析

Binder通信一般是两个进程交互的, 示意图如下:

843c8e35-8d7a-450e-9da2-e69fc120452e.013.jpeguploading.4e448015.gif转存失败重新上传取消

Client端会把请求写入驱动层, Server端则会从驱动层读取请求, 然后执行相应的服务. 下文分别阐述这两种情况的细节.

3.1             Client端把数据写入驱动层

数据传输涉及到两部分 : 数据传输的路径和数据包的封装/解封过程.

下图完整了显示了一次从上至下的数据传输流程. 宏观上来说: 在用户空间, 数据是逐步封装, 最终变成一个binder_write_read的数据包; 内核空间, 数据是逐步解封, 最终生成了一个binder_transaction事物.

         BpxxxFunc是客户端的实现代码, 这里有两种典型情况:

         一种是普通的客户端, 例如BpInputFlinger:: doSomething , 这种情况下会有3个主要的数据: code, 它一般是enum型, 意思是想调用服务端的哪个服务(也就是服务端的哪个函数); data , 它是一个parcel型的数据, 代表想传给服务端的数据; reply, 它也是一个parcel型的, 代表想从服务端获得的返回值.

         另一种是ServiceManager的客户端, 例如BpServiceManager:: addService, 这种情况与普通客户端的唯一不同之处在于, 它会在data这个parcel里面多加入一个flat_binder_object数据. 如是添加服务, flat_binder_object里面存储是服务的BBinder指针; 如果是获取服务, flat_binder_object里面存储的是驱动反馈上来的handle值.

         BpBinder::transact, 它是binder框架层的实现. 它里面有一个mHandle, 也就是前文所说的通过ServiceManager获取到的服务端的handle值. mHandle是在创建BpBinder对象时赋值的.

         IPCThreadState.writeTransactionData, 同样是binder框架层的实现. 它首先会创建一个binder_transaction_data, 把前面传下来的数据都写入到这个结构体里面. 然后它会在binder_transaction_data前加入BC_XXX, 封装成mOut.

BX_XXX是与binder驱动交互的协议, 其中BC_XXX代表用户空间到驱动层; BR_XXX代表驱动层到用户空间.

BC_XXX最常见的值就是BC_TRANSACTION, 代表Client向Binder驱动发送请求数据. 其它取值参见BC_PROTOCOL.

         IPCThreadState.talkWithDriver, 它会把前一环节创建的mOut和mIn(两者是同一种数据类型, 只不过mOut里面填充了很多数据, mIn相当于一个空的存储池. 驱动层最终会把反馈给用户空间的数据写入mIn)封装成binder_write_read, 然后通过ioctl与驱动层交互. binder_write_read数据包会传入驱动层.

         驱动层收到收到用户空间的binder_write_read数据包后会依次解析数据包的内容, 然后进行相应的处理, 这里不细看了.

3.2             Server端从驱动层读取数据

         Server端注册完服务之后, 会block在驱动层, 等待客户端向自己发送请求.

当客户端发送完请求会, 最终会生产一个binder_transaction事物, 并将binder_transaction.work加入到对应的server的队列中, 这个队列可以是thread->todo, 也可以是proc->todo(第4章会详述这几个概念). 然后调用wake_up_interruptible唤醒服务端.

服务端被唤醒后, 就从调出block状态, 然后从thread->todo或者proc->todo中取出一个binder_work.

         binder_work是binder_transaction内部的一个数据, 因此使用container_of可以获得binder_work对应的binder_transaction指针.

         通过binder_transaction.buffer中的数据(这些数据是客户端传过来的), 可以构建一个binder_transaction_data数据结构.

         binder_transaction_data最终会被封装成binder_write_read, 然后返回给用户空间.

         用户空间在收到数据后, 会调用IPCThreadState::executeCommand解析数据包, 拆分出BR_XXX, BR_XXX最常见的值是BR_TRANSACTION, 代表Binder驱动向Server端发送请求数据. 其它取值参见BR_PROTOCOL.

         如果BR_XXX是BR_TRANSACTION, 则会从数据包binder_transaction_data中取出tr.cookie, 然后将其转换为<BBinder*>(tr.cookie), 然后调用<BBinder*>(tr.cookie)->transact函数, 并传入相应的参数(tr.code, buffer, &reply, tr.flags).

这些参数的具体意义是:

         tr.code代表要调用服务端的哪个函数

         buffer是一个parcel数据包, 是从客户端传过来的

         reply也是一个parcel数据包, 代表需要回传给客户端的数据. 此时它是空的, 内容由服务端填充.

         tr.flags是标志位, 它的定义参考transaction_flags, 最常见的取值是TF_ONE_WAY

         Binder.cpp实现了一个默认的transact函数, 在函数里面会调用onTransact. onTransact是BBinder定义的一个接口类, 任何服务端都必须继承并实现onTransact函数

         服务端的BnXXX::onTransact被调用.

         在BnXXX::onTransact函数中, 会根据tr.code的值, 调用不用的实现函数BnXXXFunc

4.   Binder事物

每一次数据传输, 在Binder驱动层都会生成一个事物binder_transaction, 并把这个事物挂载到相应的链表todo上. 所谓相应的链表的意思是: 这个事物应该由谁来处理, 就挂载到谁的链表上. 比如当客户端向服务端发送一个请求时, 客户端会先把数据写入驱动层, 驱动层会生产一个事物binder_transaction, 这个事物显然应该由对应的服务端来处理, 因此这个事物会挂载到对应的服务端的todo下面.

本章主要介绍与binder事物相关的几个数据结构, 它们是binder_transaction / binder_work / binder_thread / binder_proc->todo.

binder_transaction

定义文件: drivers/android/binder.c # L355

类型

成员变量

解释

int

debug_id

用于调试

struct binder_work

work

binder工作类型

struct binder_thread *

from

发送端线程

struct binder_transaction *

from_parent

上一个事务

struct binder_proc *

to_proc

接收端进程

struct binder_thread *

to_thread

接收端线程

struct binder_transaction *

to_parent

下一个事务

unsigned

need_reply

是否需要回复

struct binder_buffer *

buffer

数据buffer

unsigned int

code

通信方法,比如startService

unsigned int

flags

标志,比如是否oneway

long

priority

优先级

long

saved_priority

保存的优先级

kuid_t

sender_euid

发送端uid

这个结构体是在binder_transaction()函数中创建的

         binder_work代表此次事物的具体类型, 取值范围后文详述

         debug_id:是一个全局静态变量,每当创建一个binder_transaction或binder_node或binder_ref对象,则++debug_id

         from与to_thread是一对,分别是发送端线程和接收端线程

         from_parent与to_parent是一对,分别是上一个和下一个binder_transaction,组成一个链表

         执行binder_transaction()方法过程,当非oneway的BC_TRANSACTION时,则设置当前事务t->from_parent等于当前线程的transaction_stack. transaction_stack代表当前线程正在处理的事物.

         执行binder_thread_read()方法过程,当非oneway的BR_TRANSACTION时,则设置当前事务t->to_parent等于当前线程的transaction_stack

         binder_buffer是用来存储数据的, 它的细节在《Binder Buffer》一节中详述

binder_work

定义文件 : drivers/android/binder.c # L214

struct binder_work {

    struct list_head entry;

    enum {

        BINDER_WORK_TRANSACTION = 1,

        BINDER_WORK_TRANSACTION_COMPLETE,

        BINDER_WORK_NODE,

        BINDER_WORK_DEAD_BINDER,

        BINDER_WORK_DEAD_BINDER_AND_CLEAR,

        BINDER_WORK_CLEAR_DEATH_NOTIFICATION,

    } type;

};

binder_work.type设置时机:

  • binder_transaction()
  • binder_thread_write()
  • binder_new_node()

binder_thread

binder_thread结构体代表当前binder操作所在的线程.

类型

成员变量

解释

struct binder_proc *

proc

线程所属的进程

struct rb_node

rb_node

int

pid

线程pid

int

looper

looper的状态

struct binder_transaction *

transaction_stack

线程正在处理的事务

struct list_head

todo

将要处理的链表

uint32_t

return_error

write失败后,返回的错误码

uint32_t

return_error2

write失败后,返回的错误码2

wait_queue_head_t

wait

等待队列的队头

struct binder_stats

stats

binder线程的统计信息

         transaction_stack代表当前线程正在处理的事物

         todo代表属于本线程的事物链表

         wait是等待队列头, 假设当前线程没有事物需要处理, 则线程会block在wait上, 直到有人唤醒它.

         looper代表当前线程的状态, 具体意义在《Binder Thread》一节中详述.

你可能还对binder_thread的具体意义摸不着头脑, 没关系, 后面《Binder Thread》会专门讲解它. 这里我们只关注与Binder事物相关的部分.

binder_proc.todo

binder_proc前文描述过, 每一个需要Binder通信的进程在驱动层都会有一个对应的binder_proc. 这个结构体里面有一个元素如下:

struct binder_proc {

    ……

    struct list_head todo;

    wait_queue_head_t wait;

    ……

};

         todo代表属于该进程的事物的链表

         wait是等待队列头, 如果进程中没有需要处理的事物, 则会block在wait上.

thread.todo or proc.todo

既然binder_thread.todo和binder_proc.todo下面都可以挂载事物, 那到底应该挂载到哪个下面?

  • reply的过程会找到target_thread.todo;
  • 非reply则一般找到target_proc.todo;
  • 对特殊的嵌套binder call会根据transaction_stack来决定是插入事务到目标线程还是目标进程

5.   Binder Thread

Note : 也可阅读http://blog.csdn.net/kc58236582/article/details/51744601这篇文章, 写得貌似比这里还清晰些.

5.1             简介

Binder通信是C/S模式的, 服务端会等待客户端向自己发起请求. 所谓等待, 就是指服务端会有一个循环(looper), 在循环里面不停的查看是否有请求.

为了不让循环卡死Server进程, 一般会在进程里面创建一个线程, 然后把这个循环丢到线程里面去运行. 这个线程就是这里的binder_thread.

在Binder通信机制里, 客户端与服务端之间的通信是在专门的IPC通信线程中进行的. 这些线程构成一个线程池. 线程的创建和销毁是在用户空间进行的, 而对线程的控制是在驱动层进行的, 即驱动控制线程池中线程的生命, 而线程本身则是运行在用户空间的.

5.2             数据结构

与线程池相关的几个变量设置在struct binder_proc结构体中:

struct binder_proc {

  1.   …  
  2.     int max_threads;//max thread  
  3.     int requested_threads;//  
  4.     int requested_threads_started;//requested thread seq No  
  5.     int ready_threads;//available for use  
  6.     …  
  7. };  

         max_threads代表当前进程最多可以创建多少个线程(不包括5.3节所说的那两个默认线程), 用户空间可以通过ioctl命令BINDER_SET_MAX_THREADS来设置这个值, 默认情况下是15.

         ready_threads表示当前线程池中有多少可用的空闲线程.

         requested_threads请求开启线程的数量.

         requested_threads_started表示当前已经接受请求开启的线程数量

5.3             默认情况下会有几个thread

Server端代码中(例如main_mediaserver.cpp), 一般我们会看到如下两句:

  1. ProcessState::self()->startThreadPool();  
  2. IPCThread::self()->joinThreadPool(); 

第一句话会新创建一个线程, 并在线程里面放一个looper, 在looper里面循环读取和解析binder驱动层传上来的数据.

第二句话会在进程的主线程里放一个looper, 然后在looper里面循环读取和解析binder驱动层传上来的数据. 这个动作有两个目的: 一是相当于在进程main函数的最后放了一个while(1), 防止进程退出. 第二个目的就是相当于多创建了一个looper.

总结来看, 默认情况下, 会有两个thread处理驱动层上报的数据. 一般情况下这已经足够了.

但如果通信量很大, 可能会出现thread处理不过来的情况, 就向超市排队的人多了, 收银的忙不过来, 这时候就需要多开几个收银窗口.

那么何时会创建新的thread呢? 请参考下节.

另外还有一个题外话, 网上有人说上面第二句代码是多余的, 去掉也可以运行. 其本质在于, 如果有其它代码可以block住进程不让其退出, 那这里是可以去掉, 带来的影响就是默认thread只有1个, 相当于通信管道变窄了.

5.4             何时会创建新的thread

当有多个并发的IPC请求时,可能会触发内核增加更多的IPC通信线程来服务这些请求。具体的情境如下代码所示:

  1. if (proc->requested_threads + proc->ready_threads == 0 &&  
  2.         proc->requested_threads_started < proc->max_threads &&  
  3.         (thread->looper & (BINDER_LOOPER_STATE_REGISTERED |  
  4.          BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */  
  5.          /*spawn a new thread if we leave this out */) {  
  6.         proc->requested_threads++;  
  7.         binder_debug(BINDER_DEBUG_THREADS,  
  8.                  “%d:%d BR_SPAWN_LOOPER\n”,  
  9.                  proc->pid, thread->pid);  
  10.         if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))  
  11.             return -EFAULT;  
  12.         binder_stat_br(proc, thread, BR_SPAWN_LOOPER);  
  13.     }  

在请求的线程数和空闲的线程数为零, 且已经请求并开启的线程数小于线程池的最大允许线程数量时, 驱动会向用户空间发送一个BR_SPAWN_LOOPER命令以开启新的接收线程来处理请求。

我们再来看看用户空间对这个命令的处理, IPCThreadState::executeCommand中相关代码如下:

  1. case BR_SPAWN_LOOPER:  
  2.     mProcess->spawnPooledThread(false);  
  3.     break;  

直接调用ProcessState的spawnPooledThread函数创建一个新的线程. 注意这里传入的参数是false, 称之为非binder主线程. 而5.3节中介绍的两个线程, 它们都没有显示指定参数, 从而会使用参数的默认值true, 因此它俩称为binder主线程.

那binder主线程和非主线程有何区别? 详见下节.

5.5             biner主线程与非主线程区别

用户空间创建线程的函数是ProcessState::spawnPooledThread, 代码如下:

  1. void ProcessState::spawnPooledThread(bool isMain)  
  2. {  
  3.     if (mThreadPoolStarted) {  
  4.         String8 name = makeBinderThreadName();  
  5.         ALOGV(“Spawning new pooled thread, name=%s\n”, name.string());  
  6.         sp<Thread> t = new PoolThread(isMain);  
  7.         t->run(name.string());  
  8.     }  
  9. }  

这里会借用Android的Thread机制创建一个线程PoolThread, 然后调用run运行此线程.

在线程的threadloop里面, 会调用joinThreadPool:

  1. protected:
  2. virtual bool threadLoop()
  3. {
  4.         IPCThreadState::self()->joinThreadPool(mIsMain);
  5.         return false;
  6.     }
  7.  
  8.     const bool mIsMain;
  9. };

注意这个threadLoop里面return的是false, 意味着一旦joinThreadPool结束运行, 整个线程就会退出了. 因此joinThreadPool里面应该是一个while循环(也就是前文所说的looper), 一般情况下不会退出.

joinThreadPool的实现函数如下:

  1. void IPCThreadState::joinThreadPool(bool isMain)  
  2. {  
  3.     LOG_THREADPOOL(“**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n”, (void*)pthread_self(), getpid());  
  4.   
  5.     mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);  
  6.       
  7.     // This thread may have been spawned by a thread that was in the background  
  8.     // scheduling group, so first we will make sure it is in the foreground  
  9.     // one to avoid performing an initial transaction in the background.  
  10.     set_sched_policy(mMyThreadId, SP_FOREGROUND);  
  11.           
  12.     status_t result;  
  13.     do {  
  14.         processPendingDerefs();  
  15.         // now get the next command to be processed, waiting if necessary  
  16.         result = getAndExecuteCommand();  
  17.   
  18.         if (result < NO_ERROR && result != TIMED_OUT && result != -ECONNREFUSED && result != -EBADF) {  
  19.             ALOGE(“getAndExecuteCommand(fd=%d) returned unexpected error %d, aborting”,  
  20.                   mProcess->mDriverFD, result);  
  21.             abort();  
  22.         }  
  23.           
  24.         // Let this thread exit the thread pool if it is no longer  
  25.         // needed and it is not the main process thread.  
  26.         if(result == TIMED_OUT && !isMain) {//当isMain是false,并且是timeout的时候线程退出  
  27.             break;  
  28.         }  
  29.     } while (result != -ECONNREFUSED && result != -EBADF);  
  30.   
  31.     LOG_THREADPOOL(“**** THREAD %p (PID %d) IS LEAVING THE THREAD POOL err=%p\n”,  
  32.         (void*)pthread_self(), getpid(), (void*)result);  
  33.       
  34.     mOut.writeInt32(BC_EXIT_LOOPER);  
  35.     talkWithDriver(false);  
  36. }  

isMain为true, 代表是binder主线程; 为false则代表非主线程. 从代码分析可知它们的区别如下:

         26行, 说明主线程是不会退出的, 一直存在. 但是非主线程result == TIMED_OUT的时候会退出.

更进一步来说, 当驱动发现没有足够的线程处理数据时, 会发送BR_SPAWN_LOOPER到用户空间申请创建线程. 当数据处理完毕之后, 这些新创建的非主线程就会退出了.

         5行, 主线程会往驱动写入BC_ENTER_LOOPER; 而非主线程写入的是BC_REGISTER_LOOPER.

BC_ENTER_LOOPER会让驱动层的looper进入BINDER_LOOPER_STATE_ENTERED状态; BC_REGISTER_LOOPER会让驱动层的looper进入BINDER_LOOPER_STATE_REGISTERED状态.

另外, BC_REGISTER_LOOPER还会更新驱动层的几个数据:

  1. case BC_REGISTER_LOOPER:  
  2.             …  
  3.             } else {  
  4.                 proc->requested_threads–;  
  5.                 proc->requested_threads_started++;  
  6.             }  
  7.             thread->looper |= BINDER_LOOPER_STATE_REGISTERED;  
  8.             break;  

驱动会更新proc->requested_threads_started来统计当前已经请求开启并成功开启的线程数量,这个值将作为判断线程池是否已经满的依据

5.6             looper的状态迁移

在驱动层, binder_thread中有一个元素用于表示该thread的looper的状态:

  1. struct binder_thread {  
  2.     struct binder_proc *proc;  //线程所属的进程  
  3.     struct rb_node rb_node;  //红黑树的结点,进程通过该结点将线程加入到红黑树中  
  4.     int pid; //线程的pid  
  5.     int looper;  //线程所处的状态  

looper的取值范围如下

  1. enum {
  2.     BINDER_LOOPER_STATE_REGISTERED  = 0x01, // 创建注册线程BC_REGISTER_LOOPER
  3.     BINDER_LOOPER_STATE_ENTERED     = 0x02, // 创建主线程BC_ENTER_LOOPER
  4.     BINDER_LOOPER_STATE_EXITED      = 0x04, // 已退出
  5.     BINDER_LOOPER_STATE_INVALID     = 0x08, // 非法
  6.     BINDER_LOOPER_STATE_WAITING     = 0x10, // 等待中
  7.     BINDER_LOOPER_STATE_NEED_RETURN = 0x20, // 需要返回
  8. };

looper状态迁移的时机:

         收到 BC_REGISTER_LOOPER,则线程状态为BINDER_LOOPER_STATE_REGISTERED;

         收到 BC_ENTER_LOOPER,则线程状态为 BINDER_LOOPER_STATE_ENTERED;

         收到 BC_EXIT_LOOPER, 则线程状态为BINDER_LOOPER_STATE_EXITED;

其他3个状态的时机:

         BINDER_LOOPER_STATE_WAITING:

         当停留在binder_thread_read()的wait_event_xxx过程, 则设置该状态;

         BINDER_LOOPER_STATE_NEED_RETURN:

         binder_get_thread()过程, 根据binder_proc查询不到当前线程所对应的binder_thread,会新建binder_thread对象;

         binder_deferred_flush()过程;

         BINDER_LOOPER_STATE_INVALID:

         当binder_thread创建过程状态不正确时会设置.

5.7             最多能创建多少个thread

线程池的大小可以设置, 如果没有主动去设置这个大小,则默认大小为15,如下代码所示:

  1. static int open_driver()  
  2. {  
  3.     int fd = open(“/dev/binder”, O_RDWR);  
  4.     if (fd >= 0) {  
  5.         …  
  6.         size_t maxThreads = 15;  
  7.         result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);  
  8.         if (result == -1) {  
  9.             ALOGE(“Binder ioctl to set max threads failed: %s”, strerror(errno));  
  10.         }  
  11.     } else {  
  12.         ALOGW(“Opening ‘/dev/binder’ failed: %s\n”, strerror(errno));  
  13.     }  
  14.     return fd;  
  15. }  
  1. ProcessState::self()->setThreadPoolMaxThreadCount(4);  

setThreadPoolMaxThreadCount可以修改线程池的大小, 例如上述代码把线程池的大小设置为4. 这意味着该进程最多只能创建4个非主线程. 注意线程池的大小不包括主线程, 也就是说5.3节介绍的2个主线程不占用线程池空间. 所以总体来说该进程最多可以有6个线程用于处理数据.

6.   Binder Buffer

ProcessState的构造函数如下:

  1. ProcessState::ProcessState()
  2.     : mDriverFD(open_driver())
  3.    ……
  4. {
  5.     if (mDriverFD >= 0) {
  6.         // mmap the binder, providing a chunk of virtual address space to receive transactions.
  7.         mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
  8.         if (mVMStart == MAP_FAILED) {
  9.             // *sigh*
  10.             ALOGE(“Using /dev/binder failed: unable to mmap transaction memory.\n”);
  11.             close(mDriverFD);
  12.             mDriverFD = -1;
  13.         }
  14.     }
  15.  
  16.     LOG_ALWAYS_FATAL_IF(mDriverFD < 0, “Binder driver could not be opened.  Terminating.”);
  17. }

主要有2件事情:

         17行会open(/dev/binder), 这个好理解, 目的就是为了和binder驱动交互嘛

         22行, 从驱动层mmap了BINDER_VM_SIZE大小的空间(#define BINDER_VM_SIZE ((1*1024*1024) – (4096 *2)))。为何要mmap?

mmap意味着用户空间和内核空间共享同一块物理内存, 它们在交互数据时不用copy_to_user. 这个特性主要是用于驱动层向用户空间传递数据的过程. 具体来讲就是 : 当Client端向Server端发送请求时, 请求数据会先copy_from_user到内核, 然后内核把这个数据交给Server端时就会用到上面的共享内存, 不会copy_to_user了; 反过来, 当Server端处理完请求, 打算把结果返回给Client端时, Server端也会先通过copy_from_user的方式把数据交给内核, 然后内核用共享内存的方式把数据返回给Client端.

关于这块的逻辑, 可以进一步看两个代码.

         首先看看Server端是如何与内核共享的:

IPCThreadState::executeCommand是Server端负责处理请求的, 它里面有如下一段:

843c8e35-8d7a-450e-9da2-e69fc120452e.016.pnguploading.4e448015.gif转存失败重新上传取消

Server端的buffer是直接用的内核传上来的tr.data.ptr.buffer作为地址的. freeBuffer表示上层用完这个数据后, 回调freeBuffer把占用的空间释放掉, 否则共享的1M空间很快会被挤爆了.

         其次看看Client端是如何与内核共享的:

IPCThreadState::waitForResponse代表Client端请求Server处理请求并等待反馈结果, 里面有如下一段:

逻辑同上, 不在赘述.

从上可知binder一次通信过程只发生一次用内存拷贝。这也是Binder的优势之一,普通的IPC通信机制, 一般都会有2次数据拷贝(Client端进程空间拷贝到内核空间,再由内核空间拷贝到Server进程空间,会发生两次拷贝)。

对于进程和内核虚拟地址映射到同一个物理内存的操作是发生在数据接收端,而数据发送端还是需要将用户态的数据复制到内核态。到此,可能有读者会好奇,为何不直接让发送端和接收端直接映射到同一个物理空间,那样就连一次复制的操作都不需要了,0次复制操作那就与Linux标准内核的共享内存的IPC机制没有区别了,对于共享内存虽然效率高,但是对于多进程的同步问题比较复杂,而管道/消息队列等IPC需要复制2两次,效率较低。这里就不先展开讨论Linux现有的各种IPC机制跟Binder的详细对比,总之Android选择Binder的基于速度和安全性的考虑。

下面这图是从Binder在进程间数据通信的流程图,从图中更能明了Binder的内存转移关系。

843c8e35-8d7a-450e-9da2-e69fc120452e.018.jpeguploading.4e448015.gif转存失败重新上传取消

首先发送方把数据地址传入内核; 然后在内核的binder_transaction函数中会调用binder_alloc_buf从共享的内存中分配一块binder_buffer, 然后调用copy_from_user将发送放的data从用户空间拷贝到内核的binder_buffer.data中; 然后这块binder_buffer会传递给数据接收方, 由于接收方与内核共享同一块物理内存, 因此接收方访问data时就不需要在做copy动作了.

注意上述的一次拷贝只是针对data. 对于BINDER协议、target、code这些字段, 发送方会copy_from_user, 接收方会copy_to_user. 原因是这些字段数据量很少, 两次拷贝不会有太大影响.

7.   flat_binder_object的本质

7.1             第一印象 : flat_binder_object用于注册服务和获取服务

从前面的章节我们知道flat_binder_object主要是用于服务的注册和服务的获取:

         当我们向ServiceManager注册服务时, 会有一个writeStrongBinder的动作, 这个动作会把代表服务的BBinder(IBinder是基类, 指向BBinder)封装成flat_binder_object, 并最终传入驱动层. 代码如下:

843c8e35-8d7a-450e-9da2-e69fc120452e.019.pnguploading.4e448015.gif转存失败重新上传取消

843c8e35-8d7a-450e-9da2-e69fc120452e.020.pnguploading.4e448015.gif转存失败重新上传取消

843c8e35-8d7a-450e-9da2-e69fc120452e.021.pnguploading.4e448015.gif转存失败重新上传取消

         驱动层收到flat_binder_object后, 会创建这个服务对应的binder_node. 代码如下:

843c8e35-8d7a-450e-9da2-e69fc120452e.023.pnguploading.4e448015.gif转存失败重新上传取消

         当我们向ServiceManager获取服务时, 驱动层会把handle值封装成flat_binder_object对象反馈给用户空间, 用户空间通过readStrongBinder的动作即可拿到handle, 并创建基于handle的BpBinder, 这个BpBinder就是服务在客户端的代理了. 用户空间的代码如下:

843c8e35-8d7a-450e-9da2-e69fc120452e.026.pnguploading.4e448015.gif转存失败重新上传取消

至此, flat_binder_object给人的第一个感觉是用于注册服务和获取服务: 注册服务时它被传入驱动层, 里面包含代表服务的BBinder; 获取服务时它由驱动层创建, 然后返回给用户层, 用户层通过其中的handle值创建BpBinder, 之后就可以通过这个BpBinder与服务端通信了.

7.2             匿名binder通信

既然flat_binder_object给人的感觉是用于注册服务和获取服务, 那我们先思考一下服务的注册与获取是否一定要与ServiceManager挂钩? 答案是否定的!

所谓注册服务, 其实就是让驱动层创建一个与服务对应的binder_node; 所谓获取服务, 其实就是要拿到服务的handle值. 这两个过程并不一定要通过ServiceManager, 它们只与writeStrongBinder和readStrongBinder有关: 任何进程, 只要它调用了writeStrongBinder, 并把自己服务的BBinder传给writeStrongBinder, 驱动层就会为这个服务创建一个binder_node. 并且驱动层会立即创建一个flat_binder_object对象, 里面包含了handle值, 然后把这个对象返回给用户空间, 此时用户空间只要调用readStrongBinder就能拿到服务的handle值, 继而就可以与服务端通信了.

驱动层的关键代码如下: 第一个红框创建与服务对应的binder_node; 第二个红框立马创建一个包含handle的flat_binder_object

843c8e35-8d7a-450e-9da2-e69fc120452e.027.pnguploading.4e448015.gif转存失败重新上传取消

这种不通过ServiceManager进行服务注册与获取的方式称作匿名binder通信, 它的意义是服务只提供给某个特定的客户端进程, 只有这个进程才能与本服务通信. 而通过ServiceManager注册的服务则可以被任何进程获取.

从代码的角度来说, 其实匿名服务的注册与ServiceManager服务注册时同一套逻辑, 唯一的不同之处在于匿名服务注册时, target_proc是那个特定的客户端进程; 而ServiceManager服务注册时, target_proc是servicemanger进程.

目前已知的匿名服务注册主要用于APK进程与ActivityManagerService之间.

7.3             flat_binder_object只能用于服务注册和获取吗

如题, flat_binder_object给人的感觉貌似就是用于服务注册和获取的, 它的使用模式也很固定: 对于注册服务的进程, 它通过writeStrongBinder生成一个flat_binder_object并传入驱动层; 驱动层创建服务对应的binder_node并生成一个包含handle值的flat_binder_object对象反馈给用户空间; 对于获取服务的进程, 则可通过readStrongBinder从内核传上来的flat_binder_object中拿到handle值, 并用这个handle创建一个BpBinder.

flat_binder_object只能这样用吗? writeStrongBinder只能被服务端进程调用吗? readStrongBinder是不是又只能被客户端进程调用呢?

其实不然, flat_binder_object的本质是它代表着一个服务, 而writeStrongBinder和readStrongBinder只是在不同进程间传递服务的方法. 注意writeStrongBinder和readStrongBinder是成对出现的, 任何两个进程可以通过writeStrongBinder->readStrongBinder的方式传递flat_binder_object这个服务. 即使是两个客户端进程! 例如某个提供服务的进程A把flat_binder_object传递给了客户进程B, 客户进程B可以直接通过writeStrongBinder->readStrongBinder把这个flat_binder_object传递给客户进程C, 然后客户进程C就可以与服务进程A通信了. 甚至客户进程B也可以通过writeStrongBinder->readStrongBinder这个flat_binder_object传递给服务进程A, 然后客户进程A就可以调用自己提供的服务了(只是这个调用不需要在通过binder了, 直接是进程内的函数调用)!

好像不是很好理解是吗? 那我们在从代码的角度来分析一下. 一个服务可以存在于不同的进程, 只是它存在的形式略有不同: 在服务进程中, 它是BBinder; 在客户进程中, 它是BpBinder. BBinder和BpBinder都是继承于IBinder, 因此不管在服务进程还是客户进程的代码中, 通常都用IBinder指针代表服务. 因此不管是服务进程还是客户进程, 当你想把某个服务传递给另外一个进程时, 你只管以IBinder为参数调用writeStrongBinder就可以了, 在writeStrongBinder中会做如下处理:

843c8e35-8d7a-450e-9da2-e69fc120452e.020.pnguploading.4e448015.gif转存失败重新上传取消

只有你传递的不是非法指针(binder != NULL), writeStrongBinder就会根据情况自动生成的不同的flat_binder_object对象: 如果binder->localBinder()的返回值为空, 说明此时是在客户端进程调用的writeStrongBinder, 这种情况下生成的flat_binder_object是BINDER_TYPE_HANDLE类型的, 而且既然是客户端进程, 那肯定能拿到handle值(proxy->handle()); 如果binder->localBinder()的返回值不为空, 则说明此时实在服务端进程调用writeStrongBinder, 这种情况下生成的flat_binder_object是BINDER_TYPE_BINDER类型的, 而且既然是服务端进程, 那肯定能知道服务的BBinder指针, 把这个指针赋值给obj.binder和obj.cookie.

上面代码中参数binder是IBinder类型的, 为什么通过binder->localBinder()就能知道此时到底处于服务端进程还是客户端进程呢? localBinder是IBinder这个基类定义的一个接口函数, 基类中有一个默认实现:

843c8e35-8d7a-450e-9da2-e69fc120452e.029.pnguploading.4e448015.gif转存失败重新上传取消

BBinder重载了这个实现, 但是BpBinder没有重载. BBinder重载的代码如下:

前面也说过, 一个服务在服务端进程是以BBinder的形式存在的, 在客户端进程是以BpBinder的形式存在的. 因此IBinder->localBinder()如果是在服务进程, 则代表的是调用BBinder->localBinder(); 如果是在客户进程, 则代表的是调用BpBinder->localBinder(). 所以通过localBinder()的返回值, 就能知道IBinder对象到底是处于服务进程还是客户进程.

当驱动层收到flat_binder_object后, 会根据它类型的不同区别对待:

         如果fp->type是BINDER_TYPE_BINDER类型的, 毫无疑问, 此时是服务端进程想把这个的服务传递某个客户端进程. 如果这个服务对应的binder_node还不存在, 驱动层会先创建它, 然后反馈给客户端进程一个包含handle、类型为BINDER_TYPE_HANDLE的flat_binder_object. 客户端通过readStrongBinder来获取和解析这个object.

843c8e35-8d7a-450e-9da2-e69fc120452e.031.pnguploading.4e448015.gif转存失败重新上传取消

         如果fp->type是BINDER_TYPE_HANDLE类型的, 那此时代表某个客户端进程想把handle这个服务传递给另外一个进程, 另外的这个进程即可以是某个其它的客户端进程, 也可以是handle这个服务所在的服务端进程:

843c8e35-8d7a-450e-9da2-e69fc120452e.032.pnguploading.4e448015.gif转存失败重新上传取消

         如果ref->node->proc == target_proc, 则代表想把handle这个服务传递给这个服务所在的服务端进程. 此时驱动层会生产一个BINDER_TYPE_BINDER类型的flat_binder_object并反馈给用户空间, 服务端进程通过readStrongBinder既可以获取和解析这个object.

         如果是else呢, 则代表想把handle这个服务传递某个其它的客户端进程. 此时驱动层会生产一个BINDER_TYPE_HANDLE类型的flat_binder_object并反馈给用户空间, “其它的客户端进程”同样通过readStrongBinder获取和解析这个object.

OK, 既然用户空间都是通过readStrongBinder获取和解析object, 那我们看看这个readStrongBinder的细节:

代码清楚的显示, 根据flat->type的不同, 会生产不同的对象: 如果type是BINDER_TYPE_BINDER, 说明某人想把服务回传给服务本身所在的进程, 此时根据flat->cookie生成一个IBinder对象即可, 注意此时IBinder其实是代表BBinder; 如果type是BINDER_TYPE_HANDLE, 说明某人想把服务传递给某个客户端进程, 此时根据flat->handle生成一个IBinder对象(getStrongProxyForHandle中会创建一个BpBinder), 此时IBinder其实是代表BpBinder.

说了这么多, 不知道自己表达清楚没有. 对于flat_binder_object的本质, 我们可以下结论了.

7.4             结论

对于flat_binder_object的本质, 我们的结论是:

一个flat_binder_object代表一个服务, 它的主要作用是在不同的进程之间传递服务, 以便不同的进程都可以访问这个服务. 传递的方法是writeStrongBinder和readStrongBinder, 任何一个src进程如果想把服务传递给一个dst进程, src进程只需调用writeStrongBinder, 然后dst进程马上通过readStrongBinder就可以拿到这个服务. 服务在服务端进程中是以BBinder的形式存在的, 在所有的客户端进程中都是以BpBinder的形式存在的.

另外还需意识到, 传递服务的目的主要是让其它进程访问此服务, 但这不是唯一功能. 很多地方都会用IBinder作为key构建map, 此时传递服务的目的就是只是传递一个普通的数据(就像传递一个int型参数一样).

8.   Binder与sp的关系

sp指的是strongpoint.

问题源自全志的在BootAnimation里面做的一件事情: 用MediaPlayer类播放视频, 代码如下:

这段代码播放视频没有问题, 出题出在视频播放完毕后, 做了这样一个赋值:

843c8e35-8d7a-450e-9da2-e69fc120452e.036.pnguploading.4e448015.gif转存失败重新上传取消

原意是想通过赋值为NULL, 执行MediaPlayer()的析构函数, 从而释放MediaPlayer.

mPlayer是sp类型的, 给sp类型的赋NULL, 会decStrong, 也就是把引用计数减1. 当引用计数减到0时, 系统会做delete对象, 进而执行对象的析构函数. 所以单从上面的代码来看, mPlayer = NULL貌似可以达到执行析构的效果.

但实际情况下却没有, 因此在代码中加了两个ALOGD, 把引用计数打印出来看看.

第一次new动作之后, 结果 : D/BootAnimation( 2133): startBootMedia, sc(1), wc(2)

第二次setDataSource后, 结果 : D/BootAnimation( 2133): startBootMedia : setDataSource, sc(2), wc(4)

看来是因为setDataSource把引用计数加1了(总数为2了), 但mPlayer = NULL只会decStrong一次, 所以无法执行析构函数.

为什么setDataSource会把引用计数加1?

因为MediaPlayer实现了一个Binder服务端 : BnMediaPlayerClient. 在setDataSource过程中, 会通过writeStrongBinder的方式把这个服务端传递给MediaPlayerService. MPS会通过readStrongBinder的方式拿到这个服务的客户端代理, 后面MPS要通过这个代理向MediaPlayer进程反馈播放时的一些消息(例如是否暂停, 是否eos, 是否遇到错误), 这个过程陈作notify.

回到引用计数的问题上, 现在问题变为 : 为什么一个Binder服务端把服务传递给另外一个进程后, 这个服务的引用计数会加1?

8.1             从逻辑上看, Binder服务端的引用计数取决于什么?

引用计数的目的是为了保证只要资源还在被使用, 那它就不应该被释放. Binder服务也可以理解为一种资源, 与普通资源的不同之处在于它可以被其它进程使用, 因此从正常逻辑来说, 只要有任何一个客户端正在使用服务, 此服务在服务端进程中就不应该被释放.

那怎么做到这一点呢? 还是借助引用计数的功能, 只要有任何一个客户端获取了本服务, 那么在服务端进程中就把本服务的引用计数加1.

客户端获取服务, 从本质上来说就是客户端进程执行了readStrongBinder()操作, 所以在这个环节把服务端进程中的服务的引用计数加1是最合理的.

下面从Android代码层面看下系统是如何保障这个机制的.

8.2             客户端获取服务 -> Binder服务引用计数加1

废话少说, 先看一下客户端获取服务的代码流程:

Parcel::readStrongBinder() -> unflatten_binder -> ProcessState::getStrongProxyForHandle.

getStrongProxyForHandle中会新建BpBinder对象, 如下:

843c8e35-8d7a-450e-9da2-e69fc120452e.037.pnguploading.4e448015.gif转存失败重新上传取消

注意最后一句话, 它把BpBinder赋值给了一个sp<IBinder>类型, 这个赋值动作会导致sp执行构造函数:

这个构造函数会执行decStrong动作, 也就是执行BpBinder->decStrong, 最终会导致BpBinder-> onFirstRef被执行:

在onFirstRef中执行了incStrongHandle:

这句话的意思是让服务端进程执行BC_ACQUIRE请求. 注意是服务端进程哦.

我们在来看服务端进程是如何响应的:

843c8e35-8d7a-450e-9da2-e69fc120452e.041.pnguploading.4e448015.gif转存失败重新上传取消

看见没, 这里把服务的引用计数加1了.

8.3             客户端释放服务 -> Binder服务引用计数减1

有加1的地方就要有减1的地方. 那客户端何时会去做减1的动作呢?

这次我们倒推, 如果要让Binder服务引用计数减1, 那BpBinder要执行onLastStrongRef, 在里面请求服务端进程执行BC_RELEASE动作.

服务端收到BR_RELEASE请求后, 会mPendingStrongDerefs.push(obj). 最终在processPendingDerefs函数里面针对每一个obj执行obj->decStrong(mProcess.get()).

所以现在的问题是客户端何时才会执行onLastStrongRef? 答案是在BpBinder对象的引用计数减为0时:

843c8e35-8d7a-450e-9da2-e69fc120452e.043.pnguploading.4e448015.gif转存失败重新上传取消

那BpBinder对象何时才会减为0?

我们以MediaPlayerService为例来说明. 前面说过, MediaPlayer实现了Binder服务端, 然后在setDataSource把这个服务传递给MPS. 我们看下MPS是如何处理传递过来的服务的:

这里有两个关键 : 第一个是readStrongBinder, 它最终会创建BpBinder并返回一个sp<IBinder>.

第二个是interface_cast. 它会经历如下这串流程:

asInterface实现如下:

843c8e35-8d7a-450e-9da2-e69fc120452e.046.pnguploading.4e448015.gif转存失败重新上传取消

所有的BpXXX都会继承至BpInterface, 因此里面的new动作会导致基类构造函数被执行:

构造函数里面调用了BpRefBase(remote), 注意这里的remote就是前面readStrongBinder返回的结果.

BpRefBase实现如下:

它的入参是o, 那就是上面的remote. 该函数的主要目的是初始化mRemote. 这里调用了一下mRemote->incStrong增加了一下BpBinder的引用计数.

大家可能会有疑问, 前面readStrongBinder的时候new BpBinder, 然后通过sp<IBinder>的形式返回, 此时已经让BpBinder的引用计数加1了. 为什么又要做一次mRemote->incStrong?

因为当interface_cast执行完毕时, sp<IBinder>这个返回值的生命周期就结束了, 结束时会有一个减1动作, 因此mRemote要自己做incStrong.

对于MPS来说, 通过onTransact->create流程, 会把上面创建的这个客户端存储在MediaPlayerService:: Client:: mClient这个变量中:

当执行disconnect操作时, 会有这样一句话:

这句话像多米洛骨牌一样, 会触发这样一整串流程:

mClient.clear()  ->  BpRefBase::onLastStrongRef  ->  “mRemote->decStrong”  ->  BpBinder::onLastStrongRef  ->  ipc->decStrongHandle  ->  binder通信  -> 服务进程中服务引用计数减1.

9.   Binder示例代码

假设你想自己动手写一个binder通信的程序, 可以参考Android官方代码:

或者参考他人自己做的示例:

 Binder系列8—如何使用Binder , 这里有Native层和JAVA层的示例.

另外, 如果想在JAVA里面简单实现一个服务, 不一定要定义AIDL. 示例代码如下:

//服务端代码

    private final IBinder mToken = new Binder() {

        private static final int CMD_GET_TOKEN_NAME = 0;

        @Override

        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)

                  throws RemoteException

        {

            switch (code) {

                case CMD_GET_TOKEN_NAME:

                    Log.e(TAG, “CMD_GET_TOKEN_NAME “ + this.toString());

                    reply.writeString(this.toString());

                    return true;

                default:

                    return super.onTransact(code, data, reply, flags);

            }

        }

    };

//客户端代码

    private String getTokenName(IBinder cb) {

        if (cb == null)

            return “ERROR cb == null”;

        Parcel data = Parcel.obtain();

        Parcel reply = Parcel.obtain();

        String res;

        try {

            cb.transact(0, data, reply, 0);

            res = reply.readString();

        } catch (RemoteException e) {

            res=  “RemoteException”;

        } finally {

            data.recycle();

            reply.recycle();

        }

        return res;

    }

10.         Reflink

原理篇:

[November 1, 2015] Binder系列1—Binder Driver初探

  • binder驱动的基本结构: open/mmap/ioctl
  • 主要的数据结构
    • binder_proc
    • binder_node

[November 2, 2015] Binder系列2—Binder Driver再探

  • binder_thread_write过程详解
  • binder_thread_read过程详解
  • Binder内存机制

[November 7, 2015] Binder系列3—启动ServiceManager

init.rc如何启动ServiceManager进程, 如何与驱动交互

[November 8, 2015] Binder系列4—获取ServiceManager

如果想与ServiceManager进程通信, 如何获取它在客户端的代理

[November 14, 2015] Binder系列5—注册服务(addService)

注册服务

[November 15, 2015] Binder系列6—获取服务(getService)

获取服务

[November 21, 2015] Binder系列7—framework层分析

Java层的binder机制

[November 22, 2015] Binder系列8—如何使用Binder

Native层和Java层的Binder示例

[November 23, 2015] Binder系列9—如何使用AIDL

AIDL的使用方法

[November 28, 2015] Binder系列10—总结

[October 2, 2016] binderDied()过程分析

Binder die机制

[October 3, 2016] Binder死亡通知机制之linkToDeath

如何接收die消息

调试篇:

[August 27, 2016] Binder子系统之调试分析(一)

Binder驱动层有很多开关,  它们的作用以及如何使用它们

[August 28, 2016] Binder子系统之调试分析(二)

同上

[September 3, 2016] Binder子系统之调试分析(三)

同上

[May 1, 2017] Binder异常解析

如何根据logcat中的异常报错, 分析binder通信出错的原因

发布了748 篇原创文章 · 获赞 458 · 访问量 243万+

猜你喜欢

转载自blog.csdn.net/u010164190/article/details/104988407