Android

    技术2024-08-18  64

    Binder是什么?

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

     

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

     

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

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

     

    这张图很形象的提现了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为什么适合呢?

    主要是两个方面

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

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

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

    Binder中四个重要的角色

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

     

     

     

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

    Binder中四个重要的对象

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

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

    Binder通讯流程

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

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

    详细流程图:

    源码分析

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

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

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

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

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

    start servicemanager

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

    ffffffff815dbf50 t binder_mmap

    在frameworks/native/cmds/servicemanager/service_manager.c的main()方法中有:

    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.c的binder_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.cpp的addService()方法: 这里会将Service相关信息打包成Parcel对象,并且调用remote()->transact()方法往下一步传输

    virtual status_t addService(const String16& name, const sp<IBinder>& 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.cpp的joinThreadPool()方法,主要是定义了一个主线程中的线程池,

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

    对Binder设备数据的读写,主要的工作就是循环的对mIn和mOut进行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+架构视频+面试文档》查看更多;

     

     

    Processed: 0.014, SQL: 9