前言
写完IPC的第一篇我就有点后悔了。。因为binder的水太深了,老罗写binder写了十几万字。如果深入学习会大量涉及到系统层的知识,甚至SM,Binder驱动都是用c语言写的。。最近也是看了很多大牛关于binder的文章,可以说对binder的认识又提升了一步。虽然我学的很浅,但是我尽量保证自己写的都是对的! 来一波binder的最新认识总结吧!~
上一篇文章主要是介绍一些简单的linux知识,通过AIDL中的生成代码来理解binder实现跨进程的原理。
任务:
- 补充一些Linux知识。
- binder实现跨进程通信的实现流程是怎样的
- 简单介绍binder机制
一些补充知识
1.UserSpace, KernelSpace ,用户态,内核态
用户空间和内核空间。内核空间是linux系统的核心,可以访问受保护资源和所有的硬件设备的权限。内核独立于应用程序,看起来是系统内部的一个应用。kernel有自己的保护措施,告知其他应用程序他拥有什么权限。所以在逻辑上,将kernal和应用程序抽离成两块空间。
当某个应用程序需要访问内核资源的时候,可以通过系统调用来接入内核。然后内核会来控制这个应用程序对资源的访问,防止这个程序破坏系统资源,保证安全。这时候就会称这个进程处于内核态 。当应用程序在跑自己的代码的时候,就称为用户态。
2.Binder驱动
Linux系统内部是支持socket,信号量等进程通信的,但是安卓系统在性能和安全两个方面设计了自己的进程通信方式:Binder。两个用户空间想要通信,必须通过内核空间来支持。所以安卓就是将binder驱动作为内核模块添加到Linux Kernel。Binder驱动运行在kernel空间,支持用户空间的通信,可以堪称一个桥梁,所有包含binder的数据包传输都会通过binder驱动来完成,无一例外!在binder驱动里面,binder的实体和引用是以节点(struct)的形式存在的,包括server的binder实体,clinet里面拥有的binder引用,内核的0号应用以及SM的binder实体(后两者后面会提到)。
驱动是Binder通信的核心,系统中所有的Binder实体以及每个实体在各个进程中的引用都登记在驱动中;驱动需要记录Binder引用->实体之间多对一的关系;为引用找到对应的实体;在某个进程中为实体创建或查找到对应的引用;记录Binder的归属地(位于哪个进程中);通过管理Binder的强/弱引用创建/销毁Binder实体等等。
3.ServiceManager, Binder,Clinet, Server
在Binder的机制中,SM, Clinet, Server以及上面提到的Binder驱动是很重要的四个部分,而binder就可以看成是这四个部件之间沟通的管道。但是binder在每个部件里面的形态,功能是完全不同的。宏观来看,binder可以看成是安卓跨进程通信的方式,工具,协议。微观的来看,binder可以看成各个部件里面重要的结构,类,binder可以看成binder驱动里面的红黑树数据结构,binder可以看成进程之间传输的数据包。binder作为胶水,将不同的进程粘合在一起,模糊了进程隔离。
ServiceManager是独立于client, server的系统进程。是由zygote进程fork出来的。它在IPC通信中的作用是作为"通讯录"。所有的server里面的binder实体会先在SM里面注册自己的信息,key是binder的名字(独一无二),在AIDL的生成代码里面你会看到这个玩意:
private static final java.lang.String DESCRIPTOR ="com.example.zane.ipc_test.IBookManager";
value就是这个binder的引用,这样就把这个binder的信息保存下来了。也就是说client需要通过SM,以名字来去得到server的binder引用。然后通过这个引用去操作server端的binder。这里也体现了binder中面向对象的设计理念。clinet自己并不知道自己获得的这个binder引用是真的实体还是什么假的实体,它只会去拿着自己获得的引用去操作。并且server中binder实体的引用会在SM,和所有需要跟自己通信的clinet里面存在。在这里,你可以把引用看成指针,或者代理对象。
client和server就是客户端和服务端。
在服务端:如果你熟悉AIDL进行进程间通信的流程或者看过我上篇博客,你应该对binder的存在形式有所体会。首先是一个aidl类型的接口,定义了binder的所有功能函数。并且这些功能函数需要被编号,因为服务端是通过解析客户端传递过来的数据包中的函数编号来知道自己应该去调用什么函数。在Stub类中有这么几行代码(函数编号):
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
我们知道Stub就是Binder在Server的实体,里面实现了很多函数,onTransact()就是来分析客户端的请求类型的函数,然后调用相应的函数,并且将返回结果放在数据包中返回给客户端。
在客户端:同样的,我们在客户端也需要去实现Binder,只不过这个binder是SM转发给我们的,如果客户端和服务端在同一个进程,那么就会返回binder实体,如果不在同一个进程就会返回binder的代理。由于binder代理和binder实体都是实现了AIDL接口的类。所以客户端看不出来这个binder是实体或是引用。如果你做过IPC通信,你会知道以上的过程是通过asInterface(IBiner binder)这个方法实现的。
实现流程
直接上一张我自己画的简介图!
可以很清楚的看出,binder驱动是整个流程的核心!
1.Server将自己的binder通过binder驱动在SM中进行注册。
2.binder驱动会建立一个binder实体的数据节点和实体的引用。
3.Binder驱动再把名字和引用打包发给SM。
4.Client通过binder驱动拿着他所需要的binder名字向SM请求binder。
5.SM在自己的查找表里面找到对应的引用之后再通过binder驱动返回给client。
所有的系统服务在SystemServer进程深沉之后就会被建立并且注册在ServiceManager里面,开发者也可以开发自定义的服务并且注册在ServiceManager里面成为系统级别的服务,前提是这个包含服务的应用必须是system用户并且带了system签名(系统安全),否则是不能随意注册的!
好了,再来一张我在一位大牛博客里面截下来的图片,这个流程描述就更具体了:
其实就是多了binder驱动里面的一些数据结构节点和一个叫0号引用的东西。那么这个0号引用是什么呢?我们知道SM, Client, Server都是运行在三个不同的进程的。那么第一步Server要向SM注册自己binder的信息,那么这里已经涉及到了跨进程通信了。那么这个进程通信是怎么实现的呢?通过上图可以看到,所有的Client里面的0号引用都指向了SM里面的binder。也就是说SM和其他所有Server通信的过程都是SM先通过特殊的命令在biner驱动中建立了自己binder的实体节点,并且其他所有地方的0号引用都默认留给SM的binder实体引用。
结束语
上一篇文章讲的AIDL生成代码的分析,本来准备第二篇总结一下AIDL的使用。后来通过不断的学习觉得这篇文章讲的东西更值得总结!
总之,binder要学习的东西还是很多。比如binder协议,binder传输数据包类型,binder在驱动里面的数据结构,缓存和线程池管理等等…Binder在安全性,效率性都优于Linux系统默认支持的IPC通信方式,拥有面向对象的设计原理。
未经博主同意,不得转载该篇文章