zoukankan      html  css  js  c++  java
  • Android Binder机制 -- libbinder

    Binder简介

    Binder是android系统提供的一种IPC机制。Android为什么选择binder?而不使用linux系统已有的IPC机制。具体原因不清楚,但是,看一下Binder机制的优点,我们可能能了解几分。

    1. 高效

      linux 的大多数IPC方式都需要至少两次内存拷贝。而Binder只需要一次内存拷贝。尽管共享内存的方式不需要多余的内存拷贝,但是对于多进程而言,要构建复杂的同步机制,这也会抵消共享内存零拷贝带来性能优势。

    2. 稳定

    3. 安全

    libbinder

    在native层,通过libbinder封装的类,我们能够很轻松(真的轻松吗?)的使用Binder机制来获取系统或应用提供的服务。

    继承关系

    其继承关系比较复杂,如果我们要实现自己的服务或代理类,我们需要实现的就是图中红色的部分。等了解了每个类的功能和知道如何实现binder service 和 binder client 后,再回过头来看这个类图,就会比较清晰了。

    类介绍

    IBinder、BBinder 和 BpBinder

    IBinder本质上就是一个接口类。提供了,BBinderBpBinder需要用到的通用函数及class。

    BBinder对应的是Binder实体对象,简单的理解就是它表示 IPC通信过程中的 服务提供者。也就是C/S模式中的Server。

    BpBinder对应的时Binder代理对象,代理的就是BBinder,通过BpBinder我们就能和BBinder对象建立联系。

    IBinder中有一个比较重要的函数transact

    // code 就是我们定义的协议码
    // data 就是我们要发送的数据
    // reply 就是接收到的数据
    virtual status_t transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) = 0;
    

    对于BBinder,作为Server,他只是接受来自BpBinder的请求,处理后回复,所以,BBinder::transact的实现就是处理数据并返回处理结果。

    对于BpBinder,作为Client,他发送请求给BBinder并等待其处理结果。所以,BpBinder::transact的实现就是将数据发送给BBinder并等待其返回。

    1. BpBinder::transact
      image-20201125160753789

      BpBinder::mHandler, 这个handler对应这一个BBinder,通过它,我们就能将数据发送到BBinder所在的进程。并交由其transact函数处理。

      当我们要发送数据时,直接调用BpBinder::transact发送即可,其就会将数据交由我们代理的BBinder对象处理。数据传输工作由IPCThreadState::self()->transact完成,这里我们暂且不谈其实现细节。

    2. BBinder::transact

      image-20201125160316047
      image-20201125160355285

      BpBinder::transact执行后,数据到达BBinder所在的进程时,首先会执行BBinder::transact函数,该函数内部又调用了BBinder::onTransact函数来处理,该函数原型如下:

          virtual status_t    onTransact( uint32_t code,
                                          const Parcel& data,
                                          Parcel* reply,
                                          uint32_t flags = 0);
      

      是一个虚函数,这意味着,我们可以重写该函数来实现我们自己的协议和处理逻辑,这是我们自定义Binder service 的关键。

    Interface、BnInterface 和 BpInterface

    ​ 前面的BBinderBpBinder本质上是对代理对象和被代理对象直接数据传递方式的封装。BnInterfaceBpInterface则是对两者的数据协议的封装。

    image-20201125161723269
    image-20201125161856792

    1. DECLARE_META_INTERFACEIMPLEMENT_META_INTERFACE

      这两个宏的作用就是让我们少写了很多代码!!!其用法我们后面会提到。前者定义了一个成员变量和4个方法,后者就是实现了这4个方法。

    2. interface_cast

      这个函数的作用就是将IBinder对象转换为IInterface对象(实际类型需要运行时才知道)。

    3. BpRefBase

      前面的类图,可以看到BpRefBaseBpBinder之间是聚合关系。BpInterface继承了BpRefBase,而BpRefBase持有了BpBinder
      image-20201125163626770

      通过remote函数,我们就能拿到其持有的BpBinder,然后在BpInterface就能发送数据到BBinder对象了。

      为甚么是聚合关系,而不是BpInterface直接继承BpBinder??。我想,大概是,这样做,整个进程只会存在一个对象,便于Binder进行生命周期管理。

    要彻底理解这几个类和宏的意义,还是要结合实例来理解,接下来我们就编写一个demo展示一下libbinder的用法。

    使用方式

    假定一个这样的场景,我们的程序运行在普通用户下,但是有时需要执行一些需要root权限的命令。我们就能创建一个运行在root权限下的service,程序通过Binder接口,将要执行的命令发送给service,并等待service 命令输出结果。

    定义协议和接口

    首先,我们需要继承IInterface类,并在定义好我们接口函数,这些接口函数在服务端和客户端都会被用到。

    #include <binder/IInterface.h>
    #include <string>
    
    using namespace android;
    
    class IExecService : public IInterface {
    public:
        // 首先定义 协议码。协议码起始值必须大于等于  IBinder::FIRST_CALL_TRANSACTION
        enum {
            EXEC_COMMAND = IBinder::FIRST_CALL_TRANSACTION,
        };
    
        //定义元接口,
        DECLARE_META_INTERFACE(ExecService)
    
        // 定义我们的接口
        virtual std::string exec(const std::string &cmd) = 0;
    };
    

    首先DECLARE_META_INTERFACE,我们先看一下展开后的样子:

    static const android::String16 descriptor;                          
    static android::sp<IExecService> asInterface(                       
                const android::sp<android::IBinder>& obj);                  
    virtual const android::String16& getInterfaceDescriptor() const;    
    IExecService();                                                     
    virtual ~IExecService();                                            
    

    额,这里,比较重要的就是 asInterface方法。该方法主要是给Client端使用,将一个IBinder转换成IInterface对象。

    Service 端实现

    首先继承BnInterface

    // 定义 服务接口
    class BnExecService : public BnInterface<IExecService> {
    protected:
        status_t onTransact( uint32_t code,
                             const Parcel& data,
                             Parcel* reply,
                             uint32_t flags = 0) override;
    };
    

    BnExecService类实际上继承了 BBinderIExecServiceIInterface。此时BnExecService还是一个抽象类,因为我们还没实现IExecService中的函数。前面我们提到过,当数据到来时,会执行到IBinder::transact--> BBInder::onTransact,那先来看看onTransact的实现。

    status_t BnExecService::onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
        switch (code) {
            case EXEC_COMMAND: {
                CHECK_INTERFACE(IExecService, data, reply);	
                std::string cmd(data.readCString());
                auto res = exec(cmd);
                reply->writeCString(res.c_str());
                return NO_ERROR;
            }
            default:
                return BBinder::onTransact(code, data, reply, flags);
        }
    }
    

    这也算是固定的写法吧,如果code不是我们自定义的,就调用BBinder::onTransact来处理(处理Android预定义的Code)。

    此外,我们使用了CHECK_INTERFACE宏,那么在Client端发送数据时,首先就应该发送descriptor(DECLARE_META_INTERFACE宏帮我们的定义的)。

    下一步就是实现exec方法,这里我们在BnExecService的子类中实现,当然也可以直接在BnExecService中实现。

    class ExecService : public BnExecService {
    public:
    
        // 实现咯
        std::string exec(const std::string &cmd) override {
    
            FILE * filp = popen(cmd.c_str(), "r");
            if (!filp) {
                return "Failed to popen";
            }
    
            std::string result;
            char line[1024]{};
            while (fgets(line, sizeof(line), filp) != nullptr) {
                result.append(line);
            }
    
            if (pclose(filp) != 0) {
                return "Failed to pclose";
            }
            return  result;
        }
    };
    

    最后就是将ExecService注册到ServiceMananger,这样Client就能通过ServiceManager引用到它。

    ProcessState::self()->startThreadPool(); 
    // 注册到 Service Manager
    defaultServiceManager()->addService(String16("exec_service"), new ExecService());
    IPCThreadState::self()->joinThreadPool();
    

    ProcessStateIPCThreadState是binder通信的关键,其完成了实际的数据接发操作。后面会详细介绍的。

    Client端实现

    首先继承BpInterface,这样我们就能通过remote()方法获取到IBinder对象(实际类型是BpBinder)。

    // 定义代理接口
    class BpExecService : public BpInterface<IExecService> {
    public:
        BpExecService(const sp<IBinder> &binder);
    
        std::string exec(const std::string &cmd) override;
    };
    

    对于BnExecService::exec其实现是执行真正的逻辑。而BpExecService::exec其实现就是将code和IPC相关的数据打包发送给Service

    BpExecService::BpExecService(const sp<IBinder> &binder) : BpInterface<IExecService>(binder) {
    
    }
    
    std::string BpExecService::exec(const std::string &cmd) {
        Parcel data;
        Parcel reply;
        data.writeInterfaceToken(IExecService::getInterfaceDescriptor());
        data.writeCString(cmd.c_str());
        if (remote()->transact(EXEC_COMMAND, data, &reply, 0)) {
            return "";
        }
    
        return reply.readCString();
    }
    

    构造函数中的IBinder对象就是remote()方法返回的IBinder对象。这个对于对象从何而来???

    还记得DECLARE_META_INTERFACE吗?其定义了几个方法。我们还没实现的,现在就要通过IMPLEMENT_META_INTERFACE宏实现了。

    IMPLEMENT_META_INTERFACE(ExecService, "com.liutimo.IExecService");
    

    第二个参数就是descriptor。其会在Service Mananger中显示。
    image-20201126122027204

    该宏展开后如下:

    	const android::String16 IExecService::descriptor("com.liutimo.IExecService"); //初始化  descriptor           
    	const android::String16&                                            
                IExecService::getInterfaceDescriptor() const {              
            return IExecService::descriptor;                                
        }                            
        android::sp<IExecService> IExecService::asInterface(                
                const android::sp<android::IBinder>& obj)                   
        {                                                                   
            android::sp<IExecService> intr;                                 
            if (obj != NULL) {                                              
                intr = static_cast<IExecService*>(                          
                    obj->queryLocalInterface(                               
                            IExecService::descriptor).get());               
                if (intr == NULL) {                                         
                    intr = new BpExecService(obj);                          
                }                                                           
            }                                                               
            return intr;                                                    
        }                                                                   
        IExecService::IExecService() { }                                    
        IExecService::~IExecService() { }                                   
    
    

    这里比较重要的函数就是asInterface

    先来看client 如何引用Service

    ProcessState::self()->startThreadPool();
    auto binder = defaultServiceManager()->getService(String16("exec_service"));
    auto es = interface_cast<IExecService>(binder);
    std::cout << es->exec(argv[1]) << std::endl;
    

    interface_cast原型如下:

    template<typename INTERFACE>
    inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
    {
        return INTERFACE::asInterface(obj);
    }
    

    最终还是调用的IExecService::asInterface(obj)。回到asInterface的定义。

        android::sp<IExecService> IExecService::asInterface(                
                const android::sp<android::IBinder>& obj)                   
        {                                                                   
            android::sp<IExecService> intr;                                 
            if (obj != NULL) {                                              
                intr = static_cast<IExecService*>(                          
                    obj->queryLocalInterface(                               
                            IExecService::descriptor).get());               
                if (intr == NULL) {                                         
                    intr = new BpExecService(obj);                          
                }                                                           
            }                                                               
            return intr;                                                    
        } 
    

    obj就是我们从service mananger拿到的IBinder对象(通过obj->transact方法就能传输数据到service端)。其实际类型分两种情况:

    1. Client 和 Service 位于同一个进程空间。obj实际类型就是BBinder
    2. Client 和 Service 位于不同的进程空间,obj实际类型就是BpBinder

    queryLocalInterface的左右就是clientService是不是在同一个进程空间。是的话,asInterface返回的实际上就是ExecService对象,否则,就创建一个BpExecService对象返回(我们通过BpExecService::exec就能进行IPC调用了)。

    libbinder数据是如何传输的

    ProcessState::self()->startThreadPool(); 
    // 注册到 Service Manager
    defaultServiceManager()->addService(String16("exec_service"), new ExecService());
    IPCThreadState::self()->joinThreadPool();
    

    这是我们注册Service 到 ServiceManager的代码。

    ProcessState::self()->startThreadPool(); 这句代码其实可以不需要。写在这里,只是为了突出有ProcessState这么一个东西。

    IPCThreadState::self()->joinThreadPool();这句代码是必须的,没有它,我们的Service虽然能注册到ServiceMananger,但是却不能对外提供服务(它收不到client的请求)。

    本节主要就是介绍IPCThreadStatusProcessState是如何接收和发送数据。

    Binder机制的核心就是Binder驱动,用户层的所有数据都经由Binder驱动进行转发,我们使用Binder机制的第一步就是需要打开Binder驱动。

    在哪里打开binder驱动

    嗯,在ProcessState的构造函数里面。ProcessState是一个单例模式,一个Android进程只会有一个实例。所以就保证了,每个进程只会打开一次Binder驱动。
    image-20201127163800364

    首先调用open_driver打开binder驱动,对于Framework应用而言,就是简单的open binder驱动节点(/dev/binder)。

    image-20201127163934213

    暂时忽略BINDER_VERSIONBINDER_SET_MAX_THREADS的这些细节。

    然后调用mmap完成 进程和binder内核空间的映射。这里是binder一次内存拷贝的关键(具体原理后续分析binder驱动的时候再介绍)。

    值得注意的是BINDER_VM_SIZE,其定义如下。

    // 1Mb - 2*PAGE_SIZE
    #define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
    

    我们通过mmap映射了1016Kb(1024-8)的地址空间到进程,也就意味着,我们通过Binder一次传输的最大数据量就是1016Kb。但是这样说也是不够准确的。(具体一次能传输多少,后续分析binder驱动的时候再介绍)。

    如何发送数据

    前面有提到,我们可以通过BpBinder::transact来发送数据。其实现比较简单的,但是要理解的话,先的了解一下用于Binder通信的协议和数据结构。

    应用程序和Binder驱动通过ioctl进行通信,应用发送数据和接收数据对应的ioctl命令字是BINDER_WRITE_READ。一次ioctl调用,会将需要发送的数据传递到内核,并将接收到的数据存入到接收缓冲区。与该命令对应的数据结构是struct binder_write_read bwr,定义如下:

    struct binder_write_read {
    	binder_size_t		write_size;		/* 写缓冲区大小*/
    	binder_size_t		write_consumed;	/* 内核实际处理了的size */
    	binder_uintptr_t	write_buffer;	/* 写缓冲区 */
    	binder_size_t		read_size;	    /* 读缓冲区大小*/
    	binder_size_t		read_consumed;	/* 接收到的数据大小 */
    	binder_uintptr_t	read_buffer;	/* 读冲区 */
    };
    

    假设我们要发送数据给Binder驱动并等待回应,可以写出如下伪代码:

    int fd = open_binder_driver();
    ...
    char send_buffer[1024];
    memcpy(send_buffer, send_data, send_data_size);
    char recv_buffer[1024];
    
    struct  binder_write_read bwr{};
    bwr.write_szie = send_data_size;
    bwr.write_buffer = send_buffer;
    
    bwr.read_size = 1024;
    bwr.read_buffer = recv_buffer;
    
    ioctl(fd, BINDER_WRITE_READ, &bwr);
    
    // bwr.write_consumed 就是实际发送了的数据大小,通常就是  send_data_size
    // bwr.read_consumed 就是我们实际接收到的数据size
    

    这其实就是Binder数据发送和接收的核心内容了,binder驱动目前对我们来说是一个盲盒,通过上面的代码,就能完成数据发送和接收。

    接下来要分析的其实就是通信协议了。

    Binder通信协议分析

    涉及到通信,就一定会涉及到Command,不然我怎么知道你发给我的是啥。Binder也不例外。不过,binder的这一套协议是内核和用户空间共同来实现的。linux传统的IPC机制,内核只需要转发数据,它不关心数据内容,但是Binder不一样,它需要传输的数据进行处理,这也是一开始说的binder机制稳定和安全的一个原因吧。

    1. 通信命令字。

      分为两种,一种是BC_*,另一种就是BR_*

      image-20201128222447542

      前面的栗子!通过BpExecService获取ExecService提供的服务,其协议交互过程如下:

      image-20201129000042940

      整个通讯过程,内核不仅仅是转发,而且还改变了一些数据(最直观的看就是命令字)。主动发送数据,BpExecService使用命令字BR_TRANSACTION,内核处理了数据后,会将数据转发到目的地ExecService,这时候命令字会被内核更改为BR_TRANSACTION;同理,ExecService执行完命令后,使用BC_REPLY将执行结果返回给BpExecService,这是,内核有需要内核转发,并将命令字更改为BR_REPLY

      这里只介绍了数据传输相关的命令字,其余的感兴趣自己去看一下。

    2. 数据传输协议

      Binder不仅支持传输基本数据类型,还支持传输文件描述符和binder实体对象(注册服务到ServiceManager,或者注册回调函数到service)。

      如何打包这些数据并能区分数据类型呢???答案就是struct binder_transaction_data

      struct binder_transaction_data {
      	union {
      		__u32	handle;
      		binder_uintptr_t ptr;
      	} target;
      	binder_uintptr_t	cookie;
      	__u32		code;		
      
      	__u32	        flags;
      	pid_t		sender_pid;
      	uid_t		sender_euid;
      	binder_size_t	data_size;
      	binder_size_t	offsets_size;	
      	union {
      		struct {
      			binder_uintptr_t	buffer;
      			binder_uintptr_t	offsets;
      		} ptr;
      		__u8	buf[8];
      	} data;
      };
      
      • target.handle: 前面说过的BpBinder::mHandle,就是这个东西,类似于文件描述符吧!其是对BBinder的一个引用,通过它,内核就能准确的将数据转发到对的BBinder端。

      • target.ptrcookie: 这两个记录的是BBinder在用户空间进程的地址。

      • code: 就是我们前面提到的命令字。Android预留命令字如下:

        image-20201129144231459

      • data_sizedata.ptr.buffer: 表示的是要发送的数据及其大小

      • offest_sizeoffsets: offsets是一个数组,每个元素记录着data.ptr.buffer中object的位置信息,offset_size对应这个数组的大小。

      现在不太理解没关系,后面会介绍libbinder是如何使用这个结构体的。

    3. BpBinder::transact发送数据时具体做了哪些操作?

      首先前面提到了,通过ioctl(fd, BINDER_WRITE_READ, &bwr),我们可以将完成接收和发送的过程。数据格式如下:

      image-20201129145944293

      根据COMMAND的不同,DATA对应的结构也是不一样的。以CMD == BC_TRANSACTION为例。DATA == struct binder_transaction_data

      libbinder使用Parcel作为接收和发送缓冲区。前面提到,libbinder支持传输文件描述符和binder对象,这里传输这些对象的关键点就是Parcel。以传输binder对象为例:

      Parcel有一个方法名为status_t Parcel::writeStrongBinder(const sp<IBinder>& val),它是怎么完成Binder对象的序列化的呢?

      libbinder使用flat_binder_object来表示序列化后IBinder对象,其定义如下:

      image-20201129151155807

      • type:通过type我们就能知道当前Binder对象类型。BINDER_TYPE_HANDLE表示BpBinderBINDER_TYPE_BINDER表示的是BBinder
      • flags:忽略吧。。。
      • handle: 当type == BINTER_TYPE_HANDLE时有效。对应BpBinder::mHandle
      • bindercookies: 当 type == BINDER_TYPE_BINDER时有效。对应的是BBinder

      接下来,我们看一下Parcel是如何序列化Binder对象的,来进一步理解上面的结构体字段的含义。

      image-20201129150904825

      可以看到Parcel最终调用了flatten_binder函数来完成IBinder的序列化工作。

      image-20201129152149806

      可能现在对于BpBinder::mHandle还不太理解哦,其是进程独立的,由内核分配的一个ID,通过这个ID,内核就能准确的找到ID所引用的BBinder对象。

      继续,对于BBinder,序列化时,直接序列化其对象地址和引用计数地址。这个比较好理解吧,后面要用到时,只要强制类型转换一次即可拿到其原来的对象。

      到这里,还只是完成了IBinder对象的flat_binder_object的转换,还没有写入到Parcel中,再看看看finish_flatten_binder的实现。

      image-20201129153513946

      现在IBinder的序列化过程比较清楚了吧。回到主题,BpBinder如何发送数据

      BpBinder传输数据的函数调用流程大致如下

      • BpBinder::transact
        • IPCThreadState::transact
          • IPCThreadState::writeTransactionData
          • IPCThreadState::talkWithDriver
            • ioctl(fd, BINDER_WRITE_READ, &bwr)

      IPCThreadState::writeTransactionData

      image-20201129153903718

      image-20201129153845389

      结合前面对struct binder_transaction_data的介绍以及IBinder对象的序列化,这段代码应该很容易懂得。

      最后就是talkWithDriver的实现了。。。

      image-20201129154341478

      至此,数据已经发送完成了,一次talkWithDriver的执行,不一定就能收到回复,那BBider::onTransact什么时候会被回调,如何被回调。

    4. BBinder::onTransact是如何被回调的。

    尾声

    Binder的大部分工作都是内核完成的,后续会对binder驱动进行分析,到时候我们就能彻底了解:

    1. binder是如何将数据从进程A传递到进程B的?
    2. 为什么binder是只需要一次内存拷贝?
    3. binder一次能传输的最大数据是多少?
    4. binder是如何进行安全校验的?
    5. binder是如何实现死亡通知的?

    不会讲解太多代码的实现细节,会通过关键数据结构和流程图来讲解其实现原理。敬请期待吧。。。。

  • 相关阅读:
    jquery 实现 返回顶部
    js 10秒倒计时 功能
    2019.6.10 工作日志
    2019.4.25 工作日志
    2019.4.22 工作日志
    2019.4.13 工作日志
    2019.3.12 工作日志
    2019.1.22 工作日志
    2019.1.18 工作日志
    2019.1.14 工作日志
  • 原文地址:https://www.cnblogs.com/liutimo/p/14036573.html
Copyright © 2011-2022 走看看