文档章节

Android Binder IPC分析

ifindbug
 ifindbug
发布于 2014/09/28 21:51
字数 3350
阅读 65
收藏 0

1. binder 通信概述

binder 通信是一种client-server 的通信结构,

1. 从表面上来看,是client 通过获得一个server 的代理接口,对server 进行直接调用;
2. 实际上,代理接口中定义的方法与server 中定义的方法是一一对应的;
3. client 调用某个代理接口中的方法时,代理接口的方法会将client 传递的参数打包成为Parcel 对象;
4. 代理接口将该Parcel 发送给内核中的binder driver.
5. server 会读取binder driver 中的请求数据,如果是发送给自己的,解包Parcel 对象,处理并将结果返回;
6. 整个的调用过程是一个同步过程,在server 处理的时候,client 会block 住。

C-S通信方式

2. service manager

Service Manager 是一个linux 级的进程, 顾名思义,就是service 的管理器。这里的service 是什么概念呢?这里的service 的概念和init 过程中init.rc 中的service 是不同,init.rc 中的service 是都是linux 进程,但是这里的service 它并不一定是一个进程,也就是说可能一个或多个service 属于同一个linux 进程。在这篇文章中不加特殊说明均指android native 端的service 。 任何service 在被使用之前,均要向SM(Service Manager) 注册,同时客户端需要访问某个service 时,应该首先向SM 查询是否存在该服务。如果SM 存在这个service ,那么会将该service 的handle 返回给client ,handle 是每个service 的唯一标识符。

SM 的入口函数在service_manager.c 中,下面是SM 的代码部分

<!-- lang: cpp -->
int main(int argc, char **argv)
{
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;
 
    bs = binder_open(128*1024);
 
    if (binder_become_context_manager(bs)) {
        LOGE("cannot become context manager (%s)/n", strerror(errno));
        return -1;
    }
 
    svcmgr_handle = svcmgr;
    binder_loop(bs, svcmgr_handler);
    return 0;
}

这个进程的主要工作如下:

1. 初始化binder ,打开/dev/binder 设备;在内存中为binder 映射128K 字节空间;
2. 指定SM 对应的代理binder 的handle 为0 ,当client 尝试与SM 通信时,需要创建一个handle 为0 的代理binder ,这里的代理binder 其实就是第一节中描述的那个代理接口;
3. 通知binder driver(BD) 使SM 成为BD 的context manager ;
4. 维护一个死循环,在这个死循环中,不停地去读内核中binder driver ,查看是否有可读的内容;即是否有对service 的操作要求, 如果有,则调用svcmgr_handler 回调来处理请求的操作。
5. SM 维护了一个svclist 列表来存储service 的信息。

SM工作方式

这里需要声明一下,当service 在向SM 注册时,该service 就是一个client ,而SM 则作为了server 。而某个进程需要与service 通信时,此时这个进程为client ,service 才作为server 。因此service 不一定为server ,有时它也是作为client 存在的。

由于下面几节会介绍一些与binder 通信相关的几个概念,所以将SM 的功能介绍放在了后面的部分来讲。

应用和service 之间的通信会涉及到2 次 binder 通信。

1. 应用向SM 查询service 是否存在,如果存在获得该service 的代理binder ,此为一次binder 通信;
2. 应用通过代理binder 调用service 的方法,此为第二次binder 通信。

3. ProcessState

ProcessState 是以单例模式设计的。每个进程在使用binder 机制通信时,均需要维护一个ProcessState 实例来描述当前进程在binder 通信时的binder 状态。

ProcessState 有如下2 个主要功能:

1. 创建一个thread, 该线程负责与内核中的binder 模块进行通信,称该线程为Pool thread ;
2. 为指定的handle 创建一个BpBinder 对象,并管理该进程中所有的BpBinder 对象。

3.1 Pool thread

在Binder IPC 中,所有进程均会启动一个thread 来负责与BD 来直接通信,也就是不停的读写BD ,这个线程的实现主体是一个IPCThreadState 对象,下面会介绍这个类型。

下面是Pool thread 的启动方式:

ProcessState::self()->startThreadPool();

3.2 BpBinder 获取

BpBinder 主要功能是负责client 向BD 发送调用请求的数据。它是client 端binder 通信的核心对象,通过调用transact 函数向BD 发送调用请求的数据,它的构造函数如下:

BpBinder(int32_t handle);

通过BpBinder 的构造函数发现,BpBinder 会将当前通信中server 的handle 记录下来,当有数据发送时,会通知BD 数据的发送目标。

ProcessState 通过如下方式来获取BpBinder 对象:

ProcessState::self()->getContextObject(handle);

在这个过程中,ProcessState 会维护一个BpBinder 的vector mHandleToObject ,每当ProcessState 创建一个BpBinder 的实例时,回去查询mHandleToObject ,如果对应的handle 已经有binder 指针,那么不再创建,否则创建binder 并插入到mHandleToObject 中。

ProcessState 创建的BpBinder 实例,一般情况下会作为参数构建一个client 端的代理接口,这个代理接口的形式为BpINTERFACE , 例如在与SM 通信时,client 会创建一个代理接口BpServiceManager .

4. IPCThreadState

IPCThreadState 也是以单例模式设计的。由于每个进程只维护了一个ProcessState 实例,同时ProcessState 只启动一个Pool thread ,也就是说每一个进程只会启动一个Pool thread ,因此每个进程则只需要一个IPCThreadState 即可。

Pool thread 的实际内容则为:

IPCThreadState::self()->joinThreadPool();

ProcessState 中有2 个Parcel 成员,mIn 和mOut ,Pool thread 会不停的查询BD 中是否有数据可读,如果有将其读出并保存到mIn ,同时不停的检查mOut 是否有数据需要向BD 发送,如果有,则将其内容写入到BD 中,总而言之,从BD 中读出的数据保存到mIn ,待写入到BD 中的数据保存在了mOut 中。

ProcessState 中生成的BpBinder 实例通过调用IPCThreadState 的transact 函数来向mOut 中写入数据,这样的话这个binder IPC 过程的client 端的调用请求的发送过程就明了了 。

IPCThreadState 有两个重要的函数,talkWithDriver 函数负责从BD 读写数据,executeCommand 函数负责解析并执行mIn 中的数据。

5. 主要基类

5.1 基类IInterface

为server 端提供接口,它的子类声明了service 能够实现的所有的方法;

5.2 基类IBinder

BBinder 与BpBinder 均为IBinder 的子类,因此可以看出IBinder 定义了binder IPC 的通信协议,BBinder 与BpBinder 在这个协议框架内进行的收和发操作,构建了基本的binder IPC 机制。

5.3 基类BpRefBase

client 端在查询SM 获得所需的的BpBinder 后,BpRefBase 负责管理当前获得的BpBinder 实例。

6. 两个接口类

6.1 BpINTERFACE

如果client 想要使用binder IPC 来通信,那么首先会从SM 出查询并获得server 端service 的BpBinder ,在client 端,这个对象被认为是server 端的远程代理。为了能够使client 能够想本地调用一样调用一个远程server ,server 端需要向client 提供一个接口,client 在在这个接口的基础上创建一个BpINTERFACE ,使用这个对象,client 的应用能够想本地调用一样直接调用server 端的方法。而不用去关心具体的binder IPC 实现。 下面看一下BpINTERFACE 的原型:

class BpINTERFACE : public BpInterface<IINTERFACE>

顺着继承关系再往上看

template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase

BpINTERFACE 分别继承自INTERFACE ,和BpRefBase ;

● BpINTERFACE 既实现了service 中各方法的本地操作,将每个方法的参数以Parcel 的形式发送给BD 。

例如BpServiceManager 的

<!-- lang: cpp -->
 virtual status_t addService(const String16& name, const sp<IBinder>& service)
{
    Parcel data, reply;
    data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
    data.writeString16(name);
    data.writeStrongBinder(service);
    status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
    return err == NO_ERROR ? reply.readExceptionCode() : err;
}

● 同时又将BpBinder 作为了自己的成员来管理,将BpBinder 存储在mRemote 中,BpServiceManager 通过调用BpRefBase 的remote() 来获得BpBinder 指针。

6.2 BnINTERFACE

在定义android native 端的service 时,每个service 均继承自BnINTERFACE(INTERFACE 为service name) 。BnINTERFACE 类型定义了一个onTransact 函数,这个函数负责解包收到的Parcel 并执行client 端的请求的方法。

顺着BnINTERFACE 的继承关系再往上看,

   class BnINTERFACE: public BnInterface<IINTERFACE>

IINTERFACE 为client 端的代理接口BpINTERFACE 和server 端的BnINTERFACE 的共同接口类,这个共同接口类的目的就是保证service 方法在C-S 两端的一致性。

再往上看

    class BnInterface : public INTERFACE, public BBinder

同时我们发现了BBinder 类型,这个类型又是干什么用的呢?既然每个service 均可视为一个binder ,那么真正的server 端的binder 的操作及状态的维护就是通过继承自BBinder 来实现的。可见BBinder 是service 作为binder 的本质所在。

那么BBinder 与BpBinder 的区别又是什么呢?

其实它们的区别很简单,BpBinder 是client 端创建的用于消息发送的代理,而BBinder 是server 端用于接收消息的通道。查看各自的代码就会发现,虽然两个类型均有transact 的方法,但是两者的作用不同,BpBinder 的transact 方法是向IPCThreadState 实例发送消息,通知其有消息要发送给BD ;而BBinder 则是当IPCThreadState 实例收到BD 消息时,通过BBinder 的transact 的方法将其传递给它的子类BnSERVICE 的onTransact 函数执行server 端的操作。

7. Parcel

Parcel 是binder IPC 中的最基本的通信单元,它存储C-S 间函数调用的参数. 但是Parcel 只能存储基本的数据类型,如果是复杂的数据类型的话,在存储时,需要将其拆分为基本的数据类型来存储。

简单的Parcel 读写不再介绍,下面着重介绍一下2 个函数

7.1 writeStrongBinder

当client 需要将一个binder 向server 发送时,可以调用此函数。例如

    <!-- lang: cpp -->
    virtual status_t addService(const String16& name, const sp<IBinder>& service)
    {
        Parcel data, reply;
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
        data.writeString16(name);
        data.writeStrongBinder(service);
        status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
        return err == NO_ERROR ? reply.readExceptionCode() : err;
    }

看一下writeStrongBinder 的实体

<!-- lang: cpp -->

status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
    return flatten_binder(ProcessState::self(), val, this);
}

接着往里看flatten_binder

<!-- lang: cpp -->

status_t flatten_binder(const sp<ProcessState>& proc,
    const sp<IBinder>& binder, Parcel* out)
{
    flat_binder_object obj;
     
    obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    if (binder != NULL) {
        IBinder *local = binder->localBinder();
        if (!local) {
            BpBinder *proxy = binder->remoteBinder();
            if (proxy == NULL) {
                LOGE("null proxy");
            }
            const int32_t handle = proxy ? proxy->handle() : 0;
            obj.type = BINDER_TYPE_HANDLE;
            obj.handle = handle;
            obj.cookie = NULL;
        } else {
            obj.type = BINDER_TYPE_BINDER;
            obj.binder = local->getWeakRefs();
            obj.cookie = local;
        }
    } else {
        obj.type = BINDER_TYPE_BINDER;
        obj.binder = NULL;
        obj.cookie = NULL;
    }
     
    return finish_flatten_binder(binder, obj, out);
}

还是拿addService 为例,它的参数为一个BnINTERFACE 类型指针,BnINTERFACE 又继承自BBinder ,

<!-- lang: cpp -->
   
 BBinder* BBinder::localBinder()
    {
        return this;
    }

所以写入到Parcel 的binder 类型为BINDER_TYPE_BINDER ,同时你在阅读SM 的代码时会发现如果SM 收到的service 的binder 类型不为BINDER_TYPE_HANDLE 时,SM 将不会将此service 添加到svclist ,但是很显然每个service 的添加都是成功的,addService 在开始传递的binder 类型为BINDER_TYPE_BINDER ,SM 收到的binder 类型为BINDER_TYPE_HANDLE ,那么这个过程当中究竟发生了什么?

为了搞明白这个问题,花费我很多的事件,最终发现了问题的所在,原来在BD 中做了如下操作

<!-- lang: cpp -->

(drivers/staging/android/Binder.c) :
 
static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)
{
..........................................
 
    if (fp->type == BINDER_TYPE_BINDER)
        fp->type = BINDER_TYPE_HANDLE;
    else
        fp->type = BINDER_TYPE_WEAK_HANDLE;
    fp->handle = ref->desc;
..........................................
}

阅读完addService 的代码,你会发现SM 只是保存了service binder 的handle 和service 的name ,那么当client 需要和某个service 通信了,如何获得service 的binder 呢?看下一个函数

7.2 readStrongBinder

当server 端收到client 的调用请求之后,如果需要返回一个binder 时,可以向BD 发送这个binder ,当IPCThreadState 实例收到这个返回的Parcel 时,client 可以通过这个函数将这个被server 返回的binder 读出。

<!-- lang: cpp -->

sp<IBinder> Parcel::readStrongBinder() const
{
    sp<IBinder> val;
    unflatten_binder(ProcessState::self(), *this, &val);
    return val;
}

往里查看unflatten_binder

<!-- lang: cpp -->

status_t unflatten_binder(const sp<ProcessState>& proc,
    const Parcel& in, sp<IBinder>* out)
{
    const flat_binder_object* flat = in.readObject(false);
     
    if (flat) {
        switch (flat->type) {
            case BINDER_TYPE_BINDER:
                *out = static_cast<IBinder*>(flat->cookie);
                return finish_unflatten_binder(NULL, *flat, in);
            case BINDER_TYPE_HANDLE:
                *out = proc->getStrongProxyForHandle(flat->handle);
                return finish_unflatten_binder(
                    static_cast<BpBinder*>(out->get()), *flat, in);
        }         
    }
    return BAD_TYPE;
}

发现如果server 返回的binder 类型为BINDER_TYPE_BINDER 的话,也就是返回一个binder 引用的话,直接获取这个binder ;如果server 返回的binder 类型为BINDER_TYPE_HANDLE 时,也就是server 返回的仅仅是binder 的handle ,那么需要重新创建一个BpBinder 返回给client 。

有上面的代码可以看出,SM 保存的service 的binder 仅仅是一个handle ,而client 则是通过向SM 获得这个handle ,从而重新构建代理binder 与server 通信。

这里顺带提一下一种特殊的情况,binder 通信的双方即可作为client ,也可以作为server. 也就是说此时的binder 通信是一个半双工的通信。那么在这种情况下,操作的过程会比单工的情况复杂,但是基本的原理是一样的,有兴趣可以分析一下MediaPlayer 和MediaPlayerService 的例子。

8. 经典桥段分析

<!-- lang: cpp -->

main_ mediaserver.cpp
int main(int argc, char** argv)
{
// 创建进程mediaserver 的ProcessState 实例
    sp<ProcessState> proc(ProcessState::self());
// 获得SM 的BpServiceManager
    sp<IServiceManager> sm = defaultServiceManager();
    LOGI("ServiceManager: %p", sm.get());
// 添加mediaserver 中支持的service 。
    AudioFlinger::instantiate();
    MediaPlayerService::instantiate();
    CameraService::instantiate();
    AudioPolicyService::instantiate();
// 启动ProcessState 的pool thread
    ProcessState::self()->startThreadPool();
// 这一步有重复之嫌,加不加无关紧要。
    IPCThreadState::self()->joinThreadPool();
}

9. Java 层的binder 机制

了解了native 通信机制后,再去分析JAVA 层的binder 机制,就会很好理解了。它只是对native 的binder 做了一个封装。这一部分基本上没有太复杂的过程,这里不再赘述了

摘自 杜文涛的专栏

本文转载自:http://www.2cto.com/kf/201202/118538.html

共有 人打赏支持
ifindbug
粉丝 0
博文 9
码字总数 0
作品 0
广州
Android 核心分析 之六 -----IPC框架分析 Binder,Service,Se...

我首先从宏观的角度观察Binder,Service,Service Manager,并阐述各自的概念。从Linux的概念空间中,Android的设计Activity托管在不同的的进程,Service也都是托管在不同的进程,不同进程间的...

LiSteven
2013/08/23
0
0
Android系统的Binder机制之一——Service Manager

Android虽然构建在Linux上面,但是在IPC(进程间)机制方面,没有利用Linux提供IPC机制,而是自己实现了一套轻量级的IPC机制——binder机制。并且Android Binder机制之上,Android框架提供了...

垂盆草
2012/08/04
0
0
Android 源码分析之旅1--系统架构与分析方法、工具

《Android 源码分析之旅》目录 apefwkall.png 《Android 源码分析之旅》的全部目录将作如下安排: 第1章节——本篇文章,将介绍一些基本的概念、方法、工具。 第2章节——着重介绍Binder IP...

猴亮屏
2017/10/19
0
0
Android应用开发以及设计思想深度剖析(3)

特别声明:本系列文章LiAnLab.org著作权所有,转载请注明出处。作者系LiAnLab.org资深Android技术顾问吴赫老师。 我们接下来从安全性,性能,功能,可移植性的角度分别分析Android系统为应用...

21cnbao
2012/09/14
0
0
Android IPC进程间通讯机制

一.Linux系统进程间通信有哪些方式? 1.socket; 2.name pipe命名管道; 3.message queue消息队列; 4.singal信号量; 5.share memory共享内存; 二.Java系统的通信方式是什么? 1.socket; ...

Simpleness
2012/06/28
0
0

没有更多内容

加载失败,请刷新页面

加载更多

《Netkiller Java 手札》· 二进制文件操作大全

本文节选自《Netkiller Java 手札》 Netkiller Java 手札 Mr. Neo Chan, 陈景峯(BG7NYT) 中国广东省深圳市望海路半岛城邦三期 518067 +86 13113668890 <netkiller@msn.com> $Id: book.xml 6......

netkiller-
23分钟前
1
0
Fiddler Debugger post请求

常用的两种: 第一种默认的 对应URL为www 的要用请求头为:Content-Type: application/x-www-form-urlencoded 请求参数为 :param1=1234¶m2=12345 注:有些接口是指定用这种的第二方式并不...

轻量级赤影
30分钟前
2
0
如何搭建母婴亲子类知识社区

近期社交领域融资动作频繁,海尔高管、海尔医疗有限公司总裁管礼庆创办的母婴知识分享社区平台Alwayslove于上月获得700万天使轮融资。 Alwayslove是一个母婴知识分享社区平台,采用UGC模式,...

ThinkSNS账号
32分钟前
1
0
Android 自定义构建类型 BuildType

最近接触到自定义构建类型 BuildType,发现这一块有些地方稍不注意的话会被绕进去浪费点时间,既然我这边已经花费时间了,如果正好你也需要接触到 BuildType,也许接下来分享的 tips 可能会帮...

猴亮屏
34分钟前
1
0
美团点评基于 Flink 的实时数仓建设实践

引言 近些年,企业对数据服务实时化服务的需求日益增多。本文整理了常见实时数据组件的性能特点和适用场景,介绍了美团如何通过 Flink 引擎构建实时数据仓库,从而提供高效、稳健的实时数据服...

美团技术团队
37分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部