"binder 通信机制详解 1. IPC与Binder 基本概念 在 linux 操作系统下,各进程间是完全隔离的,独立的,一个进程不能直接访问其他进程的资源,比如进程A有一个全局变量foobar, 进程B是没法直接读取的,进程A有一个函数,进程B也是没法直接调用的。但我们又有这样的需求,比如打印机 ...."

Binder 通信机制详解

binder 通信机制详解

1. IPC与Binder 基本概念

在 linux 操作系统下,各进程间是完全隔离的,独立的,一个进程不能直接访问其他进程的资源,比如进程A有一个全局变量foobar, 进程B是没法直接读取的,进程A有一个函数,进程B也是没法直接调用的。但我们又有这样的需求,比如打印机,我们希望由一个专门的进程(printService)来管理打印机的功能,其他进程有打印的需求,只需要把打印的请求发给 printService 来完成即可,由printService来统一管理所有的打印任务。那其他进程给printService发送打印请求的过程就是一个进程间的通信了。

但我们在前面说过了各进程间是完全隔离的,不能直接访问其他进程的资源,那要怎么实现进程间的通信呢?答案是内核。进程A和进程B相互不认识,但它们都认识kernel,如果A和B想要通信,那就只要通过kernel来进行中转就行了。linux 下进程间的通信方式有很多种,所有的IPC通信方式最终都是通过内核转发来实现的。

一般来说linux下主要有以下几种进程间通信方式:

  • 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
  • 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);
  • 报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  • 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
  • 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
  • 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

对于 Android系统来说还有另外一种IPC方式:Binder通信。该技术最早并不是由Google公司提出的,它的前身是Be Inc公司开发的OpenBinder,而且在Palm中也有应用。后来OpenBinder的作者Dianne Hackborn加入了Google公司,并负责Android平台的开发工作,所以把这项技术也带进了Android。

一开始有些疑惑,linux 已经提供了这么多的IPC机制了,为什么还要另起炉灶再搞个Binder呢?查阅了各种资料才了解到主要是出于性能,稳定性,安全各方面的考虑。

  • 从性能的角度 数据拷贝次数:Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,但共享内存方式一次内存拷贝都不需要;从性能角度看,Binder性能仅次于共享内存。
  • 从稳定性的角度Binder是基于C/S架构的,简单解释下C/S架构,是指客户端(Client)和服务端(Server)组成的架构,Client端有什么需求,直接发送给Server端去完成,架构清晰明朗,Server端与Client端相对独立,稳定性较好;而共享内存实现方式复杂,没有客户与服务端之别, 需要充分考虑到访问临界资源的并发同步问题,否则可能会出现死锁等问题;从这稳定性角度看,Binder架构优越于共享内存。仅仅从以上两点,各有优劣,还不足以支撑google去采用binder的IPC机制,那么更重要的原因是安全。
  • 从安全的角度传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Android作为一个开放的开源体系,拥有非常多的开发平台,App来源甚广,因此手机的安全显得额外重要;对于普通用户,绝不希望从App商店下载偷窥隐射数据、后台造成手机耗电等等问题,传统Linux IPC无任何保护措施,完全由上层协议来确保。 Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志,前面提到C/S架构,Android系统中对外只暴露Client端,Client端将任务发送给Server端,Server端会根据权限控制策略,判断UID/PID是否满足访问权限,目前权限控制很多时候是通过弹出权限询问对话框,让用户选择是否运行。

基于以上原因,Android需要建立一套新的IPC机制来满足系统对通信方式,传输性能,稳定性和安全性的要求,这就是Binder。Binder基于Client-Server通信模式,传输过程只需一次拷贝,为发送发添加UID/PID身份,既支持实名Binder也支持匿名Binder,安全性高。

2. binder通信架构

Binder通信采用C/S架构,从组件视角来说,包含Client、Server、ServiceManager以及Binder驱动这四个部分,架构图如下所示:
509b0bba6eff453dbe194809a8824995_IPCBinder.jpg

  • ServiceManager:用于管理系统中的各种服务,提供addService, getService 接口。
  • Server :系统服务,通过调用 ServiceManager.addService 来注册服务。
  • Client:应用程序,通过调用ServiceManager.getService 来获取已注册的系统服务。
  • Binder 驱动:进程间的数据传输通道

了解 web 工作原理的同学可作以下的类比:

ServiceManager --> DNS

Server --> Server

Client --> Client

Binder驱动 --> 路由器

ServiceManager 根据Service名称来查询Service, DNS 根据域名来查询IP,

Binder驱动作为进程间传输数据的通道, 路由器作为主机间传输数据的通道。

整个模型的工作流程如下:

  1. 内核启动,加载binder驱动
  2. android系统启动,启动ServiceManager服务,init.rc 中启动
  3. 其他系统服务启动,将自己注册到ServiceManager, addService接口
  4. 应用通过ServiceManager获取系统服务, getService接口
  5. 应用调用系统服务

3. 一个简单的binder通信C/S模型

前面说了很多概念,对于没有写过系统服务的同学可能会有点模糊,接下来我们就来实现一个最简单的C/S通信模型,以便有一个直观的认识。后面几个小节的分析也会基于这个例子来进行。

我看过很多讲解 binder 的文章,举的例子要么是从java层的 aidl,要么是 c++ 层的 BpInterface, BnInterface 类, 还有 DECLARE_META_INTERFACE 和 IMPLEMENT_META_INTERFACE 宏。在我看来是 google 对 binder 的一个过度封装了,本来其实很简单的概念,非要加上一层层的包装,最后连它妈都不认识了。若使用上面提到的接口去实现服务的话,代码没少写多少,反而增加了理解的难度,不容易维护。为了能认大家看清楚 binder 的真面目,我们不用google 封装的那些概念,来实现一个最基本的 service。

后面的代码都是在 c++ 层的,其实在 java 层也是一样的, 基本都会有一个对应的概念,用 jni 去调用 c++ 的代码而已。所以 java 层的代码我们就不分析了。

假设我们要实现的是一个计算器服务 CalculatorService,这个服务只提供一个 addInteger 调用。

3.1 CalculatorService 实现

先在 device/konka/common 目录下新建一个目录 CalculatorService, 我们的代码就放这个目录下。

3.1.1 CalculatorService 类声明

CalculatorService/CalculatorService.h:

#ifndef OREO_MSTAR_MASTER_CALCULATORSERVICE_H
#define OREO_MSTAR_MASTER_CALCULATORSERVICE_H

#include <utils/RefBase.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h>
using namespace android;
//定义 service 提供的功能代号
static const int ADD_INTERGER = 0;
//继承BBinder类,从而提供IBinder 接口
class CalculatorService : public BBinder {
public:
    CalculatorService();
    virtual ~CalculatorService();
    static int instantiate(); //单例
    virtual status_t onTransact(uint32_t, const Parcel &, Parcel *, uint32_t);
};

#endif //OREO_MSTAR_MASTER_CALCULATORSERVICE_H

直接继承 BBinder ,不要各种封装。

3.1.2 CalculatorService 实现

CalculatorService/CalculatorService.cpp:

#include <binder/IServiceManager.h>
#include "log.h"
#include "CalculatorService.h"

using namespace android;

int CalculatorService::instantiate() {
    LOGI("CalculatorService instantiate");
    int r = defaultServiceManager()->addService(String16("CalculatorService"),
                                                new CalculatorService());
    LOGI("addService CalculatorService result = %d/n", r);
    return r;
}

CalculatorService::CalculatorService() {
    LOGI("CalculatorService created");
}

CalculatorService::~CalculatorService() {
    LOGI("CalculatorService destroyed");
}

static int addInteger(const Parcel &data, Parcel *reply) {
    //读取 client 传过来的数据
    int number1 = data.readInt32();
    int number2 = data.readInt32();

    //数据处理
    int sum = number1 + number2;

    //把结果传到 client
    reply->writeInt32(sum);

    return NO_ERROR;
}

status_t
CalculatorService::onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
    LOGI("CalculatorService::onTransact code = %d", code);
    switch (code) {
        case ADD_INTERGER:
            return addInteger(data, reply);
        default:
            return BBinder::onTransact(code, data, reply, flags);
    }
    return NO_ERROR;
}

在 instantiate 实例化的时候顺便把自己添加到 ServiceManager。实现 onTransact 函数, 根据 code 类型来调用不同的功能。对于一个service 来说,最本质的其实就是onTransact 函数了。

我们把 log 的功能提供到一个单独的文件log.h中去,不同的源文件可以共用。
CalculatorService/log.h:

#ifndef OREO_MSTAR_MASTER_LOG_H
#define OREO_MSTAR_MASTER_LOG_H

#ifndef LOG_TAG
#define LOG_TAG "TAG"
#endif

#include <android/log.h>
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN , LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , LOG_TAG, __VA_ARGS__)

#endif //OREO_MSTAR_MASTER_LOG_H

3.1.3 实现 server 进程

前面已经实现了 service 相关的逻辑了,但一个服务要跑起来还是要依赖具体的进程的,
server 的目的在于: 创建一个 CalculatorService 实例,然后开启 binder_loop 等待 client 的调用。
CalculatorService/main.cpp

#include <sys/types.h>
#include <unistd.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include "CalculatorService.h"
using namespace android;
int main(int argc, char *argv[]) {
    sp < ProcessState > proc(ProcessState::self());
    CalculatorService::instantiate();
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
    return 0;
}

3.1.4 Android.mk 模块定义

CalculatorService/Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= libCalculatorService
LOCAL_SRC_FILES:= CalculatorService.cpp
LOCAL_SHARED_LIBRARIES:= liblog libutils libbinder
LOCAL_C_INCLUDES := $(TOP)/frameworks/base/include
LOCAL_PRELINK_MODULE:= false
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= CalculatorService
LOCAL_SRC_FILES:= main.cpp
LOCAL_SHARED_LIBRARIES:= liblog libutils libCalculatorService libbinder
include $(BUILD_EXECUTABLE)

3.1.5 将模块添加到系统

修改device.mk,添加一行

PRODUCT_PACKAGES += CalculatorService

3.1.6 开机启动服务

修改init.rc 文件,增加以下配置

service CalculatorService /system/bin/CalculatorService
    class main
    user root
    group root inet net_bt net_bt_admin net_bw_acct
    ioprio rt 4

经过以下这6个步骤,我们的service 已经添加到系统,并可以在开机的时候就启动了。编译,升级一下系统,执行 service list 命令查看一下我们的服务

# service list

3.2 client 实现

我们的服务已经在系统上跑起来了,接下来我们写一个测试程序,来调用这个服务的接口,看看是否正常。这个测试程序就是 binder 通信模型中的 client。client 的实现是比较简单的,我们直接放在 CalculatorService 目录下了。
CalculatorService/CalculatorTest.cpp

#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include "log.h"

//service 提供的功能
static const int ADD_INTERGER = 0;

using namespace android;

int main(int argc, char *argv[]) {
    LOGI("calculator test main is call...");
    sp <IBinder > calculatorBinder;
    Parcel data, reply;
    //获取serviceManager
    sp < IServiceManager > sm = defaultServiceManager();
    while (1) {
        //获取系统服务
        calculatorBinder = sm->getService(String16("CalculatorService"));
        LOGI("calculatorBinder = %p\n", sm.get());
        if (calculatorBinder == 0) {
            LOGE("calculatorBinder not published, waiting...");
            usleep(1000000);
            continue;
        } else {
            LOGI("get calculatorBinder success...");
            break;
        }
    }

    //要传给 service 的数据
    data.writeInt32(2);
    data.writeInt32(3);

    //调用 service 干活
    calculatorBinder->transact(ADD_INTERGER, data, &reply);

    //获取 service 返回的结果
    int sum = reply.readInt32();
    LOGI("sum of 2 + 3 = %d", sum);
    printf("sum of 2 + 3 = %d", sum)

    return 0;
}

修改CalculatorService/Android.mk,加上 CalculatorTest 模块的定义

include $(CLEAR_VARS)
LOCAL_MODULE:= CalculatorTest
LOCAL_SRC_FILES:= CalculatorTest.cpp
LOCAL_SHARED_LIBRARIES:= liblog libutils libbinder
include $(BUILD_EXECUTABLE)

修改 device.mk, 将 CalculatorTest 编译进系统

PRODUCT_PACKAGES += CalculatorTest

mm 编译出 CalculatorTest,copy 到系统里面,执行

./CalculatorTest

看输出,返回了正确的结果,也就是成功的调用了 CalculatorService 里面的 addInteger 方法。

以上就是一个最基本的 native service 的添加及调用方法了。这里我们关注的是怎么用,代码的具体细节我们在下一节再慢慢分析。

4. binder通信机制的实现原理

4.1 ServiceManager

4.1.1 ServiceManager的启动

4.1.2 addService分析

4.1.3 getService分析

4.2 service

4.2.1 ProcessState分析

4.3 client

4.3.1 IBinder 分析

0     0     0     0     0    
0 回帖