happylishang / happylishang.github.io

http://elephanty.top/
MIT License
6 stars 2 forks source link

深入理解Binder — 看书的小蜗牛 #54

Open happylishang opened 5 years ago

happylishang commented 5 years ago

https://elephanty.top//2016/02/02/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Binder/

目录 Binder概述问题引入原理ServiceManager化身大管家Service实现逻辑    – Service注册逻辑     – Service自身服务的实现     – Java层Service服务的实现与注册 Client请求的实现逻辑    – 请求ServiceManager获得Service代理     – 请求Service服务自身 Android应用层对Binder的支持AIDL机制     – Android Application天生支持binder框架原理     – AIDL及binderService实现原理 Binder面试应用Binder概述   一句话概括进程通信:进程间的数据传递。   Binder是Anroid系统里最重要的进程通信方式,很多文章会直接用代码、原理类的文字进行描述,对于接触Android与Linux不是特别深的人来说,特别晦涩难懂,经常是看了这忘了那里,其实探索Binder通信的一条核心就是:Client如何找Server,将请求发送给Server,Server再将结果返回给Client。      Binder基于OpenBinder,被引入后添加了很多Android特性,比如,在驱动层添加了ServiceManager逻辑,搭建起ServiceManger-Clien-Server框架模型。Android基于Linux内核,其进程管理模型完全沿用了Linux的进程/线程模型,进程划分为用户空间与内核空间,在用户空间,进程间是无法通信的,只有通过内核空间才能传递数据。 Binder自身的意义倾向于通信,只是进程间通信的一种方式,但是在Android系统中,Binder被提到了核心高度,Android基本可以看做基于Binder模型实现的是一种Android RPC模型(远程过程调用协议 Remote Procedure Call Protocal ),即:C/S架构。       Binder只是定义了Android通信模型,至于内部的业务实现,还是要有Server自身来实现,不要把数据传输跟业务处理弄混淆,Android只是基于Binder,搭建了一个C/S通信框架、或者说通信协议。Android基于Linux内核,在Linux中,Binder被看做一个字符设备,Binder驱动会为每个打开Binder的进程在内核里分配一块地址空间,Client向Server传递数据,其实就是将数据写道内核空间中Server的地址里面,然后通知Server去取数据。原理其实很简单,但是Google为了更加合理的使用Binder,自己进行了很多层次的封装与优化,导致代码看的昏头转向, 比较难的就是进程或者线程的挂起与唤醒以及Android CS框架。Binder常见问题–由表及里引入原理探究 ServiceManager如何管理Servers 每个Server进程在注册的时候,首先往本地进程的内核空间的Binders红黑树种插入Binder实体服务的bind_node节点,然后会在ServiceManager的进程的内核空间中为其添加引用ref结构体,ref,会保存相应的信息、名字、ptr地址等。 Client如何找到Server,并且向其发送请求Client在getService的时候,ServiceManager会找到Server的node节点,并在在Client中创建Server的bind_ref引用,Client可以在自己进程的内核空间中找到该引用,最终获取Server的bind_node节点,直接访问Server,传输数据并唤醒。 Client端,服务实体的引用bind_ref存在哪里了,与Handler的关系式怎么样的 Binder驱动会在内核空间为打开Binder设备的进程(包括Client及Server端)创建bind_proc结构体,bind_proc包含4棵红黑树:threads、bind_refs、bind_nodes、bind_desc这四棵树分别记录该进程的线程树、Binder引用树、本地Binder实体,等信息,方便本地查找。Handler其是ServiceManager为了方便客户端查找bind_ref做的一套处理,只是为了标定目标。 如何唤醒目标进程或者线程: 每个Binder进程或者线程在内核中都设置了自己的等待队列,Client将目标进程或者线程告诉Binder驱动,驱动负责唤醒挂起在等待队列上的线程或者进程。 Server如何找到返回目标进程或者线程,Client在请求的时候,会在bind_trasaction的from中添加请求端信息 如何Binder节点与ref节点的添加时机 驱动中存在一个TYPE_BINDER与TYPR_HANDLE的转换,Binder节点是Binder Server进程(一般是Native进程)在向Servicemanager注册时候添加的,而ref是Client在getService的时候添加的,并且是由ServiceManager添加的。 Binder如何实现只拷贝一次 数据从用户空间拷贝到内核中的时候,是直接拷贝到目标进程的内核空间,这个过程是在请求端线程中处理的,只不过操作对象是目标进城的内核空间。其实,内核中的bind_trasaction_data是直接在目标进程汇总分配的,由于Binder进程的Binder内存部分在内核空间跟用户空间只存在一个偏差值,用户空间不需要再次拷贝数据就可以完成访问。 Binder接收线程管理:请求发送时没有特别标记,驱动怎么判断哪些数据包该送入全局to-do队列,哪些数据包该送入特定线程的to-do队列呢?这里有两条规则:【1】 规则1:Client发给Server的请求数据包都提交到Server进程的全局to-do队列。不过有个特例,当进程P1的中的线程T1向进程P2发送请求时,驱动会先查看一下线程T1是否也正在处理来自P2某个线程请求,(尚在处理,未完成,没有发送回复),这种情况通常发生在两个进程都有Binder实体并互相对发时请求的时候。如果在进程P2中发现了这样的线程,比如说T2,就会要求T2来处理T1的这次请求。因为T2既然向T1发送了请求尚未得到返回包,说明T2肯定(或将会)阻塞在读取返回包的状态。这时候可以让T2顺便做点事情,总比等在那里闲着好。而且如果T2不是线程池中的线程还可以为线程池分担部分工。经过优化,来自T1的请求不是提交给P2的全局to-do队列,而是送入了T2的私有to-do队列。 规则2:对同步请求的返回数据包(由BC_REPLY发送的包)都发送到发起请求的线程的私有to-do队列中。如上面的例子,如果进程P1的线程T1发给进程P2的线程T2的是同步请求,那么T2返回的数据包将送进T1的私有to-do队列而不会提交到P1的全局to-do队列。 Binder Server都会在ServiceManager中注册吗? Java层的Binder实体就不会去ServiceManager,尤其是bindService这样一种,其实是ActivityManagerService充当了ServiceManager的角色。 IPCThreadState::joinThreadPool的真正意义是什么? 可以理解加入该进程内核的线程池,进行循环,多个线程开启,其实一个就可以,怕处理不过来,可以开启多个线程处理起来,其实跟线程池类似。 为何ServiceManager启动的时候没有采用joinThreadPool,而是自己通过for循环来实现自己Loop 因为Binder环境还没准备好啊,所以,自己控制,所以也咩有talkWithDriver那套逻辑,不用onTransact实现。因为前文也说过,Binder为Android做了深层的改变,其实在驱动里面ServiceManager也是特殊对待的,在binder_transaction中,会对目标是ServiceManager的请求进行特殊处理。 ServiceManager启动为管家ServiceManager是由谁启动的?在应用层ServiceManager的使用一般如下:(基于源码4.3)public abstract Object getSystemService(@ServiceName @NonNull String name);那么ServiceManager是什么时候启动的呢?ServiceManager代码位于/frameworks/native/cmds/servicemanager/中,在init.rc中可以看到 service servicemanager /system/bin/servicemanager class core user system group system critical onrestart restart zygote onrestart restart media onrestart restart surfaceflinger onrestart restart drm所以,ServiceManager是有init进程启动的,在Linux系统中init是一切用户空间进程的父进程,ServiceManager因此不依赖与任何Android服务进程。完全由系统的init进程加载进来。ServiceManager如何成为系统Server的大管家init进程启动的servicemanager的入口是service_manager.c的main函数: int main(int argc, char *argv) { struct binder_state bs; void svcmgr = BINDER_SERVICE_MANAGER; bs = binder_open(1281024); if (binder_become_context_manager(bs)) { LOGE(