深入理解Binder
Binder如何精确制导,找到Binder实体,并唤醒进程或者线程?
Binder实体服务分两种:
- 一是通过addService注册到ServiceManager中的服务,比如ActivityManagerService、PackageManagerService、PowerManagerService等,这些一般都是系统服务。
- 另一种是通过bindService拉起的一些服务,一般是开发者自己实现的服务。
理解Binder定向制导关键是理解Binder四棵红黑树,要使用Service,就需要通过getService向ServiceManager请求服务。
Client如何才能访问Service?
addService流程:
- Service通过addService将binder实体注册到ServiceManager,getService向ServiceManager请求服务。
- 请求服务时,ServiceManager会将服务相关的信息存储到自己的进程Service列表中区。
- 同时在ServiceManager进程的binder_ref红黑树中为Service添加binder_ref节点,ServiceManager就能获取Service的Binder实体信息。
getService流程:
- 当Client通过getService向ServiceManager请求该服务时,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为什么只需要一次拷贝?
主要涉及两点:
- Binder的map函数,会将内核空间直接和用户空间对应,用户空间可以直接访问内核空间的数据。
- A进程的数据会被直接拷贝到B进程的内核空间(一次拷贝)。
当APP启动时,就开启地址映射,但并未涉及到数据的拷贝。
1 | static void binder_transaction(struct binder_proc *proc, |
当数据从用户空间拷贝的内核空间的时候,是直接从当前进程的用户空间拷贝到目标进程的内核空间,这个过程在请求端线程中处理的,操作对象是目标进程的内核空间。
一次拷贝的关键点:
- 进程A直接写入进程B涉及到的内核空间。
- 进程B涉及到的内核空间和本身的用户空间做了内存映射,当内核空间改变时,用户空间也同步进行改变。
Binder传输数据的大小限制
比如在Activity之间传输BitMap的时候,如果Bitmap过大,就会引起问题,这都是和Binder传输数据大小的限制有关系。上一个问题已经说过了,mmap函数会为Binder数据传递映射一块虚拟地址,而这块地址是有大小限制的,不同进程可能还不大一样。
Binder本身就是为了进程间频繁而灵活的通信所设计的,并不是为了拷贝大数据而使用的。
普通由Zygote孵化而来的用户进程(比如App进程),所映射的Binder内存大小是不到1M的。
1
#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))
内核中限制是4M
- 有个特殊的ServiceManager进程,只申请了128k,因为只提供addService,getService等功能,不涉及多大的数据传输,因此无需申请多大的内存。
系统服务和bindService等启动的服务区别
服务可分为系统服务和普通服务:
- 系统服务:在系统启动时,由SystemServer进程创建并注册到ServiceManager中的。而普通服务一般是通过ActivityManagerService启动的服务。
- 普通服务:通过ActivityManagerService启动的服务,或者通过四大组件的Service组件启动的服务。
这两种服务在使用上是有所不同的:
- 服务的启动方式
- 服务的注册与管理
- 服务的请求使用方式
启动方式
- 系统服务:一般由SystemServer进程负责启动,比如AMS,WMS,PKMS等,这个启动过程是通过ServiceManager管理
- 普通服务:这类服务一般是通过Activity的startService或者其他context的startService启动的,这里的Service组件只是个封装,主要的是里面Binder服务实体类,这个启动过程是通过ActivityManagerService进行管理的,同Activity管理类似。
注册与管理
- 系统服务:通过ServiceManager的addService进行注册,这些服务一般需要拥有特定的权限才能注册到ServiceManager,bindService启动的服务可以注册到ActivityManagerService。
- 普通服务:bindService启动的服务可以算是注册到ActivityManagerService,只不过是通过ActivityManagerService进行管理,同Activity管理类似。
使用方式
- 系统服务:通过ServiceManager的getService得到服务的句柄,这个过程是去ServiceManager中查询注册系统服务。
- 普通服务:去AMS中查找相应的Service组件,最终会将Service内部Binder的句柄传给Client。
Binder线程,Binder主线程,Client请求线程的概念和区别
Binder线程是执行Binder服务的载体,只对于服务端才有意义。Binder线程就是执行Binder实体。
普通线程如何才能成为Binder线程?
只要开启一个监听Binder字符设备的Loop线程即可。在Android中有很多种方法,总之都是监听Binder,换成代码是通过ioctl来进行监听。
1 | void binder_loop(struct binder_state *bs, binder_handler func) |
关键点:
- 阻塞监听客户端请求
- 处理请求,一个死循环,不退出
实现原理:在SystemServer主线程执行到最后的时候,Loop监听Binder设备,变身死循环线程。
1 | extern "C" status_t system_init() |
Binder请求的同步与异步
Binder对Client端同步,Service端异步
Client在请求Server端服务的过程中,是需要返回结果的,即时看不到返回数据,还是会有成功和失败的处理结果返回给Client,这就是说Client端是同步的。
在服务端被唤醒后,就去处理请求,处理结束后,服务端将结果返回给正在等待的Client线程,将结果写入到Client内核空间后,服务端就会直接返回了,不会再等待Client端确认,这就是所说的服务端是异步的。
请求同步的例子是,Android6.0之前,国产ROM权限的申请是同步的,在申请权限的时候线程会阻塞,ROM防止ANR,会给权限申请设置一个倒计时,到了时间,就给个默认操作。(这个逻辑并没有在正式版生效,所以我们看到在6.0之前并没有动态申请的逻辑)
Android APP进程天生支持Binder通信的原理是什么?
- ProcessState::self()函数会调用open()打开/dev/binder设备,Client能通过Binder进行远程通信
- 其次proc->startThreadPool()负责新建一个binder线程,监听Binder设备。
Android APP有多少Binder线程?
Binder线程的数目不是固定的
驱动会根据目标进程中是否存在足够多的Binder线程来告诉进程是不是要新建Binder线程。
也就是说,在不够用的时候,会新建Binder线程。
但是默认的Binder线程数量是有上限的,默认为15个。
具体来看,新建的条件如下:
1 | if(proc->requested_threads + proc->ready_threads == 0 && |
关键点:
- proc->requested_threads + proc->ready_threads == 0 :如果目前还没申请新建Binder线程,并且proc->ready_threads空闲Binder线程也是0,就需要新建一个Binder线程,其实就是为了保证有至少有一个空闲的线程。
- proc->requested_threads_started < proc->max_threads:目前启动的普通Binder线程数requested_threads_started还没达到上限(默认APP进程是15)
- 如果没有Client请求服务,就保持线程数不变,减少资源浪费,需要的时候再分配新线程。
- 有请求的情况下,保证至少有一个空闲线程时给Client端,以提高Server端的响应速度。
Client端线程睡眠在哪个队列上,唤醒Server端哪个等待队列上的线程
发送端线程睡眠在哪个队列上?
发送端线程一定睡眠在自己binder_thread的等待队列上,并且,该队列上有且只有自己一个睡眠线程
在Binder驱动去唤醒线程的时候,唤醒的是哪个等待队列上的线程?
栈顶的一定先于栈内执行。
Binder协议中BC与BR的区别
BC与BR主要是标志数据及Transaction流向,其中
- BC是从用户空间流向内核,比如Client向Sever发送请求的时候,用的是BC_TRANSACTION
- BR是从内核流向用户空间,当数据被写入到目标进程之后,target_proc所在的进程被唤醒,在内核空间中,会将BC转换为BR,并将数据与操作传递该用户空间。
Binder的封装与解封装流程
Binder驱动传递数据的释放(释放时机)
在Binder通信的过程中,数据是从发起通信进程的用户空间直接写到目标进程内核空间,而这部分数据是直接映射到用户空间,必须等用户空间使用完数据才能释放,Binder通信中内核数据的释放时机应该是用户空间控制的。
执行这个函数的命令是BC_FREE_BUFFER。
上层用户空间的入口是IPCThreadState::freeBuffer。
简单的Binder通信C/S模型
很多文章将Binder框架定义了四个角色:Server,Client,ServiceManager、以及Binder驱动,但这容易将人引导到歧途:好像所有的Binder服务都需要去ServiceManager去注册才能使用,其实不是这样。例如,平时APP开发通过bindService启动的服务,以及有些自己定义的AIDL远程调用,都不一定都ServiceManager注册这条路,个人理解:ServiceManager主要功能是:管理系统服务,比如AMS、WMS、PKMS服务等,而APP通过的bindService启动的Binder服务其实是由SystemServer的ActivityManagerService负责管理。这篇主要关注Android APP Java层Binder通信一些奇葩点:
- ServiceManager addService的限制(并非服务都能使用ServiceManager的addService)
- bindService启动Service与Binder服务实体的流程
- Java层Binder实体与与BinderProxy是如何实例化及使用的,与Native层的关系是怎样的
- Parcel readStrongBinder与writeStrongBinder的原理(首先两端知晓)
- 数据传递参数AIDL单向与双向(inout)
ServiceManager addService的限制–并非服务都能通过addService添加到ServiceManager
大部分系统服务都是由SystemServer进程添加到ServiceManager中去的,在这个过程中,是需要校验权限的。
1 | int svc_can_register(unsigned uid, uint16_t *name) |
可以看到,普通的进程是没有权限注册到ServiceManager中的。那么APP平时通过bindService启动的服务怎么注册于查询的呢?接管这个任务的就是SystemServer的ActivityManagerService。
bindService启动Service与Binder服务实体的流程(ActivityManagerService)
- bindService的框架
- binder服务实例化和转化
- 业务逻辑的唤醒
- 请求代理的转化和唤醒
按步骤如下:
- Activity调用bindService:通过Binder通知ActivityManagerService,要启动哪个Service
- ActivityManagerService创建ServiceRecord,并利用ApplicationThreadProxy回调,通知APP新建并启动Service启动起来
- ActivityManagerService把Service启动起来后,继续通过ApplicationThreadProxy,通知APP,bindService,其实就是让Service返回一个Binder对象给ActivityManagerService,以便AMS传递给Client
- ActivityManagerService把从Service处得到这个Binder对象传给Activity,这里是通过IServiceConnection binder实现。
- Activity被唤醒后通过Binder Stub的asInterface函数将Binder转换为代理Proxy,完成业务代理的转换,之后就能利用Proxy进行通信了。
bindService和startService的区别
- 生命周期上的区别
- bindService比startService多了一套Binder通信,多了一个C/S通信的建立流程。