http://www.ox-holdings.com

听说你Binder机制学的不错,实现系统到 APP 的通信

摘要微信自用的安卓APP与系统间通信解决方案——Hardcoder已开源,该方案能让微信的整体性能提升10%-30%。1、Hardcoder 的诞生随着微信越来越复杂,性能优化变得越来越难做,优化所带来的效果提升也越来越不明显。所以我们⼀直在思考,该如何突破这个优化的极限?直到有一次与厂商的交流我们了解到,部分厂商会针对微信做一些小改动,其中比较典型的就是“暴力提频”。系统在识别到微信启动,页面切换等场景时,会粗暴地提高 CPU 频率,从而提升 APP 运行的性能。但由于厂商无法准确判断微信场景,暴力提频效果并不理想;而如果过多地提高 CPU 频率,又对手机的功耗有影响。这一方案启发了我们,我们何不跳出软件的范畴,在手机硬件的层面上挖掘更多的性能优化空间呢?于是 Hardcoder 框架应运而生。2、Hardcoder 是什么厂商暴力提频效果不理想是由于在目前 Android 框架下,手机没有办法准确获知 APP 需要资源的时机。如果我们需要挖掘手机硬件层面的性能优化,就需要跳过 Android 操作系统的应用框架,在应用开发者和硬件之间打开一个通道,让硬件可以直接根据应用开发者的需要进行资源的调度。Hardcoder 构建了 APP 与系统(ROM)之间可靠的通信框架,突破了 APP 只能调用系统标准 API,无法直接调用系统底层硬件资源的问题,让 Android APP 和系统能实时通信。利用 Hardcoder,APP 能充分调度系统资源如 CPU 频率,大小核,GPU 频率等来提升 APP 性能,系统能够从 APP 侧获取更多信息以便更合理提供各项系统资源。同时,对于 Android 缺乏标准接口实现的功能,APP 和系统间也可以通过该框架实现机型适配和功能拓展。3、Hardcoder 框架通信流程Hardcoder 框架分为 Server 端和 Client 端。其中 Server 端在厂商系统侧实现,Client 端以 aar 形式合入到 APP中。APP 在需要资源的时候,向 Hardcoder 的 Client 端发出请求。Hardcoder Client 端接收到请求后向 Hardcoder Server 端发出请求。Server 端接受到请求后会根据请求参数向硬件申请不同的资源,比如调整 CPU 频率,把线程绑定到大核运行等,实现了 APP 到系统的通信。同时系统也可把当前系统的状态通过 Hardcoder Client 在 Server 端注册的接口回调通知到 Client 端,从而 APP 可以获取到系统状态,实现系统到 APP 的通信。Hardcoder Client 端与 Server 端采用的是 LocalSocket 的通信方式,由于 Hardcoder 采用 Native 实现,因而在 C 层使用 Linux 的 socket 接口实现了一套 LocalSocket 机制作为 Client 端与 Server 端之间的通信方式。Hardcoder 通信框架有以下特点:1)系统服务为 optional,实现上可以完全支持或者部分支持;2)框架实现不依赖于特定 Android 系统,如 API level 限制;3)APP 的功能和业务特性不依赖于该框架。4、Hardcoder 适用场景和效果Hardcoder 框架有效提升了微信启动、发送视频、小程序启动等重度场景的速度,朋友圈的滑动流畅性也明显提升,平均优化效果达 10%-30%。此外,由于微信作为主动请求方可以在场景资源把控上做得更精细和准确,Hardcoder 在性能得到提升的同时仅增加了 2% 的电量消耗,相当于用 2% 的功耗换取平均 20% 的性能提升。Hardcoder 框架目前已接入 OPPO、vivo、华为、小米、三星、魅族等主流手机厂商,覆盖 4.6 亿+ 设备量。5、Hardcoder 开源从微信技术开放共享的理念出发,我们在腾讯内部进行了 Hardcoder 框架的宣传和推广,包括手机 QQ、企业微信、天天快报等多个应用团队接入。其中手机 QQ 接入 Hardcoder 后,在启动、打开聊天界面、发送图片等场景的平均优化效果达 10%-50%。我们现将 Hardcoder 框架开源,让更多 Android 开发者享受到 Hardcoder 框架的价值,解决大家在性能优化和机型适配上的烦恼。欢迎大家查阅 github 网址: Hardcoder一、通过 Hardcoder 技术方案介绍,了解 Hardcoder 实现原理以及框架;二、使用工程自带 testapp 快速使用 Hardcoder 并验证效果,具体请见 Hardcoder Testapp 测试指南;三、APP 接入 Hardcoder,具体请参见 Hardcoder 接入指南:1)下载 Hardcoder 工程编译 aar;2)项目 build.gradle 引入 Hardcoder aar;3)进程启动时调用 initHardCoder 建立 socket 连接(一般进程启动时需要请求资源,因而推荐在进程启动时调用)。每个进程都是独立的,都需要调用 initHardCoder 建立 socket 连接,建立连接后每个进程维持一个 socket,进程退出时 socket 也会断开;4)initHardCoder 回调成功后调用 checkPermission,传入 APP 已申请的各个厂商鉴权值;5)在需要请求资源的场景调用 startPerformance,传入请求资源的参数。若场景位于进程启动阶段,比如 APP 启动,需要在 initHardCoder 的回调成功以后再调用 startPerformance,确保连接已成功建立,或者判断 HardCoderJNI 的 isConnect() 检查 socket 是否已连接。6)场景结束时主动调用 stopPerformance,传入对应场景 startPerformance 时的返回值 hashCode 作为参数,停止本次请求。7)测试性能,APP 可对打开/关闭 Hardcoder 的情况做对比实验,测试性能是否有提升。四、向厂商申请线上权限,具体请见常见问题;五、发布带 Hardcoder 功能的 APP。附录: github的wiki 文档链接Hardcoder产品方案介绍: 技术方案介绍: testapp 测试指南: 接入指南:

Android跨进程通信IPC整体内容如下

Binder浅析

binder_transaction堆栈及唤醒那个队列

Binder是一个类似于C/S架构的通信框架,有时候客户端可能想知道服务端的状态,比如服务端如果挂了,客户端希望能及时的被通知到,而不是等到再起请求服务端的时候才知道,这种场景其实在互为C/S的时候最常用,比如AMS与APP,当APP端进程异常退出的时候,AMS希望能及时知道,不仅仅是清理APP端在AMS中的一些信息,比如ActivityRecord,ServiceRecord等,有时候可能还需要及时恢复一些自启动的Service。Binder实现了一套”死亡讣告”的功能,即:服务端挂了,或者正常退出,Binder驱动会向客户端发送一份讣告,告诉客户端Binder服务挂了。

  • 1、Android跨进程通信IPC之1——Linux基础
  • 2、Android跨进程通信IPC之2——Bionic
  • 3、Android跨进程通信IPC之3——关于"JNI"的那些事
  • 4、Android跨进程通信IPC之4——AndroidIPC基础1
  • 4、Android跨进程通信IPC之4——AndroidIPC基础2
  • 5、Android跨进程通信IPC之5——Binder的三大接口
  • 6、Android跨进程通信IPC之6——Binder框架
  • 7、Android跨进程通信IPC之7——Binder相关结构体简介
  • 8、Android跨进程通信IPC之8——Binder驱动
  • 9、Android跨进程通信IPC之9——Binder之Framework层C++篇1
  • 9、Android跨进程通信IPC之9——Binder之Framework层C++篇2
  • 10、Android跨进程通信IPC之10——Binder之Framework层Java篇
  • 11、Android跨进程通信IPC之11——AIDL
  • 12、Android跨进程通信IPC之12——Binder补充
  • 13、Android跨进程通信IPC之13——Binder总结
  • 14、Android跨进程通信IPC之14——其他IPC方式
  • 15、Android跨进程通信IPC之15——感谢

1. 背景知识

Binder在Android系统中是用来进行进程间通信的,所以在介绍Binder之前需要首先明确进程之间的通信,再次之前需要明确一些概念。
进程可以说是操作系统中最为重要的抽象概念,操作系统为了确保进程之间不会产生相互干扰,同时为了便于编写程序,采用了进程隔离的机制,即为每个进程分配独立虚拟地址空间,进程之间感觉不到彼此的存在,感觉自己仿佛占用整个内存空间。这样保证了进程的数据安全,也使得编程更加方便,但是必然存在另外的问题,那就是进程间通信,进程不可能完全独立运行,有时候需要相互通信获取别的进程的运行结果等,因此需要想办法解决进程间通信的问题。

操作系统除了为负责管理和调度进程之外,还需要管理计算机的硬件资源,但是不同进程对硬件资源的使用权限需要得到控制,因此操作系统区分了内核空间和用户空间。部分计算机资源由内核空间的进程负责管理,用户空间的进程无法访问,这种隔离可以起到安全作用。但是用户空间的进程有时候是需要访问一些计算机资源的,这时操作系统的内核就提供系统调用,可以在用户空间中使用,于是就出现了内核态和用户态的概念。用户进程运行在用户空间,此时处于用户态,没有特殊权限,不会对计算机资源造成威胁,如果需要部分的计算机资源可以通过系统调用使得该用户进程陷入到内核态,运行在内核空间,由内核控制访问计算机资源。

明白了系统调用和用户空间,内核空间就能够想到如何进行进程间通信了,既然用户空间的进程之间相互隔离,感觉不到彼此的存在,那么就可以通过操作系统的内核进行进程间通信了。Unix或者Linux系统的传统进程间通信机制比如socket,共享内存,信号量等都是基于内核实现的。Android是基于Linux的操作系统,而Linux内核是可以动态加载内核模块的,即在运行时链接到内核作为内核的一部分运行于内核空间,所以Android系统通过添加一个内核模块,用户空间的进程都是通过这个模块作为桥梁进行进程间通信,这里就出现了第一个与Binder有关的概念,即Binder驱动。

驱动程序一般指的是设备驱动程序(Device Driver),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作;

这里Binder驱动的个人理解是,它作为一个内核模块,管理一块内存,运行在内核空间,并提供进程间通信的功能,如果将这块内存看做是一个硬件的话,这个内核模块就是一个驱动程序,所以称之为Binder驱动。由于水平有限,本文只是对Binder通信机制的应用层次做浅显分析,因此对Binder驱动并不做了解。

Binder承担了绝大部分Android进程通信的职责,可以看做是Android的血管系统,负责不同服务模块进程间的通信。在对Binder的理解上,可大可小,日常APP开发并不怎么涉及Binder通信知识,最多就是Service及AIDL的使用会涉及部分Binder知识。Binder往小了说可总结成一句话:一种IPC进程间通信方式,负责进程A的数据,发送到进程B。往大了说,其实涉及的知识还是很多的,如Android 对于原Binder驱动的扩展、Zygote进程孵化中对于Binder通信的支持、Java层Binder封装,Native层对于Binder通信的封装、Binder讣告机制等等。很多分析Binder框架的文都是从ServiceManager、Binder驱动、addService、getService来分析等来分析,其实这些主要是针对系统提供的服务,但是bindService启动的服务走的却还是有很大不同的。本篇文章主要简述一些Binder难以理解的点,但不会太细的跟踪分析,只抛砖,自己去发掘玉,由于篇幅过大,分三篇:

这个“讣告”究竟是如何实现的呢?其作用又是什么呢?对于Android而言,Binder“讣告”有点采用了类似观察者模式,因此,首先需要将Observer注册到目标对象中,其实就是将Client注册到Binder驱动,将来Binder服务挂掉时候,就能通过驱动去发送。Binder“讣告”发送的入口只有一个:在释放binder设备的时候,在在操作系统中,无论进程是正常退出还是异常退出,进程所申请的所有资源都会被回收,包括打开的一些设备文件,如Binder字符设备等。在释放的时候,就会调用相应的release函数,“讣告”也就是在这个时候去发送的。因此Binder讣告其实就仅仅包括两部分:注册与通知。

本篇文章的主要内容如下:

2.Binder通信模型

在介绍Binder通信模型之前有一个问题那就是Android为什么选择使用Binder,而不是之前Linux系统中就存在的传统进程间通信机制,网上的资料说是考虑到安全和性能两个方面,由于对Linux系统了解很少,对Socket,共享内存等知之甚少,而且对Binder理解也十分浅显,所以对于Binder在安全和性能两个方面的提升并不理解,这部分需要进一步学习。

Binder通信模型采用了C/S架构,即Client和Server之间通信。在这里需要首先明确Client和Server的概念,他们代表了两种身份,某个进程在进行进程间通信时并不是一直扮演一种身份,而是根据一次进程间通信中,进程是请求某项功能还是提供某种功能扮演着不同身份。此外,这里还有另外两个概念,就是ServiceManager(以下简称SM)和Service,SM是一个守护进程,在Binder通信模型中作为中心的调度者,而Service则是代表Server为Client提供的一项服务,可以理解为一些功能的集合。

所以Binder通信模型可以简单总结为以下的过程,Server向SM注册,它可以提供哪些Service,Client在需要某些功能时向SM发出请求,SM通过查询找到相应Server,此时通过Binder驱动在Client与Server之间建立连接,这样Client与Server就实现了通信。这里需要注意的是,注册和请求过程也是通过Binder通信完成的。也就是说Server在注册的时候它是Client,SM是Server,请求过程同理,在建立连接以后,Server才作为Server身份,Client依然作为Client使用Server提供的Service。

Binder的通信模型很简单,网上的流程简介图也很多,这里就不再贴出来了,由于采用的是C/S架构,那么与计算机网络的应用层必然十分相似。Client和Server很简单,类似于浏览器和服务器,SM则类似于DNS,不过也有一些区别,DNS是负责根据域名查找IP地址,然后传输到相应的服务器则是交由网络层处理,而这里的SM则是根据请求的Service查找到相应的Server,然后则是由Binder驱动负责在Client与Server之间建立连接。最后Service的类比概念也很简单,就像是HTTP,FTP等,也就是Server所能提供的服务。

听说你Binder机制学的不错,来解决下这几个问题(一)
听说你Binder机制学的不错,来看看这几个问题(二)
听说你Binder机制学的不错,来看看这几个问题(三)

Binder"讣告"的注册入口

这里拿bindService为例子进行分析,其他场景类似,bindService会首先请求AMS去启动Service,Server端进程在启动时,会调用函数open来打开设备文件/dev/binder,同时将Binder服务实体回传给AMS,AMS再将Binder实体的引用句柄通过Binder通信传递给Client,也就是在AMS回传给Client的时候,会向Binder驱动注册。其实这也比较好理解,获得了服务端的代理,就应该关心服务端的死活 。当AMS利用IServiceConnection这条binder通信线路为Client回传Binder服务实体的时候,InnerConnection就会间接的将死亡回调注册到内核:

    private static class InnerConnection extends IServiceConnection.Stub {
        final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;

        public void connected(ComponentName name, IBinder service) throws RemoteException {
            LoadedApk.ServiceDispatcher sd = mDispatcher.get();
            if (sd != null) {
                sd.connected(name, service);
            }
        }
    }

ServiceDispatcher函数进一步调用 doConnected

public void doConnected(ComponentName name, IBinder service) {
    ServiceDispatcher.ConnectionInfo old;
    ServiceDispatcher.ConnectionInfo info;
    synchronized (this) {     
        if (service != null) {
            mDied = false;
            info = new ConnectionInfo();
            info.binder = service;
            info.deathMonitor = new DeathMonitor(name, service);
            try {
            <!-- 关键点点1-->
                service.linkToDeath(info.deathMonitor, 0);
            } 
}

看关键点点1 ,这里的IBinder service其实是AMS回传的服务代理BinderProxy,linkToDeath是一个Native函数,会进一步调用BpBinde的linkToDeath:

status_t BpBinder::linkToDeath(
    const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags){
    <!--关键点1-->              
                IPCThreadState* self = IPCThreadState::self();
                self->requestDeathNotification(mHandle, this);
                self->flushCommands();

}

最终调用IPCThreadState的requestDeathNotification(mHandle, this)向内核发送BC_REQUEST_DEATH_NOTIFICATION请求:

status_t IPCThreadState::requestDeathNotification(int32_t handle, BpBinder* proxy)
{
    mOut.writeInt32(BC_REQUEST_DEATH_NOTIFICATION);
    mOut.writeInt32((int32_t)handle);
    mOut.writeInt32((int32_t)proxy);
    return NO_ERROR;
}

最后来看一下在内核中,是怎么登记注册的:

int
binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
            void __user *buffer, int size, signed long *consumed)
{
...
case BC_REQUEST_DEATH_NOTIFICATION:
        case BC_CLEAR_DEATH_NOTIFICATION: {
            ...
            ref = binder_get_ref(proc, target);
            if (cmd == BC_REQUEST_DEATH_NOTIFICATION) {
                ...关键点1
                death = kzalloc(sizeof(*death), GFP_KERNEL);
                binder_stats.obj_created[BINDER_STAT_DEATH]++;
                INIT_LIST_HEAD(&death->work.entry);
                death->cookie = cookie;
                ref->death = death;
                if (ref->node->proc == NULL) {
                    ref->death->work.type = BINDER_WORK_DEAD_BINDER;
                    if (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED)) {
                        list_add_tail(&ref->death->work.entry, &thread->todo);
                    } else {
                        list_add_tail(&ref->death->work.entry, &proc->todo);
                        wake_up_interruptible(&proc->wait);
                    }
                }
            } 
 }

看关键点1 ,其实就是为Client新建binder_ref_death对象,并赋值给binder_ref。在binder驱动中,binder_node节点会记录所有binder_ref,当binder_node所在的进程挂掉后,驱动就能根据这个全局binder_ref列表找到所有Client的binder_ref,并对于设置了死亡回调的Client发送“讣告”,这是因为在binder_get_ref_for_node向Client插入binder_ref的时候,也会插入binder_node的binder_ref列表。

static struct binder_ref *
binder_get_ref_for_node(struct binder_proc *proc, struct binder_node *node)
{
    struct rb_node *n;
    struct rb_node **p = &proc->refs_by_node.rb_node;
    struct rb_node *parent = NULL;
    struct binder_ref *ref, *new_ref;

    if (node) {
        hlist_add_head(&new_ref->node_entry, &node->refs);
        }
    return new_ref;
}

图片 1

binder讣告原理.jpg

如此,死亡回调入口就被注册到binder内核驱动,之后,等到进程结束要释放binder的时候,就会触发死亡回调。

图片 2

死亡讣告的注册.png

  • 1 Android为什么选用Binder作为最重要的IPC机制
  • 2 Binder中相关的类简述
  • 3 Binder机制概述
  • 4 Binder通信概述
  • 5 Binder协议
  • 6 Binder架构

3. Binder通信原理

上一部分介绍Binder的通信模型中说SM负责在Client与Server之间建立连接,底层由Binder驱动负责,但是在应用层如何建立连接依然不清楚,由于本文是浅析Binder,所以对Binder驱动不做分析,只是对应用层的内容做出分析。

我们都知道Android操作系统是基于Linux内核,底层是C语言实现,是面向过程的语言,而FrameWork层以及应用层则是基于Java语言,Java语言最大的特点就是面向对象的语言,那么在应用层使用进程间通信,如果在进程间传递对象则是最好的方法。那么这里就出现了第二个与Binder相关的概念,即Binder对象,即可以在进程间传递的对象。但是之前说进程间通信底层是由Binder驱动负责,Binder驱动是运行在内核空间的内核模块,那么肯定是由C语言实现的,并不支持对象的传递,除非是可以序列化的对象,那么如果在应用层中屏蔽底层细节,建立一种可以传递对象的假象呢,那么就是使用代理,就是使用BinderProxy。

Binder对象是Server的本地对象,可以提供某项功能或者说可以作为某个Service,当Client需要某个服务向SM查询时,SM找到对应的Server,由Binder驱动向Client进程提供这个Binder对应的BinderProxy,Client进程可以拿到BinderProxy对象,就像是一个对Binder对象的引用,仿佛Binder对象从Server进程传递到了Client进程。其实Binder本地对象只有一个,而有很多的代理处在不同的Client进程中,Client进程通过调用BinderProxy方法就可以使用Server在Binder中实现的功能,中间的传递则是由Binder驱动完成,所以Binder驱动只需要负责Binder与BinderProxy之间的通信即可,这样Binder看起来就像是可以传递的对象。不过这里有一个问题,就是有很多BinderProxy的情况下,如果做同步与互斥,还需要进一步学习。

  • Binder的定向制导,如何找到目标Binder,唤起进程或者线程
  • Binder中的红黑树,为什么会有两棵binder_ref红黑树
  • Binder一次拷贝原理(直接拷贝到目标线程的内核空间,内核空间与用户空间对应)
  • Binder传输数据的大小限制(内核4M 上层限制1m-8k),传输Bitmap过大,就会崩溃的原因,Activity之间传输BitMap
  • 系统服务与bindService等启动的服务的区别
  • Binder线程、Binder主线程、Client请求线程的概念与区别
  • Client是同步而Server是异步到底说的什么
  • Android APP进程天生支持Binder通信的原理是什么
  • Android APP有多少Binder线程,是固定的么
  • Binder线程的睡眠与唤醒(请求线程睡在哪个等待队列上,唤醒目标端哪个队列上的线程)
  • Binder协议中BC与BR的区别
  • Binder在传输数据的时候是如何层层封装的--不同层次使用的数据结构(命令的封装
  • Binder驱动传递数据的释放(释放时机)
  • 一个简单的Binder通信C/S模型
  • ServiceManager addService的限制(并非服务都能使用ServiceManager的addService)
  • bindService启动Service与Binder服务实体的流程
  • Java层Binder实体与与BinderProxy是如何实例化及使用的,与Native层的关系是怎样的
  • Parcel readStrongBinder与writeStrongBinder的原理

死亡通知的发送

在调用binder_realease函数来释放相应资源的时候,最终会调用binder_deferred_release函数。该函数会遍历该binder_proc内所有的binder_node节点,并向注册了死亡回调的Client发送讣告,

static void binder_deferred_release(struct binder_proc *proc)
    {         ....
        if (ref->death) {
                death++;
                if (list_empty(&ref->death->work.entry)) {
                    ref->death->work.type = BINDER_WORK_DEAD_BINDER;
                    list_add_tail(&ref->death->work.entry, &ref->proc->todo);
                    // 插入到binder_ref请求进程的binder线程等待队列????? 天然支持binder通信吗?
                    // 什么时候,需要死亡回调,自己也是binder服务?
                    wake_up_interruptible(&ref->proc->wait);
                }               
        ...
 }

死亡讣告被直接发送到Client端的binder进程todo队列上,这里似乎也只对于互为C/S通信的场景有用,当Client的binder线程被唤醒后,就会针对“讣告”做一些清理及善后工作:

static int
binder_thread_read(struct binder_proc *proc, struct binder_thread *thread,
    void  __user *buffer, int size, signed long *consumed, int non_block)
    {
        case BINDER_WORK_DEAD_BINDER:
                case BINDER_WORK_DEAD_BINDER_AND_CLEAR:
                case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: {
                    struct binder_ref_death *death = container_of(w, struct binder_ref_death, work);
                    uint32_t cmd;
                    if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION)
                        cmd = BR_CLEAR_DEATH_NOTIFICATION_DONE;
                    else
                        cmd = BR_DEAD_BINDER;
                    ...
 }

这里会向用户空间写入一个BR_DEAD_BINDER命令,并返回talkWithDriver函数,返回后,IPCThreadState会继续执行executeCommand,

status_t IPCThreadState::executeCommand(int32_t cmd)
{
    // 死亡讣告
    case BR_DEAD_BINDER:
        {
            BpBinder *proxy = (BpBinder*)mIn.readInt32();
            <!--关键点1 -->
            proxy->sendObituary();
            mOut.writeInt32(BC_DEAD_BINDER_DONE);
            mOut.writeInt32((int32_t)proxy);
        } break;

}

看关键点1,Obituary直译过来就是讣告,其实就是利用BpBinder发送讣告,待讣告处理结束后,再向Binder驱动发送确认通知。

void BpBinder::sendObituary()
{
    ALOGV("Sending obituary for proxy %p handle %d, mObitsSent=%sn",
        this, mHandle, mObitsSent ? "true" : "false");
    mAlive = 0;
    if (mObitsSent) return;
    mLock.lock();
    Vector<Obituary>* obits = mObituaries;
    if(obits != NULL) {
    <!--关键点1-->
        IPCThreadState* self = IPCThreadState::self();
        self->clearDeathNotification(mHandle, this);
        self->flushCommands();
        mObituaries = NULL;
    }
    mObitsSent = 1;
    mLock.unlock();
    if (obits != NULL) {
        const size_t N = obits->size();
        for (size_t i=0; i<N; i++) {
            reportOneDeath(obits->itemAt(i));
        }
        delete obits;
    }
}

看关键点1,这里跟注册相对应,将自己从观察者列表中清除,之后再上报

void BpBinder::reportOneDeath(const Obituary& obit)
{
    sp<DeathRecipient> recipient = obit.recipient.promote();
    ALOGV("Reporting death to recipient: %pn", recipient.get());
    if (recipient == NULL) return;

    recipient->binderDied(this);
}

进而调用上层DeathRecipient的回调,做一些清理之类的逻辑。以AMS为例,其binderDied函数就挺复杂,包括了一些数据的清理,甚至还有进程的重建等,不做讨论。

图片 3

死亡讣告的发送.png

Android后台杀死系列之一:FragmentActivity及PhoneWindow后台杀死处理机制
Android后台杀死系列之二:ActivityManagerService与App现场恢复机制
Android后台杀死系列之三:LowMemoryKiller原理(4.3-6.0)
Android后台杀死系列之四:Binder讣告原理
Android后台杀死系列之五:Android进程保活-自“裁”或者耍流氓

作者:看书的小蜗牛
原文链接: Android后台杀死系列之四:Binder讣告原理

一 、 Android为什么选用Binder作为最重要的IPC机制

我们知道在Linux系统中,进程间的通信方式有socket,named pipe,message queue, signal,sharememory等。这几种通信方式的优缺点如下:

  • name pipe:任何进程都能通讯,但速度慢
  • message queue:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题。
  • signal:不能传递复杂消息,只能用来同步
  • shared memory:能够容易控制容量,速度快,但要保持同步,比如写一个进程的时候,另一个进程要注意读写的问题,相当于线程中的线程安全。当然,共享内存同样可以作为线程通讯,不过没有这个必要,线程间本来就已经共享了同一个进程内的一块内存。
  • socket:本机进程之间可以利用socket通信,跨进程之间也可利用socket通信,通常RPC的实现最底层都是通过socket通信。socket通信是一种比较复杂的通信方式,通常客户端需要开启单独的监听线程来接受从服务端发过来的数据,客户端线程发送数据给服务端,如果需要等待服务端的响应,并通过监听线程接受数据,需要进行同步,是一件很麻烦的事情。socket通信速度也不快。

Android中属性服务的实现和vold服务的实现采用了socket,getprop和setprop等命令都是通过socket和init进程通信来获的属性或者设置属性,vdc命令和mount service也是通过socket和vold服务通信来操作外接设备,比如SD卡

Message queue允许任意进程共享消息队列实现进程间通信,并由内核负责消息发送和接受之间的同步,从而使得用户在使用消息队列进行通信时不再需要考虑同步问题。这样使用方便,但是信息的复制需要额外消耗CPU时间,不适合信息量大或者操作频繁的场合。共享内存针对消息缓存的缺点改而利用内存缓冲区直接交换信息,无须复制,快递,信息量大是其优点。

共享内存块提供了在任意数量的进程之间进行高效的双向通信机制,每个使用者都可以读写数据,但是所有程序之间必须达成并遵守一定的协议,以防止诸如在读取信息之前覆盖内存空间等竞争状态的实现。不幸的是,Linux无法严格保证对内存块的独占访问,甚至是你通过使用IPC_PRIVATE创建新的共享内存块的时候,也不能保证访问的的独占性。同时,多个使用共享内存块的进程之间必须协调使用同一个键值。

Android应用程序开发者开发应用程序时,对系统框架的进程和线程运行机制不必了解,只需要利用四大组件开发,Android应用开发时可以轻易调用别的软件提供的功能,甚至可以调用系统App,在Android的世界里,所有应用都是平等的,但实质上应用进程被隔离在不同的沙盒里。

Android平台的进程之间需要频繁的通信,比如打开一个应用便需要在Home应用程序进程和运行在system_server进程里的ActivityManagerService通信才能打开。正式由于Android平台的进程需要非常频繁的通信,故此对进程间通信机制要求比较高,速度要快,还要能进行复杂的数据的交换,应用开发时尽可能简单,并能提供同步调用。虽然共享内存的效率高,但是它需要复杂的同步机制,使用时很麻烦,故此不能采用。Binder能满足这些要求,所以Android选择了Binder作为最核心的进程间通信方式。Binder主要提供一下功能:

  • 1、用驱动程序来推进进程间的通信方式
  • 2、通过共享内存来提高性能
  • 3、为进程请求分配的每个进程的线程池,每个进程默认启动的两个Binder服务线程
  • 4、针对系统中的对象引入技术和跨进程对象的引用映射
  • 5、进程间同步调用。

4. AIDL分析

上面概念扯了一堆,该分析一点实际的代码了,即在Android开发过程中进程间通信通常使用的AIDL语言。但是在介绍使用AIDL之前还是需要啰嗦几个概念,即在Java中的IBinder, Binder, BinderProxy, IInterface, Stub, StubProxy等几个涉及到的类。

首先是第一个接口IBinder, 它代表了一种跨进程传输的能力;只要实现了这个接口,就能将这个对象进行跨进程传递;这是驱动底层支持的;在跨进程数据流经驱动的时候,驱动会识别IBinder类型的数据,从而自动完成不同进程Binder本地对象以及Binder代理对象的转换。

Java层的Binder类,代表的其实就是Binder本地对象。BinderProxy类是Binder类的一个内部类,它代表远程进程的Binder对象的本地代理;这两个类都继承自IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder驱动会自动完成这两个对象的转换。

然后第二个接口IInterface,代表Server所提供的功能或者说是服务。在Server中定义的功能接口都需要扩展这个接口。

最后是Stub和Stub.Proxy。Stub是在AIDL中自动生成的类,它的字面意思是存根,它继承自Binder,同时实现了IInterface的接口,有点使用的适配器模式的意思,即代表了可以在进程间传递的对象,同时又可以提供对应的功能。而Stub.Proxy则是Stub的一个内部类,虽然看名字像是Stub的代理,其实这里并没有使用代理模式,Stub.Proxy中,个人理解也有点使用适配器模式的意思,而它并没有继承, 而是使用了组合的方式,即持有一个BinderProxy, 因为是BinderProxy,所以名字是StubProxy,而不是只它是Stub的代理。这两个类在这里说过于空虚,在后面的AIDL的生成代码中再做详细介绍。

首先是定义自己的AIDL文件

interface IMyAidlInterface {
   void myMethod();
}

然后在生成的文件中找到IMyAidlInterface对应的Java文件,本文主要是对生成的文件进行分析,从而理解Binder通信机制。

下面的代码中是对生成的代码做了简化,便于阅读

public interface IMyAidlInterface extends IInterface{
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends Binder implements IMyAidlInterface{

        private static final java.lang.String DESCRIPTOR = "com.example.androidlearning.IMyAidlInterface";

        /** Construct the stub at attach it to the interface. */
        public Stub(){
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.example.androidlearning.IMyAidlInterface interface,
         * generating a proxy if needed.
         */
        public static IMyAidlInterface asInterface(IBinder obj){
            if ((obj==null)) {
                return null;
            }
            IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin!=null)&&(iin instanceof IMyAidlInterface))) {
                return ((IMyAidlInterface)iin);
            }
            return new Stub.Proxy(obj);
        }

        @Override public IBinder asBinder(){
            return this;
        }

        @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException{
            switch (code){
            case INTERFACE_TRANSACTION:
                {
                reply.writeString(DESCRIPTOR);
                return true;
                }
            case TRANSACTION_myMethod:
                {
                data.enforceInterface(DESCRIPTOR);
                this.myMethod();
                reply.writeNoException();
                return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }


        private static class Proxy implements IMyAidlInterface{
            private IBinder mRemote;
            Proxy(IBinder remote){
                mRemote = remote;
            }

            @Override public IBinder asBinder(){
                return mRemote;
            }

            public String getInterfaceDescriptor(){
                return DESCRIPTOR;
            }

            @Override public void myMethod() throws RemoteException{
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_myMethod, _data, _reply, 0);
                    _reply.readException();
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
        static final int TRANSACTION_myMethod = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }


    public void myMethod() throws android.os.RemoteException;
}

这里首先看一下生成代码的主体结构,IMyAidlInterface扩展IInterface,而其内部类Stub继承Binder,同时实现IMyAidlInterface,并且Stub是一个抽象类,我们所定义的功能方法作为抽象方法,留由子类实现。Stub的一个子类Stub.Proxy则是使用另一种适配器模式的方法,即组合方式包含BinderProxy, 同时实现IMyAidlInterface, 而其他具体代码后面再做分析,我们先看Client与Server如何通过Binder完成通信。

首先看Server进程中的代码,在Service中:

public class MyService extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mStub;
    }


    private IMyAidlInterface.Stub mStub = new IMyAidlInterface.Stub() {
        @Override
        public void myMethod() throws RemoteException {
            Log.d("recluse", "log from remote process");
        }
    };
}

这里使用匿名内部类扩展了IMyAidlInterface.Stub,并实现了我们所定义的功能方法,这里mStub就是Server进程中的本地Binder对象。在onBinder 中返回,当有客户端调用bindService()方法时,系统会回到onBind方法。

再看Client中的代码,在Activity中:

public class MainActivity extends AppCompatActivity {

    private IMyAidlInterface mAidlInterface;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mAidlInterface = IMyAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent(this, MyService.class);
        bindService(intent, mConnection, 0);

        try {
            mAidlInterface.myMethod();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

这里的ServiceConnection也是使用的内部类实现,为了代码简便。这样在连接成功以后,就可以获取IMyAidlInterface.Stub对象,它实现了IMyInterface接口,在Client进程中就可以使用我们所定义的功能方法,而该方法则是在Server进程中的Service中定义的Stub中实现的,从而完成了进程间调用。那么下面就根据这三段代码分析内部如何完成进程间通信的。

首先从bindService()方法开始,Client进程就会想SM请求查询对应的Service,SM找到Server以后,系统回调Service的onBinde()方法,返回Server进程中的Binder对象,也就是我们所定义的IMyInterface.Stub对象,而由于Activity所在的Client进程和Service所在的Server进程并不是同一个进程,在Activity里面的ServiceConnection的onServiceConnection()方法被系统回调时则则传递进去Binder本地对象的代理,即BinderProxy对象。此时我们再看Stub提供的静态方法asInterface(),该方法的功能就是根据Binder对象,返回我们想要的实现我们定义接口,也就是IMyAidlInterface的对象,在该方法中,

/**
 * Cast an IBinder object into an com.example.androidlearning.IMyAidlInterface interface,
 * generating a proxy if needed.
 */
public static IMyAidlInterface asInterface(IBinder obj){
    if ((obj==null)) {
        return null;
    }
    IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin!=null)&&(iin instanceof IMyAidlInterface))) {
        return ((IMyAidlInterface)iin);
    }
    return new Stub.Proxy(obj);
}

DESCRIPTOR是一个字符串,是Binder的唯一标识。在这个方法中首先查找本地Binder对象,如果没有,说明传递进来的IBinder是一个BinderProxy对象,此时返回一个Stub.Proxy对象,下面我们来看Stub.Proxy的代码:

private static class Proxy implements IMyAidlInterface{
    private IBinder mRemote;
    Proxy(IBinder remote){
        mRemote = remote;
    }

    @Override public IBinder asBinder(){
        return mRemote;
    }

    public String getInterfaceDescriptor(){
        return DESCRIPTOR;
    }

    @Override public void myMethod() throws RemoteException{
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_myMethod, _data, _reply, 0);
            _reply.readException();
        }
        finally {
            _reply.recycle();
            _data.recycle();
        }
    }
}

Stub.Proxy同样实现了我们定义的功能接口,而且包含一个BinderProxy对象,当我们在Client进程中调用我们所定义的功能方法时,其实就是调用Stub.Proxy中实现的方法,在实现该功能方法时,它首先将参数序列化,然后调用BinderProxy的transact()方法,调用该方法以后,Binder驱动会唤醒Server进程中的本地Binder对象, 并调用它的onTransact()方法,这个过程有Binder驱动实现,暂时还没有学习到。最后Binder负责将返回数据序列化返回该方法,写入到_reply中。下面看Stub的onTransact()方法:

@Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException{
    switch (code){
    case INTERFACE_TRANSACTION:
        {
        reply.writeString(DESCRIPTOR);
        return true;
        }
    case TRANSACTION_myMethod:
        {
        data.enforceInterface(DESCRIPTOR);
        this.myMethod();
        reply.writeNoException();
        return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}

在Stub类的代码的最后我们看到定义了TRANSACTION_myMethod,是一个整型,也就是说在Binder中对每一个方法都进行了编号,在transact()方法中传入编号,然后在onTransact()方法中,根据请求的变化调用相应的方法。这里我们看到data接收参数,然后调用本地Binder中定义的功能方法,这里是抽象方法,留有子类实现,最后将结果写入到_reply中,由Binder驱动负责将返回值传递到BinderProxy的transact()方法中的_reply。到此我们就完成了对于进程间调用的整个过程,分析完了生成的代码,最后只剩下asBinder方法,这是IInterface中定义的方法,它负责返回对应的IBinder。这里有IBinder和IInterface两个接口,其中IBinder有两个实现类Binder和BinderProxy, 所以一个需要继承或者组合,一个需要实现,当然对于IInterface的实现也可以通过定义其他类实现并组合到Stub或者Stub.Proxy类中,总之具有两个接口的功能即可。AIDL的生成代码中,对于IBinder的处理,Stub使用继承,可以理解,因为需要回调,Stub.Proxy则是使用了组合,其实继承也可以,但是有什么坏处暂时还不明白。对IInterface则是统一处理,这样也最简单。

连接过程可以总结为Client调用bindService, 系统回调Server进程中Service的onBind(), 系统根据onBind()中返回的Binder,返回给Client进程一个对应的BinderProxy, 这个是在onServiceConnected()回调方法中传递到Client进程。这样Client进程持有BinderProxy对象,也就表示与Server建立连接。Client用拿到的BinderProxy或者Binder本地对象(这种是绑定本地服务,不需要进程间通信,如asInterface中,直接返回了自身), 调用静态方法asInterface()将IBinder对象转换成为我们所定义的接口对象,该对象可能继承自Binder或BinderProxy, 也可能组合的方式持有Binder或BinderProxy。这个IBinder对象就像是传到Client进程中了,同时还可以调用功能方法。

调用过程可以总结为, 在Client进程中使用asInterface()返回的对象调用功能方法,如果是进程间通信,则是序列化参数并调用BinderProxy的transact()方法,有Binder驱动完成中间过程,然后回调本地Binder的onTransact()方法,在该方法中完成远程方法的调用,并将结果写会,有Binder驱动再完成中间过程,将写过传回到BinderProxy的transact()方法中,从而完成一次进程间通信。

至此,Binder进程间通信的应用层的机制已经分析结束,关于Binder涉及到三个概念,第一个就是Binder驱动,运行在内核中,是进程间通信的关键,第二个是Binder类,通过代理模式,在BinderProxy和Binder中建立连接,造成一种可以在进程间传递对象的假象,从而使得Binder对象仿佛变成可以传递的对象。第三个就是Binder进程间通信机制,其实本文所介绍的整个可以称之为Binder通信机制。Binder之所以复杂,可能就是因为它的多重身份吧。

Binder如何精确制导,找到目标Binder实体,并唤醒进程或者线程

Binder实体服务其实有两种,一是通过addService注册到ServiceManager中的服务,比如ActivityManagerService、PackageManagerService、PowerManagerService等,一般都是系统服务;还有一种是通过bindService拉起的一些服务,一般是开发者自己实现的服务。这里先看通过addService添加的被ServiceManager所管理的服务。有很多分析ServiceManager的文章,本文不分析ServiceManager,只是简单提一下,ServiceManager是比较特殊的服务,所有应用都能直接使用,因为ServiceManager对于Client端来说Handle句柄是固定的,都是0,所以ServiceManager服务并不需要查询,可以直接使用。

理解Binder定向制导的关键是理解Binder的四棵红黑树,先看一下binder_proc结构体,在它内部有四棵红黑树,threads,nodes,refs_by_desc,refs_by_node,nodes就是Binder实体在内核中对应的数据结构,binder_node里记录进程相关的binder_proc,还有Binder实体自身的地址等信息,nodes红黑树位于binder_proc,可以知道Binder实体其实是进程内可见,而不是线程内。

struct binder_proc {
    struct hlist_node proc_node;
    struct rb_root threads;
    struct rb_root nodes;
    struct rb_root refs_by_desc;
    struct rb_root refs_by_node;
    。。。
    struct list_head todo;
    wait_queue_head_t wait;
    。。。
};

现在假设存在一堆Client与Service,Client如何才能访问Service呢?首先Service会通过addService将binder实体注册到ServiceManager中去,Client如果想要使用Servcie,就需要通过getService向ServiceManager请求该服务。在Service通过addService向ServiceManager注册的时候,ServiceManager会将服务相关的信息存储到自己进程的Service列表中去,同时在ServiceManager进程的binder_ref红黑树中为Service添加binder_ref节点,这样ServiceManager就能获取Service的Binder实体信息。而当Client通过getService向ServiceManager请求该Service服务的时候,ServiceManager会在注册的Service列表中查找该服务,如果找到就将该服务返回给Client,在这个过程中,ServiceManager会在Client进程的binder_ref红黑树中添加binder_ref节点,可见本进程中的binder_ref红黑树节点都不是本进程自己创建的,要么是Service进程将binder_ref插入到ServiceManager中去,要么是ServiceManager进程将binder_ref插入到Client中去。之后,Client就能通过Handle句柄获取binder_ref,进而访问Service服务。

binder_ref添加逻辑

getService之后,便可以获取binder_ref引用,进而获取到binder_proc与binder_node信息,之后Client便可有目的的将binder_transaction事务插入到binder_proc的待处理列表,并且,如果进程正在睡眠,就唤起进程,其实这里到底是唤起进程还是线程也有讲究,对于Client向Service发送请求的状况,一般都是唤醒binder_proc上睡眠的线程:

struct binder_ref {
    int debug_id;
    struct rb_node rb_node_desc;
    struct rb_node rb_node_node;
    struct hlist_node node_entry;
    struct binder_proc *proc;
    struct binder_node *node;
    uint32_t desc;
    int strong;
    int weak;
    struct binder_ref_death *death;
};

参考文档

Android Binder 分析——死亡通知(DeathRecipient)

二、Binder中相关的类简述

为了让大家更好的理解Binder机制,我这里把每个类都简单说下,设计到C层就是结构体。每个类/结构体都有一个基本的作用,还是按照之前的分类,如下图

图片 4

Binder类分布.png

关于其中的关系,比如继承,实现如下图:

图片 5

类关系图.png

binder_proc为何会有两棵binder_ref红黑树

binder_proc中存在两棵binder_ref红黑树,其实两棵红黑树中的节点是复用的,只是查询方式不同,一个通过handle句柄,一个通过node节点查找。个人理解:refs_by_node红黑树主要是为了
binder驱动往用户空间写数据所使用的,而refs_by_desc是用户空间向Binder驱动写数据使用的,只是方向问题。比如在服务addService的时候,binder驱动会在在ServiceManager进程的binder_proc中查找binder_ref结构体,如果没有就会新建binder_ref结构体,再比如在Client端getService的时候,binder驱动会在Client进程中通过 binder_get_ref_for_node为Client创建binder_ref结构体,并分配句柄,同时插入到refs_by_desc红黑树中,可见refs_by_node红黑树,主要是给binder驱动往用户空间写数据使用的。相对的refs_by_desc主要是为了用户空间往binder驱动写数据使用的,当用户空间已经获得Binder驱动为其创建的binder_ref引用句柄后,就可以通过binder_get_ref从refs_by_desc找到响应binder_ref,进而找到目标binder_node。可见有两棵红黑树主要是区分使用对象及数据流动方向,看下面的代码就能理解:

// 根据32位的uint32_t desc来查找,可以看到,binder_get_ref不会新建binder_ref节点
static struct binder_ref *binder_get_ref(struct binder_proc *proc,
                     uint32_t desc)
{
    struct rb_node *n = proc->refs_by_desc.rb_node;
    struct binder_ref *ref;
    while (n) {
        ref = rb_entry(n, struct binder_ref, rb_node_desc);
        if (desc < ref->desc)
            n = n->rb_left;
        else if (desc > ref->desc)
            n = n->rb_right;
        else
            return ref;
    }
    return NULL;
}

可以看到binder_get_ref并具备binder_ref的创建功能,相对应的看一下binder_get_ref_for_node,binder_get_ref_for_node红黑树主要通过binder_node进行查找,如果找不到,就新建binder_ref,同时插入到两棵红黑树中去

static struct binder_ref *binder_get_ref_for_node(struct binder_proc *proc,
                          struct binder_node *node)
{
    struct rb_node *n;
    struct rb_node **p = &proc->refs_by_node.rb_node;
    struct rb_node *parent = NULL;
    struct binder_ref *ref, *new_ref;
    while (*p) {
        parent = *p;
        ref = rb_entry(parent, struct binder_ref, rb_node_node);
        if (node < ref->node)
            p = &(*p)->rb_left;
        else if (node > ref->node)
            p = &(*p)->rb_right;
        else
            return ref;
    }

    // binder_ref 可以在两棵树里面,但是,两棵树的查询方式不同,并且通过desc查询,不具备新建功能
    new_ref = kzalloc(sizeof(*ref), GFP_KERNEL);
    if (new_ref == NULL)
        return NULL;
    binder_stats_created(BINDER_STAT_REF);
    new_ref->debug_id = ++binder_last_id;
    new_ref->proc = proc;
    new_ref->node = node;
    rb_link_node(&new_ref->rb_node_node, parent, p);
    // 插入到proc->refs_by_node红黑树中去
    rb_insert_color(&new_ref->rb_node_node, &proc->refs_by_node);
    // 是不是ServiceManager的
    new_ref->desc = (node == binder_context_mgr_node) ? 0 : 1;
    // 分配Handle句柄,为了插入到refs_by_desc
    for (n = rb_first(&proc->refs_by_desc); n != NULL; n = rb_next(n)) {
        ref = rb_entry(n, struct binder_ref, rb_node_desc);
        if (ref->desc > new_ref->desc)
            break;
        new_ref->desc = ref->desc + 1;
    }
    // 找到目标位置
    p = &proc->refs_by_desc.rb_node;
    while (*p) {
        parent = *p;
        ref = rb_entry(parent, struct binder_ref, rb_node_desc);
        if (new_ref->desc < ref->desc)
            p = &(*p)->rb_left;
        else if (new_ref->desc > ref->desc)
            p = &(*p)->rb_right;
        else
            BUG();
    }
    rb_link_node(&new_ref->rb_node_desc, parent, p);
    // 插入到refs_by_desc红黑树中区
    rb_insert_color(&new_ref->rb_node_desc, &proc->refs_by_desc);

    if (node) {
        hlist_add_head(&new_ref->node_entry, &node->refs);
        binder_debug(BINDER_DEBUG_INTERNAL_REFS,
                 "binder: %d new ref %d desc %d for "
                 "node %dn", proc->pid, new_ref->debug_id,
                 new_ref->desc, node->debug_id);
    } else {
        binder_debug(BINDER_DEBUG_INTERNAL_REFS,
                 "binder: %d new ref %d desc %d for "
                 "dead noden", proc->pid, new_ref->debug_id,
                  new_ref->desc);
    }
    return new_ref;
}

该函数调用在binder_transaction函数中,其实就是在binder驱动访问target_proc的时候,这也也很容易理解,Handle句柄对于跨进程没有任何意义,进程A中的Handle,放到进程B中是无效的。

两棵binder_ref红黑树

(一)、Java层相关类

Binder一次拷贝原理

Android选择Binder作为主要进程通信的方式同其性能高也有关系,Binder只需要一次拷贝就能将A进程用户空间的数据为B进程所用。这里主要涉及两个点:

  • Binder的map函数,会将内核空间直接与用户空间对应,用户空间可以直接访问内核空间的数据

  • A进程的数据会被直接拷贝到B进程的内核空间(一次拷贝)

      #define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))
    
      ProcessState::ProcessState()
          : mDriverFD(open_driver())
          , mVMStart(MAP_FAILED)
          , mManagesContexts(false)
          , mBinderContextCheckFunc(NULL)
          , mBinderContextUserData(NULL)
          , mThreadPoolStarted(false)
          , mThreadPoolSeq(1){
         if (mDriverFD >= 0) {
          ....
              // mmap the binder, providing a chunk of virtual address space to receive transactions.
              mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
         ...
    
          }
      }
    

mmap函数属于系统调用,mmap会从当前进程中获取用户态可用的虚拟地址空间(vm_area_struct *vma),并在mmap_region中真正获取vma,然后调用file->f_op->mmap(file, vma),进入驱动处理,之后就会在内存中分配一块连续的虚拟地址空间,并预先分配好页表、已使用的与未使用的标识、初始地址、与用户空间的偏移等等,通过这一步之后,就能把Binder在内核空间的数据直接通过指针地址映射到用户空间,供进程在用户空间使用,这是一次拷贝的基础,一次拷贝在内核中的标识如下:

    struct binder_proc {
    struct hlist_node proc_node;
    // 四棵比较重要的树 
    struct rb_root threads;
    struct rb_root nodes;
    struct rb_root refs_by_desc;
    struct rb_root refs_by_node;
    int pid;
    struct vm_area_struct *vma; //虚拟地址空间,用户控件传过来
    struct mm_struct *vma_vm_mm;
    struct task_struct *tsk;
    struct files_struct *files;
    struct hlist_node deferred_work_node;
    int deferred_work;
    void *buffer; //初始地址
    ptrdiff_t user_buffer_offset; //这里是偏移

    struct list_head buffers;//这个列表连接所有的内存块,以地址的大小为顺序,各内存块首尾相连
    struct rb_root free_buffers;//连接所有的已建立映射的虚拟内存块,以内存的大小为index组织在以该节点为根的红黑树下
    struct rb_root allocated_buffers;//连接所有已经分配的虚拟内存块,以内存块的开始地址为index组织在以该节点为根的红黑树下

    }

上面只是在APP启动的时候开启的地址映射,但并未涉及到数据的拷贝,下面看数据的拷贝操作。当数据从用户空间拷贝到内核空间的时候,是直从当前进程的用户空间接拷贝到目标进程的内核空间,这个过程是在请求端线程中处理的,操作对象是目标进程的内核空间。看如下代码:

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply){
                   ...
        在通过进行binder事物的传递时,如果一个binder事物(用struct binder_transaction结构体表示)需要使用到内存,
        就会调用binder_alloc_buf函数分配此次binder事物需要的内存空间。
        需要注意的是:这里是从目标进程的binder内存空间分配所需的内存
        //从target进程的binder内存空间分配所需的内存大小,这也是一次拷贝,完成通信的关键,直接拷贝到目标进程的内核空间
        //由于用户空间跟内核空间仅仅存在一个偏移地址,所以也算拷贝到用户空间
        t->buffer = binder_alloc_buf(target_proc, tr->data_size,
            tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
        t->buffer->allow_user_free = 0;
        t->buffer->debug_id = t->debug_id;
        //该binder_buffer对应的事务    
        t->buffer->transaction = t;
        //该事物对应的目标binder实体 ,因为目标进程中可能不仅仅有一个Binder实体
        t->buffer->target_node = target_node;
        trace_binder_transaction_alloc_buf(t->buffer);
        if (target_node)
            binder_inc_node(target_node, 1, 0, NULL);
        // 计算出存放flat_binder_object结构体偏移数组的起始地址,4字节对齐。
        offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));
           // struct flat_binder_object是binder在进程之间传输的表示方式 //
           // 这里就是完成binder通讯单边时候在用户进程同内核buffer之间的一次拷贝动作 //
          // 这里的数据拷贝,其实是拷贝到目标进程中去,因为t本身就是在目标进程的内核空间中分配的,
        if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
            binder_user_error("binder: %d:%d got transaction with invalid "
                "data ptrn", proc->pid, thread->pid);
            return_error = BR_FAILED_REPLY;
            goto err_copy_data_failed;
        }

可以看到binder_alloc_buf(target_proc, tr->data_size,tr->offsets_size, !reply && (t->flags & TF_ONE_WAY))函数在申请内存的时候,是从target_proc进程空间中去申请的,这样在做数据拷贝的时候copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)),就会直接拷贝target_proc的内核空间,而由于Binder内核空间的数据能直接映射到用户空间,这里就不在需要拷贝到用户空间。这就是一次拷贝的原理。内核空间的数据映射到用户空间其实就是添加一个偏移地址,并且将数据的首地址、数据的大小都复制到一个用户空间的Parcel结构体,具体可以参考Parcel.cpp的Parcel::ipcSetDataReference函数。

Binder一次拷贝原理.jpg

1、IInterface

类型:接口
作用:供Java层服务接口继承的接口,所有的服务提供者,必须继承这个接口

比如我们知道的ActivityManagerService继承自ActivityManagerNative,而ActivityManagerNative实现了IActivityManager,而IActivityManager继承自IInterface

Binder传输数据的大小限制

虽然APP开发时候,Binder对程序员几乎不可见,但是作为Android的数据运输系统,Binder的影响是全面性的,所以有时候如果不了解Binder的一些限制,在出现问题的时候往往是没有任何头绪,比如在Activity之间传输BitMap的时候,如果Bitmap过大,就会引起问题,比如崩溃等,这其实就跟Binder传输数据大小的限制有关系,在上面的一次拷贝中分析过,mmap函数会为Binder数据传递映射一块连续的虚拟地址,这块虚拟内存空间其实是有大小限制的,不同的进程可能还不一样。

普通的由Zygote孵化而来的用户进程,所映射的Binder内存大小是不到1M的,准确说是 110241024) - (4096 *2) :这个限制定义在ProcessState类中,如果传输说句超过这个大小,系统就会报错,因为Binder本身就是为了进程间频繁而灵活的通信所设计的,并不是为了拷贝大数据而使用的:

#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))

而在内核中,其实也有个限制,是4M,不过由于APP中已经限制了不到1M,这里的限制似乎也没多大用途:

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
    struct vm_struct *area;
    struct binder_proc *proc = filp->private_data;
    const char *failure_string;
    struct binder_buffer *buffer;
    //限制不能超过4M
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;
    。。。
    }

有个特殊的进程ServiceManager进程,它为自己申请的Binder内核空间是128K,这个同ServiceManager的用途是分不开的,ServcieManager主要面向系统Service,只是简单的提供一些addServcie,getService的功能,不涉及多大的数据传输,因此不需要申请多大的内存:

int main(int argc, char **argv)
{
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;

        // 仅仅申请了128k
    bs = binder_open(128*1024);
 if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)n", strerror(errno));
        return -1;
    }

    svcmgr_handle = svcmgr;
    binder_loop(bs, svcmgr_handler);
    return 0;
}   
2、IBinder

类型:接口
作用:Java层的IBinder类,定义了Java层Binder通信的一些规则;提供了transact方法来调用远程服务

比如我们知道的Binder类就是实现了IBinder

系统服务与bindService等启动的服务的区别

服务可分为系统服务与普通服务,系统服务一般是在系统启动的时候,由SystemServer进程创建并注册到ServiceManager中的。而普通服务一般是通过ActivityManagerService启动的服务,或者说通过四大组件中的Service组件启动的服务。这两种服务在实现跟使用上是有不同的,主要从以下几个方面:

  • 服务的启动方式
  • 服务的注册与管理
  • 服务的请求使用方式

首先看一下服务的启动上,系统服务一般都是SystemServer进程负责启动,比如AMS,WMS,PKMS,电源管理等,这些服务本身其实实现了Binder接口,作为Binder实体注册到ServiceManager中,被ServiceManager管理,而SystemServer进程里面会启动一些Binder线程,主要用于监听Client的请求,并分发给响应的服务实体类,可以看出,这些系统服务是位于SystemServer进程中(有例外,比如Media服务)。在来看一下bindService类型的服务,这类服务一般是通过Activity的startService或者其他context的startService启动的,这里的Service组件只是个封装,主要的是里面Binder服务实体类,这个启动过程不是ServcieManager管理的,而是通过ActivityManagerService进行管理的,同Activity管理类似。

再来看一下服务的注册与管理:系统服务一般都是通过ServiceManager的addService进行注册的,这些服务一般都是需要拥有特定的权限才能注册到ServiceManager,而bindService启动的服务可以算是注册到ActivityManagerService,只不过ActivityManagerService管理服务的方式同ServiceManager不一样,而是采用了Activity的管理模型,详细的可以自行分析

最后看一下使用方式,使用系统服务一般都是通过ServiceManager的getService得到服务的句柄,这个过程其实就是去ServiceManager中查询注册系统服务。而bindService启动的服务,主要是去ActivityManagerService中去查找相应的Service组件,最终会将Service内部Binder的句柄传给Client。

系统服务与bindService启动服务的区别.jpg

3、Binder

类型:类
作用:实现了IBinder接口,封装了JNI的实现。Java层Binder服务的基类。存在服务端的Binder对象

比如我们知道的ActivityManagerService继承自ActivityManagerNative,而ActivityManagerNative继承自Binder

Binder线程、Binder主线程、Client请求线程的概念与区别

Binder线程是执行Binder服务的载体,只对于服务端才有意义,对请求端来说,是不需要考虑Binder线程的,但Android系统的处理机制其实大部分是互为C/S的。比如APP与AMS进行交互的时候,都互为对方的C与S,这里先不讨论这个问题,先看Binder线程的概念。

Binder线程就是执行Binder实体业务的线程,一个普通线程如何才能成为Binder线程呢?很简单,只要开启一个监听Binder字符设备的Loop线程即可,在Android中有很多种方法,不过归根到底都是监听Binder,换成代码就是通过ioctl来进行监听。

拿ServerManager进程来说,其主线就是Binder线程,其做法是通过binder_loop实现不死线程:

void binder_loop(struct binder_state *bs, binder_handler func)
{
   ...
    for (;;) {
    <!--关键点1-->
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
     <!--关键点2-->
        res = binder_parse(bs, 0, readbuf, bwr.read_consumed, func);
        。。
    }
}

上面的关键代码1就是阻塞监听客户端请求,2 就是处理请求,并且这是一个死循环,不退出。再来看SystemServer进程中的线程,在Android4.3(6.0以后打代码就不一样了)中SystemSever主线程便是Binder线程,同时一个Binder主线程,Binder线程与Binder主线程的区别是:线程是否可以终止Loop,不过目前启动的Binder线程都是无法退出的,其实可以全部看做是Binder主线程,其实现原理是,在SystemServer主线程执行到最后的时候,Loop监听Binder设备,变身死循环线程,关键代码如下:

extern "C" status_t system_init()
{
    ...
    ALOGI("System server: entering thread pool.n");
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
    ALOGI("System server: exiting thread pool.n");
    return NO_ERROR;
}

ProcessState::self()->startThreadPool()是新建一个Binder主线程,而PCThreadState::self()->joinThreadPool()是将当前线程变成Binder主线程。其实startThreadPool最终也会调用joinThreadPool,看下其关键函数:

void IPCThreadState::joinThreadPool(bool isMain)
{
    ...
    status_t result;
    do {
        int32_t cmd;
        ...关键点1 
        result = talkWithDriver();
        if (result >= NO_ERROR) {
           ...关键点2 
            result = executeCommand(cmd);
        }
        // 非主线程的可以退出
        if(result == TIMED_OUT && !isMain) {
            break;
        }
        // 死循环,不完结,调用了这个,就好比是开启了Binder监听循环,
    } while (result != -ECONNREFUSED && result != -EBADF);
 }

status_t IPCThreadState::talkWithDriver(bool doReceive)
{  
    do {
        ...关键点3 
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
   }   

先看关键点1 talkWithDriver,其实质还是去掉用ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)去不断的监听Binder字符设备,获取到Client传输的数据后,再通过executeCommand去执行相应的请求,joinThreadPool是普通线程化身Binder线程最常见的方式。不信,就再看一个MediaService,看一下main_mediaserver的main函数:

int main(int argc, char** argv)
{
   。。。
        sp<ProcessState> proc(ProcessState::self());
        sp<IServiceManager> sm = defaultServiceManager();
        ALOGI("ServiceManager: %p", sm.get());
        AudioFlinger::instantiate();
        MediaPlayerService::instantiate();
        CameraService::instantiate();
        AudioPolicyService::instantiate();
        registerExtensions();
        ProcessState::self()->startThreadPool();
        IPCThreadState::self()->joinThreadPool();
    }

其实还是通过joinThreadPool变身Binder线程,至于是不是主线程,看一下下面的函数:

void IPCThreadState::joinThreadPool(bool isMain)

void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        String8 name = makeBinderThreadName();
        ALOGV("Spawning new pooled thread, name=%sn", name.string());
        sp<Thread> t = new PoolThread(isMain);
        t->run(name.string());
    }
}

其实关键就是就是传递给joinThreadPool函数的isMain是否是true,不过是否是Binder主线程并没有什么用,因为源码中并没有为这两者的不同处理留入口,感兴趣可以去查看一下binder中的TIMED_OUT。

最后来看一下普通Client的binder请求线程,比如我们APP的主线程,在startActivity请求AMS的时候,APP的主线程成其实就是Binder请求线程,在进行Binder通信的过程中,Client的Binder请求线程会一直阻塞,知道Service处理完毕返回处理结果。

郑重声明:本文版权归新匍京a奥门-最全网站手机版app官方下载所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。