Android8.0.0-r4的Binder进程间通信机制
Binder是Android系统中进程间通讯(IPC)的一种方式,也是Android系统中最重要的特性之一。Android中的四大组件Activity,Service,Broadcast,ContentProvider,不同的App等都运行在不同的进程中,它是这些进程间通讯的桥梁。正如其名“粘合剂”一样,它把系统中各个组件粘合到了一起,是各个组件的桥梁。
1. Binder 架构
Binder 通信采用 C/S 架构,从组件视角来说,包含 Client、 Server、 ServiceManager 以及 Binder 驱动,其中 ServiceManager 用于管理系统中的各种服务。
Binder 在 framework 层进行了封装,通过 JNI 技术调用 Native(C/C++)层的 Binder 架构。
Binder 在 Native 层以 ioctl 的方式与 Binder 驱动通讯。
2. Binder 机制
首先需要注册服务端,只有注册了服务端,客户端才有通讯的目标,服务端通过 ServiceManager 注册服务,注册的过程就是向 Binder 驱动的全局链表 binder_procs 中插入服务端的信息(binder_proc 结构体,每个 binder_proc 结构体中都有 todo 任务队列),然后向 ServiceManager 的 svcinfo 列表中缓存一下注册的服务。
有了服务端,客户端就可以跟服务端通讯了,通讯之前需要先获取到服务,拿到服务的代理,也可以理解为引用。比如下面的代码:
//获取WindowManager服务引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);
//获取PowerManager服务引用
owerManager mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
有了服务端的引用就可以向服务端发送请求了,通过 BinderProxy 将请求参数发送给 ServiceManager,通过共享内存的方式使用内核方法 copy_from_user() 将参数先拷贝到内核空间,这时客户端进入等待状态,然后 Binder 驱动向服务端的 todo 队列里面插入一条事务,执行完之后把执行结果通过 copy_to_user() 将内核的结果拷贝到用户空间(这里只是执行了拷贝命令,并没有拷贝数据,binder只进行一次拷贝),唤醒等待的客户端并把结果响应回来,这样就完成了一次通讯。
3、Binder内核驱动程序
用户空间/内核空间
Kernel space 是 Linux 内核的运行空间,可以执行任意命令,调用系统的一切资源。
User space是用户程序的运行空间,只能执行简单的运算,不能直接调用系统资源,必须通过系统接口(又称 system call),才能向内核发出指令。
用户空间中 binder_open(), binder_mmap(), binder_ioctl() 这些方法通过 system call 来调用内核空间 Binder 驱动中的方法。内核空间与用户空间共享内存通过 copy_from_user(), copy_to_user() 内核方法来完成用户空间与内核空间内存的数据传输。 Binder驱动中有一个全局的 binder_procs 链表保存了服务端的进程信息。
为了安全,用户空间/内核空间是隔离的,即使用户的程序崩溃了,内核也不受影响。
3.1 Binder内核驱动程序
Binder内核驱动程序初始化:
Binder初始化做的事情就是在设备上创建了一个目录,一个文件
3.1.1 /proc/binder/proc目录
每一个使用了Binder进程间通信机制的进程在该目录下都对应有一个文件,这些文件以进程ID来命名,通过他们就可以读取到各个进程的Binder线程池、Binder实体对象、Binder引用对象以及内核缓冲区的信息
3.1.2 /dev/binder设备文件
这个设备文件的操作方法列表是由全局变量binder_fops指定的,在设备文件里有操作方法:
打开方法binder_open
内存映射binder_mmap
IO控制binder_ioctl
3.1.2.1 打开方法binder_open
1、一个进程在使用Binder进程间通信机制之前,首先就会调用binder_open来打开设备文件/dev/binder来获得一个文件描述,然后才能通过这个文件描述和Binder驱动程序交互,继而执行Binder进程间通信。
2、Binder驱动程序会在内核中创建一个binder_proc结构体proc。结构体大家当对象理解就好了。并将proc加入到一个hash队列binder_procs中,Binder驱动程序将所有打开了设备文件/dev/binder的进程都加入到全局hash队列binder_procs中,通过遍历这个hash队列就知道系统当前有多少个进程在使用Binder进程间通信机制。
3、文件描述和结构体proc关联在一起,使用文件描述作为参数调用mmap和ioctl,内核就能找到proc结构体。
3.1.2.2 内存映射binder_mmap
1、进程打开了设备文件/dev/binder之后,还必须要调用函数mmap把这个设备文件映射到进程的地址空间,映射到进程的地址空间,映射到进程的地址空间。
2、为进程分配内核缓冲区,以便用来传输进程间通信的数据。
内核缓冲区:当进程调用函数mmap将设备文件/dev/binder映射到自己的地址空间时,就会分配内核缓冲区,Binder驱动程序为进程分配的内核缓冲区有两个地址,其中一个是用户空间地址,由参数vma所指向的一个vm_area_struct结构体来描述;另一个地址是内核空间地址,由变量area所指向的一个vm_struct结构体来描述。进程通过用户空间地址来访问这块内存缓冲区的内容,而Binder驱动程序通过内核空间地址来访问这块内核缓冲区内容,由于它们是连续的,并且起始值相差一个固定值,因此,只要知道其中一个地址,就可以方便地计算另外一个地址。
3、将内核缓冲区的信息和binder_proc关联在一起
3.1.2.3 系统调用IO控制binder_ioctl
1、系统调用:进程可以通过系统调用访问内核。
2、IO命令控制:进程与内核间的很多协议通信传输就是依靠这个方法的case来进行相应的处理
4、Binder进程间通信库
Android系统在应用程序框架层中将各种Binder驱动程序操作封装成一个Binder库,这样进程就可以方便地调用Binder库提供的接口来实现进程间通信。Android提供了很多的方法,其中基于Binder原理实现的进程间通信方式有Content Provider、Messenger、AIDL等,其中Messenger是基于AIDL的封装,Content Provider和AIDL底层都是Binder。
模板类BnIterface
模板类BnIterface继承自BBinder类,BBinder类有两个重要的成员函数transact和OnTransact,当一个Binder代理对象通过Binder驱动程序向一个Binder本地对象发出一个进程间通信请求时,Binder驱动程序就会调用Binder本地对象的成员函数transact来处理该请求。OnTransact是由BBinder的子类来实现的,它就是负责分发和业务相关的进程间通信请求,比如你要提供什么服务,服务里想做些什么,就是在这里写逻辑的。BBinder类又继承了IBinder类,而后者有继承了RefBase类。
模板类BpInterface
模板类BpInterface继承自BpRefBase类,BprefBase类也继承了RefBase类,BpRefBase类有一个重要的成员变量mRemote,它指向一个BpBinder对象(Binder代理对象),可以用通过成员函数remote来获取。
BpBinder类
BpBinder实现了BpRefBase类的进程间通信接口,BpBinder成员变量mHandle是一个整数,表示一个Client组件的句柄值,每一个Client组件在Binder驱动程序中都对应一个Binder引用对象,而每一个Binder引用对象都有一个句柄值,这里又出现一个句柄值概念,句柄值是和内存地址息息相关的,通过句柄值就可以在列表中找到Binder引用对象。其中Client组件就是通过这个句柄值来和Binder驱动程序中的Binder引用对象建立对应关系的。BpBinder类成员函数transact用来发送进程间通信请求。
Binder线程池
每一个使用了Binder进程间通信机制的进程都有一个Binder线程池们用来处理进程间通信请求,这个Binder线程池由Binder驱动程序来维护,还记的之前打开/dev/binder设备文件创建的结构体binder_proc吗,它的成员变量threads就是一个红黑树的根节点,它以线程ID作为关键字来组织一个进程的Binder线程池。进程可以调用ioctl将一个线程注册到Binder驱动中。
IPCThreadState类
无论是BBinder类(Binder本地对象),还是BpBinder类(Binder代理对象),它们都是通过IPCThreadState类来和Binder驱动程序交互的,对于每一个Binder线程来说,它内部都有一个IPCThreadState对象,IPCThreadState对象我们可以IPCThreadState的静态成员函数self来获取,并且调用它的成员函数transact(臭不要脸起一样的名字)来和Binder驱动程序交互,在IPCThreadState类的成员函数transact内部,与Binder驱动程序的交互操作又是通过调用成员函数talkWithDriver来实现的,OK终于知道进程是怎么和内核中Binder驱动程序交互的。
ProcessState类
IPCThreadState类有一个成员变量mProcess,它指向一个ProcessState对象。对于每一个使用了Binder进程间通信机制的进程来说,它内部都有一个ProcessState对象,它负责初始化设备,即打开/dev/binder设备文件,以及将设备文件/dev/binder映射到进程的地址空间。OK我们又找到了打开设备文件和进行内存映射的类。ProcessState对象在进程范围内是唯一的,不像IPCThreadState对象每个Binder线程内部都有一个,因此每个Binder线程都可以通过它来和Binder驱动程序建立连接。
Service组件(Server端)和Client组件(Client端)分别使用模板类BnIterface和BpInterface来描述,Bn是Binder native的缩写,Bp是Binder Proxy的缩写,前者称为Binder本地对象,后者称为Binder代理对象,实际上进程间通信就是通过Binder代理对象发送进程间通信请求,通过Binder驱动等一系列操作,最后由Binder本地对象来处理请求,完成进程间通信。在使用Binder库开发Service组件和Client组件时,除了要定义Service组件接口之外,还必须要实现一个Binder本地对象和一个Binder代理对象,而如何获取Binder代理对象或者注册一个Binder本地对象就需要Service Manager的协助
Binder本地对象、Binder代理对象都是在用户空间中的,也就是C/S端进程中,也说了实际上进程间通信就是通过Binder代理对象发送进程间通信请求,通过Binder驱动等一系列操作,最后由Binder本地对象来处理请求,完成进程间通信。
Binder引用对象、Binder实体对象都是位于Binder驱动程序中的其实就是C语言写的数据结构,Binder代理对象对应的就是Binder引用对象,Binder本地对象对应的就是Binder实体对象,而Binder引用对象,引用的就是Binder实体对象,Binder实体对象中有Binder引用对象的列表,为什么是对应列表呢?因为Binder进程间通信是支持并发的,同时一个Server端是可以向多个Client端提供服务的,所以用一个列表来保存Client端Binder代理对象对应的Binder本地对象,Binder引用对象有又相应的句柄值。
5、Binder 进程与线程
对于底层Binder驱动,通过 binder_procs 链表记录所有创建的 binder_proc 结构体,binder 驱动层的每一个 binder_proc 结构体都与用户空间的一个用于 binder 通信的进程一一对应,且每个进程有且只有一个 ProcessState 对象,这是通过单例模式来保证的。在每个进程中可以有很多个线程,每个线程对应一个 IPCThreadState 对象,IPCThreadState 对象也是单例模式,即一个线程对应一个 IPCThreadState 对象,在 Binder 驱动层也有与之相对应的结构,那就是 Binder_thread 结构体。在 binder_proc 结构体中通过成员变量 rb_root threads,来记录当前进程内所有的 binder_thread。
Binder 线程池:每个 Server 进程在启动时创建一个 binder 线程池,并向其中注册一个 Binder 线程;之后 Server 进程也可以向 binder 线程池注册新的线程,或者 Binder 驱动在探测到没有空闲 binder 线程时主动向 Server 进程注册新的的 binder 线程。对于一个 Server 进程有一个最大 Binder 线程数限制,默认为16个 binder 线程,例如 Android 的 system_server 进程就存在16个线程。对于所有 Client 端进程的 binder 请求都是交由 Server 端进程的 binder 线程来处理的。
6、Service Manager上下文管理者
ServiceManager 是由init进程负责启动的,init进程是Linux的第一个进程,init 进程解析 init.rc 文件调用 service_manager.c 中的 main() 方法入口启动的。native 层有一个 binder.c 封装了一些与 Binder 驱动交互的方法。
![](https://i-blog.csdnimg.cn/blog_migrate/fbf345b49c578fb87dd0ed751822511d.jpeg)
ServiceManager 的启动分为三步,首先打开驱动创建全局链表 binder_procs,然后将自己当前进程信息保存到 binder_procs 链表,最后开启 loop 不断的处理共享内存中的数据,并处理 BR_xxx 命令(ioctl 的命令,BR 可以理解为 binder reply 驱动处理完的响应)
6.1 打开/dev/binder设备文件、映射到本进程的地址空间
打开/dev/binder设备文件、映射到本进程的地址空间,获得文件描述用来和驱动程序交互用,Binder驱动程序分配内核缓冲区,创建binder_proc结构体等。
6.2 注册成为管理者
注册成为管理者,Service Manager想注册成为管理者,就必须要通过IO控制命令将自己注册到Binder驱动程序,IO控制命令可以理解成进程和内核的交互方法,既然Service Manager是一个特殊的Server端,如果想成为Server那么它就需要有一个自己Binder本地对象,我们知道创建对象在内存中都是有一个对应的内存地址值的,但是Service Manager的Binder本地对象是一个虚拟对象,它的地址值为0。而Binder驱动程序就是通过binder_ioctl函数来处理IO控制命令的。简单的分析下注册流程:
首先,第一步为ServiceManager创建的结构体binder_proc:
binder_proc中的threads是对应这Binder线程池的(threads红黑树,以线程PID为关键字来组织),Binder线程池中的线程就是处理和Binder驱动程序交互的,而描述这个Binder线程的结构体就是binder_thread,Binder驱动程序会查看这个threads有没有没对应的binder_thread结构体,因为ServiceManager是进行初始化注册,所以没有对应的结构体,Binder驱动程序就为Service Manager创建一个这样的结构体,当前线程就是Service Manager的主线程。
其次,Binder驱动程序会根据ServiceManager的Binder本地对象(地址为0的虚拟对象),判读是否存在对应的Binder实体对象,还是因为进行初始化注册,没有对应的实体对象,就为其建一个Binder实体对象,保存在全局变量binder_context_mgr_node中。最后,将这个新创建的实体对象加入结构体binder_proc的nodes成员变量里,然后继续返回用户空间,开始开启循环。
总结一下就是,让驱动程序根据IO控制命令弄出Binder线程,Binder实体对象,然后和binder_proc这个结构体的相关成员变量关联起来。
6.3 循环等待Client进程请求
出现一个新的结构体binder_writr_read,binder_writr_read就包含输入缓存区和输出缓冲区,循环依然使用的是IO控制命令通过Binder线程(binder_thread结构体)以及inder_writr_read来和Binder驱动程序交互,依然使用的是binder_ioctl函数来处理IO控制命令,注册的时候我们已经创建了binder_thread结构体,所有就使用这个结构体,使用copy_from_use将从用户空间(Service Manager)传进来的结构体binder_writr_read拷贝进来。既然是交互,那么就有接收和返回,当进程同过IO控制命令传递binder_writr_read数据的时候,如何判断是让Binder驱动程序读数据,还是让Binder驱动程序写入数据返回给用户空间呢,这里就是通过判断binder_writr_read结构体的缓冲区,如果输入缓存区长度大于0,Binder驱动程序就使用binder_thread_write来处理数据,如果输出缓存区长度大于0,Binder驱动程序就使用binder_thread_read将需要处理的工作项写入到输出缓冲区,即将相应的返回协议写入该缓冲区中,以便进程在用户空间处理。工作项在内核中使用binder_transaction结构体来描述,工作项对应的数据就是binder_transaction_data结构。
总结,循环我们可以想象我们工作中使用的轮询操作,就是不停的发送数据给Binder驱动程序,然后驱动程序会根据发送来的数据状态是将数据copy下来,还是去找看看有没有你需要做的工作返回给你做,总之一句话就是循环等待Client进程请求。
7、ServiceManager代理对象的获取
ServiceManager成为管理者了,也开启循环了,它已经准备好的一切,就等为Client进程服务了。要想使用其他进程提供的服务,就需要拿到Server端的Binder代理对象,要使用ServiceManager的服务,当然也要获取它Binder代理对象,然后通过代理对象找到Binder引用对象然后找到Binder实体对象,然后最终交给Binder本地对象来处理。但是Service Manager的代理对象比较特殊,它省去了和Binder内核驱动的交互,提供了直接获取ServiceManager的Binder代理对象的函数defaultServiceManager。
进程通过使用Binder库的defaultServiceManager函数获取ServiceManager的过程也比较简单,
1.判断进程中有没有ServiceManager代理对象,有就直接用还获取这个代理对象,如果没有继续。
2.这个部分总共分为3小部分,获取Service Manager代理对象的过程,实际上就是进程间通信的过程,Client端和Service Manager进程间通信,凡是需要进程间通信的,都需要打开和映射/dev/binder设备文件这个过程,从上文知道干这件事的类就是ProcessState对象,然后在使用句柄值0创建一个Binder代理对象,接着将这个Binder代理对象调用模板函数封装成一个Service Manager代理对象。
对于一般的Service组件来说,Client进程首先要通过Binder驱动程序来获得它的一个句柄值,然后根据这个句柄值创建Binder代理对象,最后将这个Binder对象封装成一个实现特定接口的代理对象。因为ServiceManager的句柄值恒为0,所以省略了和Binder驱动程序的交互过程。对于非Servicer Manager来说,获取句柄值就需要和内核进行交互。
7.1 Server端,服务的提供者
服务不是你想提,想提就能提,要想成为Server端,先得将Service组件注册到ServiceManager中,接着启动Binder线程池来等待和处理Client进程的通信请求。
注册服务就是Binder库提供的addService函数。首先如果想成为Server端,就得先准备Binder本地对象,数据,Service名称,偏移数组(Binder本地对象的地址值)等,将这部分东西通过一个Parcel的对象进行多次封装,使用Service Manager代理对象将Parcel封装的数据(最终转换成flat_binder_object结构体)发送给Binder内核驱动程序,内核驱动程序就会根据ServiceManager代理对象的句柄值0,找到了全局变量binder_context_mgr_node,最后定位到了binder_proc结构体和内核缓冲区,将数据拷贝到ServiceManager对应的内核缓冲区处理数据,同时也可以根据ServiceManager代理对象传入的数据分析出注册Service的进程对应的binder_proc结构体,由于是首次注册该binder_proc结构体的nodes对象一定为null,Binder驱动程序就会为创建一个Binder实体对象关联到nodes,然后再看它有没有引用对象,肯定没有啊实体都没有,哪里来的引用,接着创建引用对象(binder_ref),然后将这个对象(binder_ref)插入到Service Manager进程的binder_proc对象的一个链表中。最后启动Binder线程池将当前线程加入到Binder线程池中。
7.2 Client端,服务的请求者
Client端想要使用服务,当然需要获取Server端的代理对象了,只有获取到这个代理对象,才能通过代理对象来让Server端的本地对象服务。Server端的代理对象的换取关键就是句柄值的获取,Service Manager代理对象的句柄值恒为0,直接用0去获取。
Server端的Binder引用对象(desc变量就是分配的句柄值)最终通过一系列的交互插入到ServiceManager的svclist列表中,所以Client端想要获取Server端的代理对象就需要去ServiceManager中去找,而去Service Manager找的过程又是一个进程间通信过程,还是要获得Service Manager的代理对象。
Binder进程间通信库提供了defaultServiceManager()函数来获取ServiceManager的代理对象,提供了addServicer()函数来注册成为Server端,那么也同时提供了一个函数getService()来获取Server端的代理对象,在调用getService()来获取Server端的代理对象时,需要指定这个Service组件的名称,内核驱动最终联系到Service Manager进程,然后ServiceManager进程根据名称数据,在和内核交互,从全局变量svclist中查找,找到指定的Server端句柄,内核根据句柄找到相应的引用对象,然后在根据引用对象找到实体对象,重新创建一个引用对象供客户端转换成代理对象
8. ServiceManager 注册服务
注册 MediaPlayerService 服务端,通过 ServiceManager 的 addService() 方法来注册服务。
首先 ServiceManager 向 Binder 驱动发送 BC_TRANSACTION 命令(ioctl 的命令,BC 可以理解为 binder client 客户端发过来的请求命令)携带 ADD_SERVICE_TRANSACTION 命令,同时注册服务的线程进入等待状态 waitForResponse()。 Binder 驱动收到请求命令向 ServiceManager 的 todo 队列里面添加一条注册服务的事务。事务的任务就是创建服务端进程 binder_node 信息并插入到 binder_procs 链表中。
事务处理完之后发送 BR_TRANSACTION 命令,ServiceManager 收到命令后向 svcinfo 列表中添加已经注册的服务。最后发送 BR_REPLY 命令唤醒等待的线程,通知注册成功。
9. ServiceManager 获取服务
获取服务的过程与注册类似,相反的过程。通过 ServiceManager 的 getService() 方法来注册服务。
首先 ServiceManager 向 Binder 驱动发送 BC_TRANSACTION 命令携带 CHECK_SERVICE_TRANSACTION 命令,同时获取服务的线程进入等待状态 waitForResponse()。
Binder 驱动收到请求命令向 ServiceManager 的发送 BC_TRANSACTION 查询已注册的服务,查询到直接响应 BR_REPLY 唤醒等待的线程。若查询不到将与 binder_procs 链表中的服务进行一次通讯再响应。
10. 一次完整通讯
在使用 Binder 时基本都是调用 framework 层封装好的方法,AIDL 就是 framework 层提供的傻瓜式是使用方式。假设服务已经注册完,客户端怎么执行服务端的方法:
首先通过 ServiceManager 获取到服务端的 BinderProxy 代理对象,通过调用 BinderProxy 将参数,方法标识(例如:TRANSACTION_test,AIDL中自动生成)传给 ServiceManager,同时客户端线程进入等待状态。
ServiceManager 将用户空间的参数等请求数据复制到内核空间,并向服务端插入一条执行执行方法的事务。事务执行完通知 ServiceManager 将执行结果从内核空间复制到用户空间,并唤醒等待的线程,响应结果,通讯结束
参考:
https://blog.csdn.net/freekiteyu/article/details/70082302
https://blog.csdn.net/omyrobin/article/details/78832654
https://blog.csdn.net/ma969070578/article/details/45871323/