Android_Binder原理分析

Binder是什么?

Binder可以实现进程与进程之间的通信(IPC), Binder是Android底层系统的一个特色了,它很好地解决了进程间通讯的问题。

Android_Binder原理分析_第1张图片

 

可能很多小伙伴对Binder感觉有点儿陌生,但是Binder在Android系统中无处不在,比如:

 

  • 媒体的播放
  • 音视频捕获
  • 传感器使用
  • startActivity()/startService()
  • 等...

Binder是Android独有的跨线程通讯机制,它的运行机制和现实中的一个例子很像,我们来看一张图

 

Android_Binder原理分析_第2张图片

这张图很形象的提现了Binder的运行机制,有Client(个人电脑),Server(应用服务器),Binder(路由器),ServiceManager(DNS服务器)

 

Binder对服务端(Server)而言相当于服务端提供特定服务的接入点,想要对接该服务就要从这个接入点入手 对于客户端(Client)而言,Binder相当于通向服务端(Server)管道的入口,要想和服务端(Server)某个服务通讯,必须先建立管道,并获得管道的入口,也就是接入点

ServiceManager相当于DNS服务器

注: 这里只是举个形象的栗子,具体是怎样的,都做了什么,下面会慢慢讲~

Android为什么使用Binder做IPC?

为什么Android会采用Binder做IPC(进程间通讯)呢?这也是Binder的由来,首先Linux中是有多种跨进程通讯的方式,但是它们不太适用于Android的跨进程通讯的场景,我们大概来看下:

  • 管道 大多是指半双工管道,半双工管道指的是,A给B数据和,B给A数据是两件事情,只允许数据在一个方向上传输,类似于对讲机,同一时间双方只能有一方发送数据,而全双工就像电话,可以双方同时发送/接收数据,这种方式是非常消耗内存的(具体可百度~)
  • 共享内存 共享内存值得是多个进程可以访问同一块内存空间,这种方式管理会很混乱~
  • Socket Socket相对来说更适合的是网络通讯,对于进程间通讯显然不够和谐~

所以Binder是应需求而生,前面三种方式只是说了不是和Android的进程建通讯,那么Binder为什么适合呢?

主要是两个方面

  1. 安全性 Binder协议支持对通讯双方的身份信息进行较校验,既支持匿名的Binder也支持实名的Binder,像传统的Socket通讯,并没有严格的身份校验,只要知道ip地址就可以访问,在Android中每个应用安装成功都会分配一个唯一的UID,而每个进程都有一个PID,例如在Android9.0源码中startActivity()会对UIDPID做校验,下面会提到~
  2. 性能 Binder机制在进程间通讯时,数据只需Copy一次,而传统的通讯方式,比如管道的方式需要Copy两次,性能方面仅次于共享内存的方式~

另外还有一点是为什么Binder设计的是Client/Server的形式,因为系统提供了一个服务,可能很多app都需要使用该服务,所以是一个一对多的场景,所以Binder采用的是Client/Server的形式

如图(管道方式需要两次Copy操作):

Android_Binder原理分析_第3张图片

Binder中四个重要的角色

  • Client 客户端
  • Server 服务端
  • ServiceManager 就像上文所述的DNS服务器,它的主要作用是ClientServer之间的桥梁,Client可以通过ServiceManager拿到ServerBinder实体的引用
  • Binder驱动是连接 ClientServerServiceManager的桥梁,Android重很多系统服务是通过Binder拿到的,比如context.getSystemService (Context.AUDIO_SERVICE)获取音量的服务

 

Android_Binder原理分析_第4张图片

 

 

其中前三者ClientServerServiceManager都属于用户空间,而Binder驱动属于内核空间 注意用户空间是不可以进程间通讯的,内核空间是可以进程间通讯的 这里需要主要的是Binder驱动它是有个线程池的存在,有可能是并发, 这个线程池是由Binder驱动管理的,一个进程的Binder线程数默认是16,超过这个数会阻塞等待~

Binder中四个重要的对象

首先需要简单说下AIDL : AIDL是Android Interface definition language 安卓接口定义语言,是BinderClient进程Server进程通讯的语言,是为了Binder简化代码的架构

  • IBinder 是一个接口,表示可以实现跨进程通讯的能力,只需要实现找个接口就可以跨进程传输
  • Iinterface 代表的Server进程具备什么样的功能、能力,能够提供哪些方法,对应AIDL定义的接口
  • Binder Java层的Binder类,代表的是Binder的本地对象,有个重要的内部类BinderProxy
  • Stub 是使用AIDL时编译工具自动生成一个Stub的静态内部类,继承自Binder,是个抽象类,具体实现Iinterface的接口的具体逻辑,开发者自己实现

Binder通讯流程

先看两张图,Binder通讯流程图:

Android_Binder原理分析_第5张图片

如图: Binder通讯流程首先是,Client需要发送数据,做了(只做一次)copy from userBinderProxy,BinderProxy是可以操作内核的缓存区,内核的缓存区和Binder创建的内存映射(Binder创建的接收缓存区)是存在映射关系的,而服务端是与内存映射(Binder创建得接收缓存区)是存在直接的内存映射关系,所以只需要一次copy操作,相当于这一次复制,直接将数据复制到了Server进程的内存空间中去了。当然这中间室友校验的,比如: descriptorBinder实体的引用Binder实体是否匹配

详细流程图:

Android_Binder原理分析_第6张图片

源码分析

下面从源码角度简单分析内核层主要做的以下步骤:

  • 打开binder设备
  • buffer创建 (用于进程间数据传递)
  • 开辟内存映射 (128K)
  • ServiceManager启动
  • 打包Parcel中,数据写入binder设备,copy_from_user
  • 服务注册,添加到链表svclist中
  • 定义主线程中的线程池
  • 循环从mIn和mOut中取出读写请求,发到binder设备中

我们从Android源码中都可以看到这些,下面代码以Android9.0为例:

有兴趣的小伙伴可以自行翻阅:Android在线源码阅读

首先我们看下ServiceManager启动,ServiceManager是在Android系统启动时就就会唤起的服务可见system/core/rootdir/init.rc407行:

start servicemanager

ServiceManager会完成打开binder设备和开辟内存映射 (128K)的动作,可见device/google/cuttlefish_kernel/4.4-x86_64/System.map25306行(该文件需要下载查看,不支持在线浏览):

ffffffff815dbf50 t binder_mmap

frameworks/native/cmds/servicemanager/service_manager.cmain()方法中有:

 if (argc > 1) {
        driver = argv[1];
    } else {
        //打开Binder设备文件,返回文件描述符
        driver = "/dev/binder";
    }
    //Binder的buffer创建,用于进程间数据传输,开启128k大小的内存映射,路径见下方
    bs = binder_open(driver, 128*1024);

其实Service的注册也是在service_manager中的do_add_service()方法中完成的,这个不是Binder的核心知识,简单提下,感兴趣的可以看下

    //权限检查
  if (!svc_can_register(s, len, spid, uid)) {
        ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",
             str8(s, len), handle, uid);
        return -1;
    }
    //根据服务名在svclist链表上查找,看服务是否已经注册
    si = find_svc(s, len);
    if (si) {
        if (si->handle) {
        //注册过
            ALOGE("add_service('%s',%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n",
                 str8(s, len), handle, uid);
            svcinfo_death(bs, si);
        }
        si->handle = handle;
    } else {
        //没注册,分配一个服务管理的结构svcinfo,并将其添加到链表的list当中
        si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
        if (!si) {
            ALOGE("add_service('%s',%x) uid=%d - OUT OF MEMORY\n",
                 str8(s, len), handle, uid);
            return -1;
        }
        si->handle = handle;
        si->len = len;
        memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
        si->name[len] = '\0';
        si->death.func = (void*) svcinfo_death;
        si->death.ptr = si;
        si->allow_isolated = allow_isolated;
        si->dumpsys_priority = dumpsys_priority;
        //将代表该服务的结构插入到链表
        si->next = svclist;
        svclist = si;
    }

    //增加Binder的应用计数
    binder_acquire(bs, handle);
    //该服务退出需要通知ServiceManager
    binder_link_to_death(bs, handle, &si->death);
    return 0;

打开Binder设备驱动是在frameworks/native/cmds/servicemanager/binder.cbinder_open()方法中有这么一行代码:

//打开Binder设备驱动的时候,开启128k大小的内存映射是在这里执行的
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

打包Parcel,数据写入binder设备,copy_from_user可见frameworks/native/libs/binder/IServiceManager.cppaddService()方法: 这里会将Service相关信息打包成Parcel对象,并且调用remote()->transact()方法往下一步传输

 virtual status_t addService(const String16& name, const sp& service,
                                bool allowIsolated, int dumpsysPriority) {
        Parcel data, reply;
        //Parcel对象打包过程
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
        data.writeString16(name);
        data.writeStrongBinder(service);
        data.writeInt32(allowIsolated ? 1 : 0);
        data.writeInt32(dumpsysPriority);
        status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
        return err == NO_ERROR ? reply.readExceptionCode() : err;
    }

数据写入binder设备的过程在frameworks/native/libs/binder/IPCThreadState.cpp中实现的writeTransactionData()方法,

//这里主要是将`Parcel`对象中的信息封装成结构体,并且写入到`mOut`当中
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
...
//将数据写入Binder的设备当中,并等待返回结果
err = waitForResponse(reply);

另外从Binder设备中不停地读写的实现方式,是通过线程池的方式(上文有提到),不停地去读写,具体可见: frameworks/native/libs/binder/IPCThreadState.cppjoinThreadPool()方法,主要是定义了一个主线程中的线程池,

//将对象设为当前线程的私有
 pthread_setspecific(gTLS, this);
 clearCaller();
 //输入buffer预分配256大小的空间
 mIn.setDataCapacity(256);
 //输出buffer预分配256大小的空间
 mOut.setDataCapacity(256);

对Binder设备数据的读写,主要的工作就是循环的对mInmOut进行IO的读写,然后发送到Binder的设备中 对于数据是否需要读取/写的

     // Is the read buffer empty?,是否有读的请求
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();

    // We dont want to write anything if we are still reading
    // from data left in the input buffer and the caller
    // has requested to read the next data.
    // 是否有写的请求
    const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
    
    ...
    
    //将读写的请求数据发送到Binder设备中
     if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)

分享

最后在这里我分享自己收录在整理的Android学习知识点文档,里面有Handler相关面试题和其他知识点的讲解,希望可以帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,可以分享给身边好友一起学习
有需要的朋友可以点赞+评论+转发,关注我,然后私信我【666】获取,也可以点击《Android学习PDF+架构视频+面试文档》查看更多;

Android_Binder原理分析_第7张图片

 

Android_Binder原理分析_第8张图片

 

你可能感兴趣的:(程序员,Android)