Android_Binder原理分析

Binder是什么?

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

image

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

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

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

image

这张图很形象的提现了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操作):

管道方式需要Copy两次数据

Binder中四个重要的角色

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

其中前三者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通讯流程图:

image

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

详细流程图:

image

源码分析

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

  • 打开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<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.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)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269