天天看点

Android-binder通信详解

1. binder是什么?

binder是安卓系统的进程间通信方式。

2. 为什么安卓要用binder?

Android内核是基于Linux系统,linux本身就有很多种进程间通信方式: 内存共享,消息队列、信号量等,为什么安卓还要用binder呢?

传统ipc(进程间通信):

共享内存,不需要内存拷贝,但是控制繁琐。

管道通信,需要两次内存拷贝。

Android-binder通信详解

binder通信:

Binder只需要一次拷贝是因为安卓的内存映射方法,也就是mmap。a进程发数据给b进程,a进程把数据拷贝给mmap开辟的内核空间,b进程通过mmap就可以取出来这个数据而不用重新拷贝。

Android-binder通信详解

3. binder的实现:

binder的实现是基于内存映射的,也就是上面说的mmap。

这里我们首先要理解用户空间和内核空间。内核空间可以访问所有的进程页表。也就是内核空间可以访问所有的用户空间。也是基于这个前提,进程间通信才可以实现。

要理解mmap必须先理解物理地址、虚拟地址和页表的概念。

物理地址:

物理地址空间是实在的存在于计算机中的一个实体,在每一台计算机中保持唯一独立性。我们可以称它为物理内存;如在32位的机器上,物理空间的大小理论上可以达到2^32字节(4GB)。

虚拟地址:

虚拟地址并不真实存在于计算机中。每个进程都有4G的虚拟地址空间,其中3G用户空间,1G内核空间(linux),每个进程共享内核空间,独立的用户空间。每个进程都有自己独立的虚拟地址空间。这样每个进程都能访问自己的地址空间,这样做到了有效的隔离。

Android-binder通信详解

 虚拟地址里面存放的什么内容?

Android-binder通信详解

 mmap的作用是映射文件描述符和指定文件的(off_t off)区域至调用进程的(addr,addr *len)的内存区域,如下图所示:

Android-binder通信详解

 binder的整个流程:

Android-binder通信详解

4. binder的通信流程

Android-binder通信详解

整个安卓的进程间通信都是基于c/s架构的,就是client / server 架构。

系统启动时servicemanager作为0号进程先启动,然后会读取rc文件,把系统中其他的进程依次启动。

server启动的时候就把自己注册到servicemanager里面,servicemanager里面会维护一个services的list。

client端想要和service通信就会先获取servicemanager服务,然后从servicemanager里面通过service name获取对应的service的binder,拿到了这个binder client端就可以通过这个binder和service通信了。

具体过程如下:

Android-binder通信详解

 4.1 server注册进程:

此处以mediaplayer为例:

int main(int argc __unused, char **argv __unused)
{
    signal(SIGPIPE, SIG_IGN);
  // 获取service manager的代理供IPC使用。
    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm(defaultServiceManager());
    ALOGI("ServiceManager: %p", sm.get());
    InitializeIcuOrDie();

    MediaPlayerService::instantiate(); // 注册service,这里是注册了一个binder
    ResourceManagerService::instantiate(); // 这里和上面一样

    // 这块是调用binder driver,底层实现是一个thread pool就是一个死循环
    ProcessState::self()->startThreadPool(); 
    IPCThreadState::self()->joinThreadPool();
}
           

所有的::instantiate()函数都是static型函数,且分别是创建各个子模块的对象实例。由此可看出mediaserver共分2个子模块: MediaPlayerService & ResourceManagerService。

void MediaPlayerService::instantiate() {
    defaultServiceManager()->addService( // 注册server,server name为media.player
            String16("media.player"), new MediaPlayerService());
}
           

 server注册到servermanager的时候会调用frameworks/native/cmds/servicemanager/service_manager.c中的do_add_service。这个函数中首先检查是否有权限注册service,没权限就会报错返回;然后检查是否已经注册过,注册过的service将不能再次注册。然后构造一个svcinfo对象,并加入一个全局链表中svclist中。最后通知binder设备:有一个service注册进来。

Tips: server一定是一个死循环,一直在等待client的调用,我们自己写的server一般是while死循环之类的。安卓server的死循环在binder driver里面,我们在server代码里面看不到,但是只要server调用了startThreadPool就会有一个死循环在底层跑着。

4.2 service启动流程

首先,每个service都要有对应的rc文件,里面说明了这个service启动需要加载的文件流程:

以mediaserver.rc为例:

service media /system/bin/mediaserver
    class main
    user media
    group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc mediadrm
    ioprio rt 4
    task_profiles ProcessCapacityHigh HighPerformance
    writepid /dev/memcg/camera/media/service/cgroup.procs
           

有了这个rc文件,系统在启动的时候就会调用system/bin/mediaserver这个可执行文件。这个文件里面的实现就是上面的main函数,然后就会把MediaPlayerService和ResourceManagerService注册进去。

4.3 client调用流程

下面以MediaCodecList为例:

// frameworks/av/media/libstagefright/MediaCodecList.cpp
sp<IMediaCodecList> MediaCodecList::getInstance() {
    Mutex::Autolock _l(sRemoteInitMutex);
    if (sRemoteList == nullptr) {
       // 此处就是通过servicemanager获取name为"media.player"的server的binder
        sp<IBinder> binder =
            defaultServiceManager()->getService(String16("media.player")); 
        // 拿到了binder就可以访问service的接口了
        sp<IMediaPlayerService> service =
            interface_cast<IMediaPlayerService>(binder);
        if (service.get() != nullptr) {
            sRemoteList = service->getCodecList();
            if (sRemoteList != nullptr) {
                sBinderDeathObserver = new BinderDeathObserver();
                binder->linkToDeath(sBinderDeathObserver.get());
            }
        }
        if (sRemoteList == nullptr) {
            // if failed to get remote list, create local list
            sRemoteList = getLocalInstance();
        }
    }
    return sRemoteList;
}
           

client端调用的就是IMediaPlayerService中的函数,这个IMediaPlayerService就是接口类,bn和bp都会继承这个类。client端调用的是bp的代码,然后bp通过binder调用到bn。

class BpMediaPlayerService: public BpInterface<IMediaPlayerService> {
    virtual sp<IMediaCodecList> getCodecList() const {
        Parcel data, reply;
        data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
        remote()->transact(GET_CODEC_LIST, data, &reply); 
        // remote这里就是通过binder调用从client端调用server端
        return interface_cast<IMediaCodecList>(reply.readStrongBinder());
    }
}
           
status_t BnMediaPlayerService::onTransact(...) {
     case GET_CODEC_LIST: {
          sp<IMediaCodecList> mcl = getCodecList();
          reply->writeStrongBinder(IInterface::asBinder(mcl));
          // 这里就会通过binder调用把server端的调用结果返回给client端
     }
}

sp<IMediaCodecList> MediaPlayerService::getCodecList() const {
    return MediaCodecList::getLocalInstance();
}
           

4.4 一个问题: MediaPlayerService和ResourceManagerService属于一个进程吗?

这里首先理解一个概念: 代码中的service究竟是什么?

class MediaPlayerService : public BnMediaPlayerService
class BnMediaPlayerService: public BnInterface<IMediaPlayerService>
class IMediaPlayerService: public IInterface
           

service是一个继承了interface的具体实现,用于和client通信。server端和client端同时继承interface类,一个是bp一个是bn。

所以呢?service和binder有啥关系? service和进程有啥关系?

service调用了instantiate函数就是把自己注册到了servicemanager里面,这个时候service就是一个binder接口。

注意:直到这里都没有进程的概念。service 不等于 进程。

那么进程是怎么来的?

看看service的启动流程, 在servicemanager加载rc文件的时候,这个rc文件里面调用的是/system/bin/mediaserver,一个可执行文件,可执行文件在系统中是什么?是一个单独的进程。到这里就破案了。

MediaPlayerService和ResourceManagerService都在/system/bin/mediaserver中。所以他们肯定属于同一个进程,但是他们分别注册到了service manager中,所以他们是两个binder。

5. 安卓的treble机制

在Android 8.0开始,Android引入了Treble的机制,为了方便Android系统的快速移植、升级,提升系统稳定性,Binder机制被拓展成了"/dev/binder", "/dev/hwbinder","/dev/vndbinder"。

Android-binder通信详解

三种binder的具体使用如下:

Android-binder通信详解

此处需要注意VNDK的两种概念:

在代码中,当一个so既需要在system分区使用又需要在vendr分区使用则声明该so为VNDK,然后so生成的时候就会产生两个, 一个在system目录,一个在vendor目录。

此处的vndk是两个vendor分区的进程通信用的。

6. 总结

   本文讲述了安卓binder通信。首先从linux已有进程通信存在的问题讲起,讲述了binder存在的必要,然后讲述了binder的实现流程,以及通过media player service为例讲述了在c/s架构中binder的具体使用过程。最后讲述了安卓中的treble机制,就是安卓对已有binder的几种扩展。下篇文章将通过audio finger和audio hal的通信再次讲述hwbinder的实现过程。敬请期待~