很全:亲,耐心看完会有收获的
1.Binder通信机制介绍
这篇文章会先对比Binder机制与Linux的通信机制的差别,了解为什么Android会另起炉灶,采用Binder。接着,会根据Binder的机制,去理解什么是Service Manager,在C/S模型中扮演什么角色。最后,会从一次完整的通信活动中,去理解Binder通信的过程。
1.1 Android与Linux通信机制的比较
虽然Android继承使用Linux的内核,但Linux与Android的通信机制不同。
在Linux中使用的IPC通信机制如下:
- 传统IPC:无名pipe, signal, trace, 有名管道
- AT&T Unix 系统V:共享内存,信号灯,消息队列
- BSD Unix:Socket
而在Android中,并没有使用这些,取而代之的是Binder机制。Binder机制是采用OpenBinder演化而来,在Android中使用它的原因如下:
- 采用C/S的通信模式。而在linux通信机制中,目前只有socket支持C/S的通信模式,但socket有其劣势,具体参看第二条。
- 有更好的传输性能。对比于Linux的通信机制,
- socket:是一个通用接口,导致其传输效率低,开销大;
- 管道和消息队列:因为采用存储转发方式,所以至少需要拷贝2次数据,效率低;
- 共享内存:虽然在传输时没有拷贝数据,但其控制机制复杂(比如跨进程通信时,需获取对方进程的pid,得多种机制协同操作)。
- 安全性更高。Linux的IPC机制在本身的实现中,并没有安全措施,得依赖上层协议来进行安全控制。而Binder机制的UID/PID是由Binder机制本身在内核空间添加身份标识,安全性高;并且Binder可以建立私有通道,这是linux的通信机制所无法实现的(Linux访问的接入点是开放的)。
综上所述,Android采用Binder机制是有道理的。既然Binder机制这么多优点,那么我们接下来看看它是怎样通过C/S模型来实现的。
1.2 Binder在Service服务中的作用
在android中,有很多Service都是通过binder来通信的,比如MediaServer旗下包含了众多service:
- AudioFlinger 音频核心服务
- AudioPolicyService:音频策略相关的重要服务
- MediaPlayerService:多媒体系统中的重要服务
- CameraService:有关摄像/照相的重要服务
Binder在C/S中的流程如下:
- Server注册服务。Server作为众多Service的拥有者,当它想向Client提供服务时,得先去Service Manager(以后缩写成SM)那儿注册自己的服务。Server可以向SM注册一个或多个服务。
- Client申请服务。Client作为Service的使用者,当它想使用服务时,得向SM申请自己所需要的服务。Client可以申请一个或多个服务。
- 当Client申请服务成功后,Client就可以使用服务了。
SM一方面管理Server所提供的服务,同时又响应Client的请求并为之分配相应的服务。扮演的角色相当于月老,两边牵线。这种通信方式的好处是:一方面,service和Client请求便于管理,另一方面在应用程序开发时,只需为Client建立到Server的连接,就可花很少时间和精力去实现Server相应功能。那么,Binder与这个通信模式有什么关系呢?!其实,3者的通信方式就是Binder机制(例如:Server向SM注册服务,使用Binder通信;Client申请请求,用的是Binder通讯)
1.3 Binder通信机制流程(整体框架)
上图即是Binder的通信模型。我们可以发现:
- Client和Server是存在于用户空间
- Client与Server通信的实现,是由Binder驱动在内核空间实现
- SM作为守护进程,处理客户端请求,管理所有服务项。
为了方便理解,我们可以把SM理解成DNS服务器; 那么Binder Driver 就相当于路由的功能。这里就涉及到Client和Server是如何通信的问题。下面对1.2中提到的3个流程进行说明。
1.3.1 Server向SM注册服务
- 首先,XXXServer(XXX代表某个)在自己的进程中向Binder驱动申请创建一个XXXService的Binder的实体,
- Binder驱动为这个XXXService创建位于内核中的Binder实体节点以及Binder的引用,注意,是将名字和新建的引用打包传递给SM(实体没有传给SM),通知SM注册一个名叫XXX的Service。
- SM收到数据包后,从中取出XXXService名字和引用,填入一张查找表中。
- 此时,如果有Client向SM发送申请服务XXXService的请求,那么SM就可以在查找表中找到该Service的Binder引用,并把Binder引用(XXXBpBinder)返回给Client。
在进一步了解Binder通信机制之前,我们先弄清几个概念。
- 引用和实体。这里,对于一个用于通信的实体(可以理解成具有真实空间的Object),可以有多个该实体的引用(没有真实空间,可以理解成实体的一个链接,操作引用就会操作对应链接上的实体)。如果一个进程持有某个实体,其他进程也想操作该实体,最高效的做法是去获得该实体的引用,再去操作这个引用。
- 有些资料把实体称为本地对象,引用成为远程对象。可以这么理解:引用是从本地进程发送给其他进程来操作实体之用,所以有本地和远程对象之名。
1.3.2 一个问题-如何获得SM的远程接口
如果你足够细心,会发现这里有一个问题:
Sm和Server都是进程,Server向SM注册Binder需要进程间通信,当前实现的是进程间通信却又用到进程间通信。这就好比鸡生蛋、蛋生鸡,但至少得先有其中之一。
巧妙的Binder解决思路:
针对Binder的通信机制,Server端拥有的是Binder的实体;Client端拥有的是Binder的引用。
如果把SM看作Server端,让它在Binder驱动一运行起来时就有自己的Binder实体(代码中设置ServiceManager的Binder其handle值恒为0)。这个Binder实体没有名字也不需要注册,所有的client都认为handle值为0的binder引用是用来与SM通信的(代码中是这么实现的),那么这个问题就解决了。那么,Client和Server中这么达成协议了(handle值为0的引用是专门与SM通信之用的),还不行,还需要让SM有handle值为0的实体才算大功告成。怎么实现的呢?!当一个进程调用Binder驱动时,使用BINDER_SET_CONTEXT_MGR命令(在驱动的binder_ioctl中)将自己注册成SM时,Binder驱动会自动为它创建Binder实体。这个Binder的引用对所有的Client都为0。
1.3.3 Client从SM获得Service的远程接口
Server向SM注册了Binder实体及其名字后,Client就可以通过Service的名字在SM的查找表中获得该Binder的引用了(BpBinder)。Client也利用保留的handle值为0的引用向SM请求访问某个Service:我申请访问XXXService的引用。SM就会从请求数据包中获得XXXService的名字,在查找表中找到该名字对应的条目,取出Binder的引用打包回复给client。之后,Client就可以利用XXXService的引用使用XXXService的服务了。
如果有更多的Client请求该Service,系统中就会有更多的Client获得这个引用。
1.3.4 建立C/S通路后
首先要理清一个概念:client拥有自己Binder的实体,以及Server的Binder的引用;Server拥有自己Binder的实体,以及Client的Binder的引用。我们也可以从接收方和发送方的方式来理解:
- 从client向Server发数据:Client为发送方,拥有Binder的实体;Server为接收方,拥有Binder的引用
- 从server向client发数据:Server为发送方,拥有Binder的实体;client为接收方,拥有Binder的引用。
也就是说,我们在建立了C/S通路后,无需考虑谁是Client谁是Server,只要理清谁是发送方谁是接收方,就能知道Binder的实体和引用在哪边。
建立CS通路后的流程:(当接收方获得Binder的实体,发送方获得Binder的引用后)
- 发送方会通过Binder实体请求发送操作。
- Binder驱动会处理这个操作请求,把发送方的数据放入写缓存(binder_write_read.write_buffer) (对于接收方为读缓冲区),并把read_size(接收方读数据)置为数据大小(对于具体的实现后面会介绍);
- 接收方之前一直在阻塞状态中,当写缓存中有数据,则会读取数据,执行命令操作
- 接收方执行完后,会把返回结果同样用binder_transaction_data结构体封装,写入写缓冲区(对于发送方,为读缓冲区)
1.3.5 匿名Binder
之前在介绍Android使用Binder机制的优点中,提到Binder可以建立点对点的私有通道,匿名Binder就是这种方式。在Binder通信中,并不是所有用来通信的Binder实体都需要注册给SM广而告之的,Server可以通过已建立的实体Binder连接将创建的Binder实体传给Client。而这个Binder没有向SM注册名字。这样Server与Client的通信就有很高的隐私性和安全性。
这样,整个Binder的通信流程就介绍完毕了,但是对于具体的代码实现(比如binder_transaction_data是什么?binder_write_read.write_buffer又是什么?具体的驱动和逻辑实现又是怎么样?),在后面章节中会一一介绍。
几点疑问:
1. 是谁,怎么样成为SM守护进程,handle为0的binder实体什么时候创建?
2. binder引用和实体是如何创建的?在驱动中如何实现的通信?
3. 在SM中,binder实体是怎样转换成为引用的?
4. Server是如何注册服务,Client是如何获取服务的?
- Binder的数据结构
-
Binder在传输数据中的表述:flat_binder_object
-
Binder对象类型
-
Binder实体在驱动中的表述:binder_node
-
Binder引用在驱动中的表述:binder_ref
-
Binder 进程、线程结构:binder_proc和binder_thread
-
Binder收发数据包结构:binder_transaction_data
-
Binder写操作命令字: BC_XXX
-
Binder读操作命令字: BR_XXX
-
Binder驱动
-
binder_init
-
binder_ioctl
-
binder_open
-
binder_release
-
binder_flush
-
binder_poll
-
binder_mmap
2. Binder的数据结构
2.1 Binder在传输数据中的表述flat_binder_object
Binder可以在数据包的有效数据中越过进程边界从一个进程传递给另一个进程,这些传输中的Binder用 flat_binder_object结构来表示,如上所示
无论是Binder实体还是对实体的引用都从属于某个进程,所以该结构不能透明地在进程之间传输,必须有驱动的参与。例如当Server把 Binder实体传递给Client时,在发送数据中,flat_binder_object中的type是 BINDER_TYPE_BINDER,binder指向Server进程用户空间地址。如果透传给接收端将毫无用处,驱动必须对数据流中的这个 Binder做修改:将type该成BINDER_TYPE_HANDLE;为这个Binder在接收进程中创建位于内核中的引用并将引用号填入 handle中。对于发生数据流中引用类型的Binder也要做同样转换。经过处理后接收进程从数据流中取得的Binder引用才是有效的,才可以将其填 入数据包binder_transaction_data的target.handle域,向Binder实体发送请求。
这样做也是出于安全性考虑:应用程序不能随便猜测一个引用号填入target.handle中就可以向Server请求服务了,因为驱动并没有为你 在内核中创建该引用,必定会驱动被拒绝。唯有经过身份认证确认合法后,由‘权威机构’通过数据流授予你的Binder才能使用,因为这时驱动已经在内核中 为你建立了引用,交给你的引用号是合法的。
2.2 Binder对象的类型
2.5 Binder实体在驱动中的表述 binder_node
驱动中的Binder实体也叫‘节点’,隶属于提供实体的进程,由struct binder_node结构来表示
每个进程都有一棵红黑树用于存放创建好的节点,以Binder在用户空间的指针作为索引。每当在传输数据中侦测到一个代表Binder实体的 flat_binder_object,先以该结构的binder指针为索引搜索红黑树;如果没找到就创建一个新节点添加到树中。由于对于同一个进程来说 内存地址是唯一的,所以不会重复建设造成混乱。
2.6 Binder引用在驱动中的表述--引用描述结构:binder_ref
和实体一样,Binder的引用也是驱动根据传输数据中的flat_binder_object创建的,隶属于获得该引用的进程,用struct binder_ref结构体表示
就象一个对象有很多指针一样,同一个Binder实体可能有很多引用,不同的是这些引用可能分布在不同的进程中。和实体一样,每个进程使用红黑树存放所有该进程正在使用的引用。但Binder的引用可以通过两个键值索引:
- 对应实体在内核中的地址。注意这里指的是驱动创建于内核中的binder_node结构的地址,而不是Binder实体在用户进程中的地址。实体在内核中 的地址是唯一的,用做索引不会产生二义性;但实体可能来自不同用户进程,而实体在不同用户进程中的地址可能重合,不能用来做索引。驱动利用该红黑树在一个 进程中快速查找某个Binder实体所对应的引用(一个实体在一个进程中只建立一个引用)。
- 引用号。引用号是驱动为引用分配的一个32位标识,在一个进程内是唯一的,而在不同进程中可能会有同样的值,这和进程的打开文件号很类似。引用号将返回给 应用程序,可以看作Binder引用在用户进程中的句柄。除了0号引用在所有进程里都保留给SMgr,其它值由驱动在创建引用时动态分配。向Binder 发送数据包时,应用程序通过将引用号填入binder_transaction_data结构的target.handle域中表明该数据包的目的 Binder。驱动根据该引用号在红黑树中找到引用的binder_ref结构,进而通过其node域知道目标Binder实体所在的进程及其它相关信 息,实现数据包的路由。
2.7 Binder节点、进程、线程结构
binder_transaction:
- 主要C/S即请求进程和服务进程的相关信息,方便进程间通信,以及信息的调用。该结构体包括了binder_work以及进程/线程结构体信息,以及binder状态结构体
binder_node:
- binder的节点信息结构体,包括了binder_work, binder_proc.
binder_work:
binder_proc:
- 每打开一个binder驱动(系统允许多个进程打开binder驱动),都会有一个专门的binder_proc管理 当前进程的信息,包括:进程的ID、当前进程由mmap所映射出的buffer信息、以及当前进程所允许 的最大线程量。同时这个binder_proc会加入到系统的全局链表binder_procs中去,方便在不同进程之 间可以查找信息。
binder_thread:
- 线程信息结构体。在进程下存在一个或多个线程,因此binder驱动使用binder_thread来管理对应的线 程信息,主要包括线程所属的binder_proc、当前状态looper以及一个binder_transaction结构的 transaction_stack.
3. Binder驱动
3.1 binder_init
binder_init()初始化函数流程:
- 创建系统根节点:proc/binder
- 如果成功,创建”proc/binder/proc”,并把自己注册成Misc设备。在Misc的Struct miscdevice binder_miscdev中:有.fops = &binder_fops。这个就对应了Binder驱动的6个函数调用方法:ioctl, poll, open, release,flush, mmap
- 创建目录:”/dev”,并创建如上图的5个文件节点
3.2 binder_ioctl
在该函数中,一共有7个命令,但只实现了5个。在用户空间通过ioctl函数调用相应底层驱动的命令,来实现相应的方法。
3.2.1 BINDER_WRITE_READ
这个io操作码有一个参数,形式为struct binder_write_read
BINDER_WRITE_READ命令流程:
- 检查命令的完整性
- 把数据从用户空间拷贝到”binder_write_read”的结构体中
- 判断这个结构体中的write_size和read_size是否大于0.
- 如果write_size大于0则调用binder_thread_write函数;如果read_size大于0则调用binder_thread_read函数。
- write_bufffer和read_buffer所指向的数据结构还指定了具体要执行的操作,write_bufffer和read_buffer所指向的结构体是struct binder_transaction_data
Binder收发数据包结构:binder_transaction_data
和写数据一样,其中最重要的消息是BR_TRANSACTION 或BR_REPLY,表明收到了一个格式为binder_transaction_data的请求数据包(BR_TRANSACTION)或返回数据包 (BR_REPLY)
Binder写操作命令字(code)
在这些命令中,最常用的是BC_TRANSACTION/BC_REPLY命令对,Binder数据通过这对命令发送给接收方。这对命令所承载的数 据包由结构体struct binder_transaction_data定义。Binder交互有同步和异步之分,利用binder_transaction_data中 flag域区分。如果flag域的TF_ONE_WAY位为1则为异步交互,即Client端发送完请求交互即结束, Server端不再返回BC_REPLY数据包;否则Server会返回BC_REPLY数据包,Client端必须等待接收完该数据包方才完成一次交 互。
Binder读操作命令字(code)
和写数据一样,其中最重要的消息是BR_TRANSACTION 或BR_REPLY,表明收到了一个格式为binder_transaction_data的请求数据包(BR_TRANSACTION)或返回数据包 (BR_REPLY)
3.2.2 BINDER_SET_CONTEXT_MGR命令
作用是:将当前进程注册为SMgr。系统中同时只能存在一个SMgr。只要当前的SMgr没有调用close()关闭Binder驱动就不能有别的进程可以 成为SMgr。
流程:
- 检查binder_context_mgr_node的值是否为空,如果为空说明当前没有Context Manager的节点;
- 检查binder_context_mgr_uid是否存在,如果不存在,则设驱动中的全局变量binder_context_mgr_uid为当前进程的uid;如果存在,检查当前进程是否有执行命令的权限。
- 创建并初始化一个binder_node并赋值给全局变量binder_context_mgr_node。
这个命令是将一个进程/线程设置为Context Manager。该命令一般在系统启动时初始化Binder驱动的过程中被调用
3.2.3 BINDER_SET_MAX_THREADS
该命令告知Binder驱动接收方(通常是Server端)线程池中最大的线程数。由于Client是并发向Server端发送请求 的,Server端必须开辟线程池为这些并发请求提供服务。告知驱动线程池的最大值是为了让驱动在线程达到该值时不要再命令接收端启动新的线程。
3.2.4 BINDER_THREAD_EXIT
通知Binder驱动当前线程退出了。Binder会为所有参与Binder通信的线程(包括Server线程池中的线程和Client发出请求的 线程)建立相应的数据结构。这些线程在退出时必须通知驱动释放相应的数据结构。
3.2.5 BINDER_VERSION
获得Binder驱动的版本号
3.3 binder_open
流程:
- 创建并分配空间,保存Binder的数据。增加当前线程或进程的引用计数,把值赋给binder_proc的tsk段。
- 初始化binder_proc队列;设置当前进程nice值为default_priority
- 并把创建的binder_proc对象添加到全局的binder_proc Hash table中。这样,进程间就可以互相访问彼此的binder_proc对象了。
- 把当前进程的组id赋值给proc->pid,把proc赋值给filp->private_data,最后保存filp。
3.4 binder_release
流程:
- 获得当前进程/线程的pid
- 调用remove_proc_entry()删除pid命名的只读文件。
- 调用binder_defer_work()释放binder_proc对象;此处使用workqueue来提高系统性能。
3.5 binder_flush
流程:
- 调用函数binder_defer_work
- 调用schedule_work()来执行相应操作;
- 调用binder_free_thread()释放thread信息。
此函数一般在将在关闭一个设备文件描述符复制时被调用
3.6 binder_poll
实现非阻塞IO模型函数流程:
- binder_get_thread(proc)获得当前进程信息。
- 如果是进程,调用proc_work();如果是线程,调用thread_work()
- 调用binder_has_x_work检测,来判断所要采用的等待方式
- 队列是否为空;
- 线程/进程的循环状态;
- 返回信息;
- 调用poll_wait实现poll操作。
3.7 binder_mmap
注意:mmap函数的第二个参数为VMA的结构体指针,VMA是用来管理进程地址空间中不同区域的数据结构,由内核来维持。最终的作用是把分配的空间加入到VMA区域中去。VMA在mmap函数中会被使用。
内存空间映射函数流程:
- 检查映射空间大小(分配空间不能大于4M)
- 检查flags(映射区域不能是可写区域)
- 检查内存是否被映射过(内存空间不能被重复映射)
- 申请虚拟空间
- 分配页空间
- 分配物理内存
Native层流程大纲
介绍完Binder驱动的构架后,下面我们进入到Binder Native层的流程分析。Binder的整体流程可分为如下几个步骤:
- Service Manager成为守护进程--- Service Manager告知Binder驱动程序它是Binder机制的上下文管理者。
- Server和Client获得Service Manager的远程接口--- defaultServiceManager接口是如何实现的。
- Server向SM注册服务---- IServiceManager::addService接口是如何实现的
- Client从SM获得服务----IServiceManager::getService接口是如何实现的
其中,第2步是3和4的基础。上面4个步骤,是系统中Binder驱动和Native层的主要工作,而Binder Java层的每次调用底层函数都离不开这4步的操作。这里介绍Native层,Java层将在下章节介绍。
4.1 SM成为守护进程
SM成为守护进程的过程中,与驱动交互频繁,下面会结合之前的驱动框架来分析:
- Service Manager在用户空间的源代码位于frameworks/base/cmds/servicemanager 目录下,主要是由binder.h、binder.c和service_manager.c三个文件组成。
- Service Manager的入口位于service_manager.c文件中的main函数。Main函数主要有三个功能:
- 打开Binder设备文件;
- 调用mmap
- 告诉Binder驱动程序自己是Binder上下文管理者,即我们前面所说的守护进程;
- 进入一个无穷循环,充当Server的角色,等待Client的请求
4.1.1打开Binder设备文件
在service_manager.c打开Binder设备文件的核心代码见下图:
函数首先是执行打开Binder设备文件的操作binder_open,这个函数位于frameworks/base/cmds/servicemanager/binder.c文件中, 代码如下:
主要做了2件主要的事情:
- 打开驱动创建的/dev/binder文件结点,创建binder_proc结构体,保存/dev/binder上下文信息
- 调用驱动的mmap分配空间
4.1.2 打开设备详细分析
- 通过文件操作函数open打开/dev/binder设备文件,(/dev/binder是在Binder驱动模块初始化时创建的,创建入口在binder_init());此处调用驱动函数binder_open(),功能如下:
- 创建struct binder_proc,保存打开设备文件的进程上下文信息到struct file的私有变量private_data中
- binder_proc下,会挂4棵红黑树的节点:
- threads树:用来保存binder_proc进程内用于处理用户请求的线程
- node树:保存binder_proc进程内的binder实体
- refs_by_desc和refs_by_node:保存binder_proc进程内的Binder引用(前者以句柄,后者以节点地址的key来组织)
- 调用驱动的binder_mmap(),对打开的设备文件进行内存映射mmap:
- 通过filp->private_data得到打开/dev/binder时创建的binder_proc结构。
- 同一块地址,会通过struct vm_area_struct 映射用户空间server进程的使用信息;通过struct vm_struct 同时映射给内核;
- 好处:这样,同一块物理地址分别映射给了内核和server进程,就减少了数据从内核到server的拷贝了。 (一般是:client到内核、内核到server)
- 检查:映射内存大小不能超过4M(此处是128*1024为128K)
- 调用get_vm_area()获得空间的vm_struct 空间,初始化binder_proc结构体
- 调用binder_update_pange_range()为虚拟地址空间分配空闲的物理页面
4.1.3 成为守护进程
回到frameworks/base/cmds/servicemanager/service_manager.c文件中的main函数,下一步就是调用binder_become_context_manager来通知Binder驱动程序自己是Binder机制的上下文管理者,即守护进程。
binder_become_context_manager函数位于frameworks/base/cmds/servicemanager/binder.c文件中,
这里通过调用ioctl文件操作函数来通知Binder驱动程序自己是守护进程,命令号是BINDER_SET_CONTEXT_MGR,流程如下:
- 通过filp->private_data获得proc变量
- 通过binder_get_thread()获得线程信息,其流程如下:
- 把当前线程pid作为键值,在进程proc->thread表示的红黑树中进行查找,看是否创建了binder_thread信息;如果没有创 建,创建完后会插入到proc->threads红黑树中,下次就可从proc中找到
- 碰到binder_context_mgr_node和binder_context_mgr_uid。由于第一次用到,binder_context_mgr_node表示SM的实体,此时为null; binder_context_mgr_uid表示SM守护进程的id,此时为-1;于是会初始化binder_context_mgr_uid为current->cred->euid,这样当前线程就成为Binder机制的守护进程了,并且通过binder_new_node为Service Manager创建Binder实体。
- 如果第3步创建了新的binder_node,就会把新建的binder_node指针保存在binder_context_mgr_node中
4.1.4进入loop循环
回到frameworks/base/cmds/servicemanager/service_manager.c文件中的main函数中,继续往下看,会看到调用了binder_loop()函数进入循环,等待Client来请求。
binder_loop函数定义在frameworks/base/cmds/servicemanager/binder.c文件中,流程如下:
- 首先是通过binder_write()执行BC_ENTER_LOOPER命令告诉Binder驱动程序,Service Manager要进入循环了,调用驱动 ioctl():
- 调用binder_get_thread函数获取binder_thread,就能从proc中直接找到了,不需要创建一个新的。这里进入case BINDER_WRITE_READ:
- 首先是通过copy_from_user(&bwr, ubuf, sizeof(bwr))语句把用户传递进来的参数转换成struct binder_write_read 结构,并保存在本地变量bwr中,这里可以看出bwr.write_size等于4,
- 于是进入binder_thread_write函数,这里我们只关注BC_ENTER_LOOPER相关的代码
- 在执行完BC_ENTER_LOOPER时,thread->looper值就变为BINDER_LOOPER_STATE_ENTERED了,表明当前线程进入循环状态了
- 回到binder_ioctl函数,由于bwr.read_size == 0,binder_thread_read函数就不会被执行了,这样,binder_ioctl的任务就完成了
- 回到binder_loop函数,进入for循环,又会执行一个ioctl(bs->fd, BINDER_WRITE_READ, &bwr); 由于bwr.write_size等于0,会执行binder_thread_write函数,bwr.read_size等于32,于是进入到binder_thread_read()。
4.1.5 SM成为守护进程-总结
至此,我们就从源代码一步一步地分析完Service Manager是如何成为Android进程间通信(IPC)机制Binder守护进程的了。总结一下,Service Manager是成为Android进程间通信(IPC)机制Binder守护进程的过程是这样的:
1. 打开/dev/binder文件:open("/dev/binder", O_RDWR);
2. 建立128K内存映射:mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
3. 通知Binder驱动程序它是守护进程:binder_become_context_manager(bs);
4. 进入循环等待请求的到来:binder_loop(bs, svcmgr_handler);
在这个过程中,在Binder驱动程序中建立了一个struct binder_proc结构、一个struct binder_thread结构和一个struct binder_node结构,这样,Service Manager就在Android系统的进程间通信机制Binder担负起守护进程的职责了。
4.2 Server和Client获得Service Manager远程接口
我们知道,Service Manager在Binder机制中既充当守护进程的角色,同时它也充当着Server角色,然而它又与一般的Server不一样。对于普通的Server来说,Client如果想要获得Server的远程接口,那么必须通过Service Manager远程接口提供的getService接口来获得,这本身就是一个使用Binder机制来进行进程间通信的过程。而对于Service Manager这个Server来说,Client如果想要获得Service Manager远程接口,却不必通过进程间通信机制来获得,因为Service Manager远程接口是一个特殊的Binder引用,它的引用句柄一定是0。
获取Service Manager远程接口的函数是defaultServiceManager,这个函数声明在frameworks/base/include/binder/IServiceManager.h文件中:
4.2.1 Service Manager继承关系
那么为什么要获取远程端口呢?!是因为要实现业务以及完成通信。先来看看网上的一个关于继承关系的图示:
IServiceManager类提供的业务函数:getService(), checkService(), addService();
BpServiceManager类继承了BpInterface类,而BpInterface类继承了BpRefBase类。
在BpRefBase类中,有一个成员变量mRemote,它的类型是IBinder*,实现类为BpBinder,它表示一个Binder引用,引用句柄值保存在BpBinder类的mHandle成员变量中。
BpBinder类通过IPCThreadState类来和Binder驱动程序并互,而IPCThreadState又通过它的成员变量mProcess来打开/dev/binder设备文件,mProcess成员变量的类型为ProcessState。
ProcessState类打开设备/dev/binder之后,将打开文件描述符保存在mDriverFD成员变量中,以供后续使用。
4.2.2 defaultServiceManager
理清了上述的继承关系后,我们再来看看defaultServicemanager这个函数。
从这个函数可以看出:gDefaultServiceManager是单例模式,调用defaultServiceManager函数时,如果gDefaultServiceManager已经创建,则直接返回,否则通过interface_cast<IServiceManager>(ProcessState::self()->getContextObject(NULL))来创建一个,并保存在gDefaultServiceManager全局变量中。
在这个过程中,有4个关键步骤:
- 调用ProcessStaate::self()
- 调用getContextObject(NULL)
- 使用interface_cast
- 使用 <IserviceManager>
下面来分析这4个步骤
4.2.2.1 ProcessState::self()
ProcessState::self()是ProcessState的静态成员函数,它的作用是返回一个全局唯一的ProcessState实例变量,就是单例模式了,这个变量名为gProcess。如果gProcess尚未创建,就会执行创建操作,在ProcessState的构造函数中,会通过open文件操作函数打开设备文件/dev/binder,并且返回来的设备文件描述符保存在成员变量mDriverFD中。也就是得到了一个可以和底层打交道的ProcessState类型的gProcess对象。在addervice()的流程里,会深入分析。
4.2.2.2 getContextObject()
gProcess->getContextObject(NULL),传递的值为NULL即0,返回的值为sp<IBinder>类型的getStrongProxyForHandle(0)。getStrongProxyForHanlde(int32_t handle)中,handle的值是一个资源项数组中的索引值。
- 作用:根据索引查找对应的资源,如果lookupHandleLocked发现没有对应资源项,则创建一个新的BpBinder项,填充BpBinder,并返回(handle值为0)
结果为:返回一个handle为0的Binder引用,即BpBinder;于是创建Service Manager远程接口的语句可以简化为:gDefaultServiceManager = interface_cast<IServiceManager>(new BpBinder(0));
4.2.2.3 interface_cast
再来看函数interface_cast<IServiceManager>的实现,它是一个模板函数,定义在framework/base/include/binder/IInterface.h文件中:
这里的《INTERFACE>是IServiceManager,于是调用了IServiceManager::asInterface函数,那么 gDefaultServiceManager = interface_cast<IServiceManager>(new BpBinder(0)); 可以变成:gDefaultServiceManager = IServiceManager::asInterface(new BpBinder(0));
IserviceManager::asInterface是通过DECLARE_META_INTERFACE(ServiceManager)宏在IServiceManager类中声明的,它位于framework/base/include/binder/IServiceManger.h文件中。IServiceManager::asInterface的实现是通过IMPLEMENT_META_INTERFACE(ServiceManager, "android.os.IServiceManager")宏定义的,它位于framework/base/libs/binder/IServiceManager.cpp文件中。
4.2.2.4 IServiceManager::asInterface
最终,在IServiceManager::asInterface函数中会调用:Return intr = new BpServiceManager(obj); 这里的obj就则刚才创建的new BpBinder(0),上面的代码为:
- intr = new BpServiceManager(new BpBinder(0));
回到defaultServiceManager函数中,最终结果为:gDefaultServiceManager = new BpServiceManager(new BpBinder(0));
这样,Service Manager远程接口就创建完成了,它本质上是一个BpServiceManager,包含了一个句柄值为0的Binder引用。这里,实现asInterface函数,是利用BpBinder对象新建了一个BpServiceManager对象
4.3 Server向SM注册服务
实际上,BnMediaPlayerService并不是直接接收到Client处发送过来的请求,而是使用了IPCThreadState接收Client处发送过来的请求,而IPCThreadState又借助了ProcessState类来与Binder驱动程序交互。
IPCThreadState接收到了Client处的请求后,就会调用BBinder类的transact函数,并传入相关参数,BBinder类的transact函数最终调用BnMediaPlayerService类的onTransact函数,于是,就开始真正地处理Client的请求了。
以MediaServer作为Server的例子,这里我们只看MediaPlayerService:多媒体系统的重要服务。MS的入口函数在 \frameworks\av\media\mediaserver\ main_mediaserver.cpp 如下图,
可分为5步:
- 创建ProcessState
- 创建IServiceManager对象
- 初始化MediaPlayerService服务对象
- 创建进程的线程池
- 把自己加入线程池
下面来分别分析。
4.3.1 ProcessState
之前分析过,这里详细介绍下参数传递的流程。ProcessState::self()调用创建一个ProcessState实例。ProcessState::self()是ProcessState类的一个静态成员变量,定义在frameworks/base/libs/binder/ProcessState.cpp文件中:
这里可以看出,这个函数作用是返回一个全局唯一的ProcessState实例gProcess。全局唯一实例变量gProcess定义在frameworks/base/libs/binder/Static.cpp文件中
再来看ProcessState的构造函数:ProcessState::ProcessState()的流程:
- 通过open_driver()打开binder设备 /dev/binder,并将打开设备文件描述符保存在成员变量mDriverFD中,这样,与Binder驱动有了交互的通道。open_driver这个函数同样位于frameworks/base/libs/binder/ProcessState.cpp文件中,流程如下:
- 通过调用驱动open()打开/dev/binder,在打开/dev/binder设备文件后,Binder驱动程序就为MediaPlayerService进程创建了一个struct binder_proc结构体实例来维护MediaPlayerService进程上下文相关信息。
- 调用ioctl()执行BINDER_VERSION获得当前Binder驱动程序的版本号。将BINDER_CURRENT_PROTOCOL_VERSION写入到传入的参数arg指向的用户缓冲区中。
- 调用ioctl()执行BINDER_SET_MAX_THREADS,通知Binder驱动程序最多能设置多少个线程,MediaPlayerService最多可同时启动15个线程来处理Client端的请求。把用户传进来的参数保存在proc->max_threads中就完毕了。(注意,这里再调用binder_get_thread函数的时候,就可以在proc->threads中找到当前线程对应的struct binder_thread结构了,因为前面已经创建好并保存在proc->threads红黑树中)
- 对返回的fd使用mmap,把设备文件/dev/binder映射到内存中(流程见“成为守护进程mmap"部分)
这样,ProcessState全局唯一变量gProcess就创建完毕了,回到了frameworks/base/media/mediaserver/main_mediaserver.cpp文件中的main函数
4.3.2 创建IServiceManager对象
DefaultServiceManager函数的实现在之前分析过,则:sp<IServiceManager> sm = defaultServiceManager(); 为:sp<IServiceManager> sm = new BpServiceManager(new BpBinder(0));
4.3.3 注册MediaPlayerService
MediaPlayerService::instantiate()调用:defaultServiceManager()->addService()。这个函数最终会通过IPCthreadState->transact()和Binder驱动打交道,把数据传送到SM中,完成MediaPlayerService的注册。defaultServiceManager()返回的对象是BpServiceManager,而BpServiceManager是IServiceManager的后代。IServiceManager是一个抽象类,里面有我们操作业务层的各种函数。
4.3.3.1 MediaPlayerService::instantiate()
MediaPlayerService::instantiate();的实现如下:
这里,实际上是调用的BpServiceManger::addService()这个函数实现在frameworks/base/libs/binder/IServiceManager.cpp文件中的addService()。addService是一个业务层的函数,调用remote()->transact();remote()返回的是mRemote,也就是BpBinder对象。在addService函数中,把请求数据打包成data后,传递给了BpBinder的transact函数,把通信任务交给了BpBinder;即交给了通信层去处理。
4.3.3.2 transact()通信层的工作
BpBinder::transact()调用了:status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
这里的mHandle为0,code为ADD_SERVICE_TRANSACTION。ADD_SERVICE_TRANSACTION是上面以参数形式传进来的,这里表示的是Service Manager远程接口,mHandle的句柄值一定是0。即,把transact工作交给了IPCThreadState。IPCThreadState::self()中,TLS: Thread Local Storage,每个线程都有,且不共享。通过pthread_getspecific/pthread_setspecific函数可以获得/设置这些空间中的内容。
流程如下:
- pthread_getspecific()从线程本地存储空间中获得保存在其中的IPCThreadState对象
- new一个IPCThreadState对象,调用构造函数,构造函数中会调用pthread_setspecific函数。则上面的调用变为: new IPCThreadState()->transact(mHandle, code, data, reply, flags);
- 构造函数 IPCThreadState()的流程:
- pthread_setspecific() 把自己设置到线程本地存储中
- 设置mIn和mOut搜法命令的缓冲区
- IPCThreadState::transact()调用:
- err = writeTransactionData(BC_TRANSACTION,...); 函数里会创建一个binder_transaction_data tr,这个就是等下要传给Binder驱动的。发送的消息码为:BC_XX
- 该函数原型为:IPCTheadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
- 其中函数中定义的binder_transaction_data是和binder设备通信的数据结构 binder_transaction_data tr;
- handle的值传递给了target用来标识目的端,其中0为SM的标志。tr.target.handle = handle;
- cmd:发送的命令消息码 BC_XXX,此处为BC_TRANSACTION
- code: 用来switch/case的消息码
- 把命令写到mOut中,并不直接发出去。mOut.writeInt32(cmd); mOut.write(&tr, sizeof(tr));
- 这时,已经把addService的请求信息”tr”写到mOut中了,写入的内容为:
- err = waitForResponse(reply); binder设备向应用程序回复消息,BR_XX为回复的消息码
- 接收回复的数据waitForResponse(Rarcel *reply, status_t *acquireResult)。在无限循环中(数据读完或异常,则退出)做了如下事情:
- 调用talkWithDriver();
- 其中定义了binder_write_read bwr; 一个与binder设备交换数据的结构;这里调用了驱动的ioctl()与驱动交互:
- 写流程:
- 请求命令填充
- 调用ioctl,由binder驱动实现具体操作
- 读流程:
- 接收数据缓冲区信息的填充,如果以后收到数据,就填到mIn中
- 调用ioctl,由binder驱动实现具体操作
- 把数据读到mIn中
- 把命令(BR_XXX)读出并switch/case
- executeCommand(cmd)执行命令
- executeCommand(int32_t cmd)收到cmd后,switch(cmd):
- case BR_ERROR:
- case BR_TRANSACTION:
- sp<BBinder>b((BBinder*)tr.cookie); 设置BnServiceXXX的对象b,BnServiceXXX是从BBinder派生
- b->transact(); 调用transact函数,发送binder_transaction_data和Parcel类型的数据
- case BR_DEAD_BINDER:
- 收到Binder驱动发来的service死掉的消息,只有Bp端能收到
- case BR_SPAWN_LOOPER:
- 收到来自驱动的指示以创建一个新线程,用于和Binder通信
4.3.4 StartThreadPool()
StartThreadPool()调用spawnPooledThread()函数:sp<Thread> t = new PoolThread(isMain); isMain参数是true; PoolThread是在IPCThreadState中定义了一个Thread子类。PoolThread类中,调用threadLoop()。threadLoop()中,在这个线程中又创建了一个IPCThreadState: IPCThreadState::self()->joinThreadPool(mIsMain)。
joinThreadPool()流程:
- mOut.writeInt32(isMain?BC_ENTER_LOOPER: BC_REGISTER_LOOPER); 如果isMain为true,则需要循环处理。把请求写到mOut中,过会一起发出
- 处理已经死亡的BBinder对象
- 发送命令,读取请求:调用talkWithDriver();
4.3.5 MS中的joinThreadPool()
MediaServer进程一共注册了4个服务。MS中目前有两个线程在talkWithDriver():
- 一个是StartThreadPool启动新线程通过joinThreadPool,调用talkWithDriver(),读取binder设备;
- 另一个是MS主线程调用joinThreadPool,调用talkWithDriver(),读取binder设备
BnService和BpService负责业务的交互;BBinder和BpBinder负责通信的交互
4.3.6 MediaServer注册服务到SM流程图
4.4 Client从SM获得服务
client从SM获得服务,也就是从SM获得 BpMediaPlayerService。BpMediaPlayerService的构造函数有一个参数impl,它的类型为const sp<IBinder>&,从上面的描述中,这个实际上就是一个BpBinder对象。这样,要创建一个BpMediaPlayerService对象,首先就要有一个BpBinder对象。再来看BpBinder类的构造函数,它有一个参数handle,类型为int32_t,这个参数的意义就是请求MediaPlayerService这个远程接口的进程对MediaPlayerService这个Binder实体的引用了。因此,获取MediaPlayerService这个远程接口的本质问题就变为从Service Manager中获得MediaPlayerService的一个句柄了。
4.4.1 详细分析:
这里我们以MediaServer中的MediaPlayService与Client交互为例,来了解请求数据是如何从通信层传递到业务层并进行处理的:
client想要获得某个Service的信息,就得先和SM交互,通过调用getService()来获得对应的Service信息,这里用到的是IMediaDeathNotifier::getMeidaPlayerService()
- 通过defaultServiceManager()获得SM的远程接口BpServiceManager的IServiceManager接口
- 用while循环通过binder= sm->getService()不断尝试获得名字为”media.player”的service;向SM查询对应服务的信息,返回一个与MediaPlayerService通信的BpBinder;如果SM上还没有注册对应的服务,则睡0.5秒再尝试,直到对应服务注册到SM上才中止
- 通过interface_cast,将这个binder转换成一个BpMediaPlayerService
有了BpMediaPlayerService,client就能够使用任何IMediaPlayerService提供的业务逻辑函数了,e.g: createMediaRecorder和createMetadataRetriever等。调用这些函数都将把请求数据打包发给Binder驱动,并由BpBinder中handle知道对应端的处理者来处理,包括
- 通信层接收到请求
- 递交给业务层处理
4.4.2 BpServiceManager::getService()
BpServiceManager::getService通过BpServiceManager::checkService执行操作。
在BpServiceManager::checkService中:
- 首先是通过Parcel::writeInterfaceToken往data写入一个RPC头;就是写往data里面写入了一个整数和一个字符串“android.os.IServiceManager”, Service Manager来处理CHECK_SERVICE_TRANSACTION请求之前,会先验证一下这个RPC头,看看是否正确。接着再往data写入一个字符串name,这里就是“media.player”了
- 调用remote()返回的是一个BpBinder,于是,就进行到BpBinder::transact函数了。这里的mHandle = 0,code = CHECK_SERVICE_TRANSACTION,flags = 0。进入到IPCThread::transact函数中:
- 首先是调用函数writeTransactionData写入将要传输的数据到IPCThreadState的成员变量mOut中去
- 调用waitForResponse(reply),这个函数通过IPCThreadState::talkWithDriver与驱动程序进行交互,这里的needRead为true,因此,bwr.read_size大于0;outAvail也大于0,因此,bwr.write_size也大于0;
- 最后ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)
4.5 c/s Native层通信小结
- 通信层
- 首先server和client都调用了ProcessState的构造函数从而都向驱动申请了一块物理内存用于通信数据的存放
- server调用SM的addService函数传递一个字符串和实际Binder对象在自己虚拟地址空间的地址到Binder驱动,Binder驱动记录下该地址值,在SM申请的物理内存中分配一个虚拟地址并连同字符串一起传递给SM,而且Binder驱动会记录下两个地址之间的对应关系.
- client调用SM的getService函数传递一个字符串给SM,SM将相对应的虚拟地址值传递给Binder驱动,Binder驱动查询出实际对象的地址,在client申请的物理内存中分配一个虚拟地址并传递给client,而且Binder驱动会记录下这个地址和实际Binder对象地址之间的对应关系,client这里得到的就是实际Binder的引用了。到了这一步,真正的Binder对象就拥有两个引用,一个在SM,一个client.
- client通过得到的Binder引用调用server中的函数,驱动会根据传递过来引用值找到应该处理该请求的进程,并唤醒其中的Binder线程,调用BBinder对象的onTransaction函数,最终调用到实际的函数
- 从通信层到业务层
- 从bpxxxService的构造函数可以看出,在Client进程,每一个BpxxxService对象都封装了一个BpBinder对象,因此,确切来说,Client进程缓存的是BpBinder对象,每一个BpBinder对象都与一个Binder句柄值对应。每当Client进程需要创建一个BpxxxService对象时,就会检查本地缓存中是否已经存在对应的BpBinder对象。如果存在的话,就可以直接使用它来创建一个BpxxxService对象,否则的话,就要通过Binder驱动程序来获得一个Binder句柄值,再以这个句柄值来创建一个BpBinder对象,最后再根据这个BpBinder对象来创建一个BpxxxService对象。
- 业务层
- 由于BpMediaPlayerService继承了IMediaPlayerService,IXXService有比如createMediaRecord(), CreateMetaDataRetrive()等业务层方法,则可以完成相应业务需要
通常,应用程序框架中基于Java语言的Binder接口是通过JNI来调用基于C/C++语言的Binder运行库来为Java应用程序提供进程间通信服务的。在应用程序中,Server被实现为Service的形式,并通过IServiceManager.addService接口来把这个Service添加到Service Manager中;Client也是通过IServiceManager.getService接口来获得Service接口,这样就可以使用这个Service提供的功能了。
这里,我们以XXXService代表XXX的Service服务为例,把整个交互的过程分为如下几个部分来分析:
-
初始化Framework层Binder框架
-
C/S获得ServiceManager的Java远程接口过程
-
XXXService的接口定义和启动过程,添加自己到SM中
-
Client获得XXXService的Java远程接口过程
-
Client通过Java远端接口使用XXXService提供的服务
5.1 Binder framework层的初始化
在java层工作前,需建立与Native层的关系,建立这个关系的函数是 android_util_Binder.cpp::register_android_os_Binder(),流程如下:
- 初始化Java Binder类和Native层的关系:调用 int_register_android_os_Binder(env)
- 找到Java层的Binder
- 通过gBinderOffsets对象保存和Binder类相关的某些在JNI层中使用的信息
- 注册Binder类中native函数的实现
- 初始化Java BinderInternal类和Native层的关系:调用 int_register_android_os_BinderInternal(env)
- 根据BinderInternal的全路径(com/android/internal/os/BinderInternal)找到代表该类的jclass对象
- 通过gBinderInternal静态对象,保存BinderInternal类的一些信息:如methodID和filedID
- 注册BinderInternal类中native函数的实现
- 初始化 Java BinderProxy类和Native层的关系:调用 int_register_android_os_BinderProxy(env)
- 通过gWeakReferenceOffsets来和WeakReference类交互,包括:获取WeakReference类get函数的methodID
- 通过gErrorOffsets来和Error类交互
- 通过gBinderProxyOffsets来和BinderProxy类交互,包括:获取BinderProxy的一些信息
- 通过gClassOffsets来和Class类交互
- 注册BinderProxy native函数的实现
- 初始化Java Parcel类和Native层的关系:调用 int_register_android_os_Parcel(env)
作用:初始化其实就是提前获取一些JNI层的使用信息,这样可以节省每次使用时获取这些信息的时间
5.2 Binder framework层构架总览
这里解释一下Java Binder, Java Internal, Java Proxy,以及IBinder的关系:
- 系统定义了一个IBinder接口类以及DeathRecipient接口。IBinder接口类中定义了FLAG_ONEWAY标志,作用是实现非阻塞,只把请求发送到Binder驱动即可返回,不用等待服务端的结果。
- Binder类和BinderProxy类分别实现了IBinder接口。Binder类作为服务端Bn的代表;BinderProxy类作为客户端Bp的代表
- BinderInternal类仅供Binder架构使用,其内部有一个GcWatcher类,专门用于处理和Binder架构相关的垃圾回收
- Java层同样提供一个用于承载通信数据的Parcel类
5.3 C/S获得ServiceManager的Java远程接口过程
我们要获取的ServiceManager的Java远程接口是一个ServiceManagerProxy对象的IServiceManager接口,如上图。IServiceManager接口提供了getService()和addService()两个函数来管理Service。从serviceManagerProxy的构造函数中发现,它需要一个BinderProxy对象的IBinder接口作为参数;所以得先获得BinderProxy对象。
上图中,我们可以看到获取SM的Java远程接口ServiceManagerProxy的路径,是通过ServiceManager.getIServiceManager()来获取,而该函数又是通过ServiceManagerNative来获取。
我们先来分析getIServiceManager()
5.3.1 ServiceManager.getIServiceManager()
这个函数定义在frameworks/base/core/java/android/os/ServiceManager.java文件中,里面的核心代码:
- sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
- return sServiceManager;
如果其静态成员变量sServiceManager尚未创建,首先要通过BinderInternal.getContextObject()来获得一个BinderProxy对象,再调用ServiceManagerNative.asInterface()来创建对应的ServiceManagerProxy对象
接下来通过BinderInternal.getContextObject() 和ServiceManagerNative.asInterface()两个部分来分析。
5.3.1.1 BinderInternal.getContextObject()
BinderInternal.getContextObject()这个函数定义在frameworks/base/core/java/com/android/internal/os/BinderInternal.java文件中,调用了JNI层的android_os_BinderInternal_getContextObject()。android_os_BinderInternal_getContextObject()是一个JNI方法,在frameworks/base/core/jni/android_util_Binder.cpp文件中,核心代码为:
- sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
- ProcessState::self()->getContextObject(NULL)返回一个BpBinder对象,handle值为0,代码即可以写成:
- sp<IBinder> b = new BpBinder(0);
- return javaObjectForIBinder(env, b); 把这个BpBinder对象转换成一个BinderProxy对象
- 在该函数中,有两个变量gBinderOffsets和gBinderProxyOffsets:
- gBinderOffsets变量是用来记录”ppt 2.”左下角第二个类图中的Binder类的相关信息的,它是在注册Binder类的JNI方法的int_register_android_os_Binder函数初始化的
gBinderProxyOffsets是用来变量是用来记录”ppt 2.”右上角第一个图中的BinderProxy类的相关信息的,它是在注册BinderProxy类的JNI方法的int_register_android_os_BinderProxy函数初始化的
核心代码:
- object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor); 创建了一个BinderProxy对象
- env->SetIntField(object, gBinderProxyOffsets.mObject, (int)val.get()); 把BpBinder对象和BinderProxy对象关联起来;BinderProxy.mObject成员记录了这个BpBinder对象的地址
- 最后,把BinderProxy返回到android_os_BinderInternal_getContextObject函数,再返回到ServiceManager.getIServiceManager()中,我们就获得一个BinderProxy对象了
- 在getIServiceManager()中的:sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
相当于:sServiceManager = ServiceManagerNative.asInterface(new BinderProxy());
5.3.1.2 ServiceManagerNative.java::asInterface()
接下来调用asInterface(),定义在frameworks/base/core/java/android/os/ServiceManagerNative.java 文件中:
- asInterface(IBinder obj):obj是一个BpProxy对象或者BinderProxy对象(取决于这个申请SM的Java层远程接口的对象是Client还是Server),构造一个和业务相关的Proxy对象。这里我们假设申请者是client,构造了ServiceManagerProxy对象
这样,在getIServiceManager()中:sServiceManager = ServiceManagerNative.asInterface(new BinderProxy()); 相当于:sServiceManager = new ServiceManagerProxy(new BinderProxy());
5.3.2 获得SM的Java远端接口整体流程
这样,申请者在Java层拥有了一个Service Manager远程接口ServiceManagerProxy,这个ServiceManagerProxy对象在JNI层有一个handle值为0的BpBinder对象与之通过gBinderProxyOffsets关联起来,整体流程如下:
5.4 Service启动过程
XXXService继承了IXXXService.Stub类,并通过本地方法实现了Stub类的业务函数,有了XXXService之后,我们就会把它的Server类SystemServer启动起来。
定义在frameworks/base/services/java/com/android/server/SystemServer.java文件中,SystemServer对象是在系统启动时创建的,创建后会启动一个线程来创建XXXService,并把它添加到SM中去
在serverThread extends Thread中:ServiceManager.addService("XXX", new XXXService());
下面我们会进行new XXXService() 和 ServiceManager.addService()两部分的分析
5.4.1 new XXXService()
New XXXService()会调用XXXService类的构造函数,而XXXService类继承于IXXXService.Stub类(XXXNative),Stub类又继承于Binder类,因此会调用Binder类的构造函数,在这个构造函数里,又会调用init()来初始化这个Binder对象
init()只做了一件事,就是创建一个JavaBBinderHolder对象,然后把这个对象的地址保存在Binder类的mObject成员变量中
那么结果为:获得了一个新的实例XXXService(); 且Java层的Binder对象把Native层的JavaBBinderHolder(就是BBinder)保存在变量mObject中
5.4.2 ServiceManager.addService()的实现
分析完了new XXXService(),再来看下ServiceManager.addService()的实现,核心代码:
- getIServiceManager().addService(name, service);
getIServiceManager()之前分析过,返回一个ServiceManagerProxy对象的IServiceManager接口,那么我们看ServiceManagerProxy.addService()的实现:
- 获得Parcel类型的data和reply
- data.writeStrongBinder(service)
- 调用mRemote.transact(ADD_SERVICE_TRANSACTION,data,reply,0);这里的mRemote就是BinderProxy对象,调用transact把封装好的请求数据发送出去
addService()最终会调用到Framework层的BinderProxy.transact();最后调用到Native层的BpBinder::transact()进入到Binder驱动,然后驱动唤醒SM响应这个ADD_SERVICE_TRANSACTION请求,把自己注册到SM中;
AMS在SM中注册服务流程图
XXXService注册服务的类图
5.5.1 Client获得XXXService的Java远程接口过程
Client是通过IServiceManager.getService()来获得XXXService的远程接口的;在client这边的onCreate()中调用IXXXService.Stub.asInterface(ServiceManager.getService(“XXX”)); 先看ServiceManager.getService(“XXX”)
ServiceManager.getService(“XXX”)。实际是调用了ServiceManagerProxy.getService(),这个函数通过mRemote.transact执行操作;和前面一样mRemote是一个BinderProxy对象。
然后调用IBinder binder = reply.readStrongBinder();作用是:调用JNI层的 android_os_Parcel_readStrongBinder(),其作用是把Java语言的Parcel对象转换成C++语言的Parcel对象parcel,并通过parcel->readStrongBinder函数来获得一个Binder引用的BpBinder对象
最后:
- return javaObjectForIBinder(env, parcel->readStrongBinder());
javaObjectForIBinder()之前介绍过,会创建一个BinderProxy对象。相当于: return javaObjectForIBinder(env, new BpBinder(handle)); 返回给上层getService()的binder对象
那么 XXXService = IXXXService.Stub.asInterface(ServiceManager.getService("XXX")); 相当于:XXXService = IXXXService.Stub.asInterface(new BinderProxy()));
5.5.2 IXXXService.Stub.asInterface()
这个函数的核心代码为:
- android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);
- if (((iin!=null)&&(iin instanceof android.os.IXXXService))) {
- return ((android.os.IXXXService)iin);
- }
- return new android.os.IXXXService.Stub.Proxy(obj);
- }
这里的obj是一个BinderProxy对象,它的queryLocalInterface返回null,于是调用下面语句获得XXXService的远程接口: return new android.os.IXXXService.Stub.Proxy(obj);
相当于:return new android.os.IXXXService.Stub.Proxy(new BinderProxy());
这样就获得了XXXService的远程接口了,实质上是实现了IXXXService接口的IXXXService.Stub.Proxy对象
获得XXXService的类图
client使用XXXService框架图
5.6 framework层总结
Native层和Java层的关键点:
- native层中,client会有一个BpBinder引用,Server会有一个BBinder引用来实现通信;
- Java层中,client会有一个BinderProxy对象,server会有一个JavaBBinder对象来负责通信,但实质的通信实现是基于native层,Native层又基于Binder驱动的
针对Java层C/S交互的5个步骤的总结:
- 初始化Java层Binder框架,是为了减少在以后通信过程中初始化的时间
- C/S获得ServiceManager的Java远程接口过程,实际上就是获得ServiceManagerProxy,为Java层提供C/S与SM通信的对象
- XXXService的接口定义和启动过程,添加自己到SM中,实际上是为XXXService对象创建与SM通信的BinderProxy对象(就是步骤2),和创建为与client通信的JavaBBinder对象
- Client获得XXXService的Java远程接口过程,实际上是为client创建与SM通信的BinderProxy对象(就是步骤2),和创建与XXXService通信的BinderPorxy对象
- Client通过Java远端接口使用XXXService提供的服务,实际上是利用client的BinderProxy对象与XXXService的JavaBBinder对象通信,达到client使用服务的目的
虽然Binder机制的代码层层嵌套,逻辑复杂,但Binder机制的实质就是实现不同进程间的通信,通过SM来管理跨进程的服务;理解这一点,就可以理清Binder的核心:通信的实现最终都会由Binder的驱动实现,Native层和Java层的复杂构架,是出于2方面考虑:
- Native层的构架是为了提供统一的接口,并把业务层和逻辑层分开
- Java层的构架是为应用层提供统一的接口,把内部实现封装起来,隔开了底层实现和上层应用
6. 用eclipse实现PMService
PMservice是一个通过Service服务,来实现任务管理的程序。分为客户端PMClient和服务端PMService。
PMService提供一些操作方法:
- 服务开始的提示方法:getVal();
- 任务管理器的查询方法:getProcessID() 获取进程号,和getProcessName()获取进程名;
- 以及终止进程的方法:killProc(String ID),来提供服务给客户端。
PMClient使用PMService所提供的服务,来调用这些方法实现业务逻辑,并提供显示界面。
对于PMService的实现
- 通过ActivityManager activityManager = (ActivityManager) getSystemService("activity"); 获得activityService
- 编写aidl文件,eclipse会自动生成相应的Stub和Proxy的接口实现
对于PMClient的实现
- 复制PMService的aidl文件,eclipse会为PMClient生成相应的接口实现
- 通过在ServiceConnection:: onServiceConnected()中,PMService = IPMService.Stub.asInterface(service); 获得PMService创建的 PMServiceProxy(new BinderProxy()); 并把这个proxy对象保存在PMService中
- 在onCreate()方法中,调用bindService(new Intent(IPMService.class.getName()),serConn, Context.BIND_AUTO_CREATE);
其中, serConn 是ServiceConnection类的实例化,传递的是PMService对象,这里是把当前类的PMService与PMService那边的PMService绑定在一起,这样就实现了两个进程的通信了
实现流程分析
- 调用的时候,客户端首先调用bindService(new Intent (IPMService.class.getName(), serConn,Context.BIND_AUTO_CREATE);激活serviceConnection的onServiceConnected方法,在此方法中获取到一个binder,这个binder是系统给我们的一个与远程进行通信的binder,此binder能够查找系统中注册的service,如果没有查找到该Service,那么可认定该service是从其他apk获得的,就创建一个此service的静态代理类Proxy,否则,就把这个service返回,供客户端调用。
- 服务端收到这个Intent后,激活PMServiceImpl extends IPMService.Stub的onBind方法,创建一个Binder返回 (return new PMServiceImpl())。之后,这个Binder负责与客户端的Proxy通信。
源码流程:
PMService的源码
在eclipse新建PMServer工程,我用的是android 4.2.2
先列出PMServer工程的文件清单,其中IPMService.java是通过IPMService.aidl自动创建的
下面是各个文件的源码:
IPMService.aidle
- package com.example.pmserver;
-
-
-
-
- interface IPMService
- {
- double getVal(String val);
- List<String> getProcessName();
- List<String> getProcessID();
- String killProc(String PID);
- }
把这个文件放入到我们工程的com.example.pmserver包中,系统会自动生成IPMService.java
为了实现进程信息的查询,我们需要CommandHelper.java这个类,通过API执行shell语言的方式来收集我们需要的进程信息。
CommandHelper.java
下面,需要提供一些操作的接口,以便调用
CommandResult.java
- package com.example.pmserver;
-
- public class CommandResult {
- public static final int EXIT_VALUE_TIMEOUT=-1;
-
- private String output;
-
- void setOutput(String error) {
- output=error;
- }
-
- String getOutput(){
- return output;
- }
-
- int exitValue;
-
- void setExitValue(int value) {
- exitValue=value;
- }
-
- int getExitValue(){
- return exitValue;
- }
-
- private String error;
-
-
-
-
- public String getError() {
- return error;
- }
-
-
-
-
- public void setError(String error) {
- this.error = error;
- }
- }
接下来,就是我们Service的核心文件了,实现了业务逻辑。
PMService.java
- package com.example.pmserver;
-
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.Iterator;
- import java.util.List;
- import com.example.pmserver.CommandHelper;
- import com.example.pmserver.CommandResult;
- import android.os.IBinder;
- import android.os.RemoteException;
- import android.app.ActivityManager;
- import android.app.ActivityManager.RunningAppProcessInfo;
- import android.app.Service;
- import android.content.Intent;
- import android.util.Log;
-
- public class PMService extends Service {
-
- private static final String TAG = "PMService";
-
- List<String> ProcName = new ArrayList<String>();
- List<String> ProcID = new ArrayList<String>();
-
- public class PMServiceImpl extends IPMService.Stub {
-
- @Override
- public double getVal(String val) throws RemoteException {
- Log.v(TAG, "getVal() called for " + val);
- return 1.0;
- }
-
- public List<String> getProcessName() {
-
- List<RunningAppProcessInfo> procList = this.getProcessInfo();
- int j = 0;
- Iterator<RunningAppProcessInfo> iterator = procList.iterator();
-
- if(iterator.hasNext()) {
- do {
- RunningAppProcessInfo procInfo = iterator.next();
-
- Log.v("ProcInfo", "ProcName = " + procInfo.processName);
-
- ProcName.add(procInfo.processName);
-
-
- Log.v("ProcName", "ProcName = " + ProcName.get(j++));
-
- }while(iterator.hasNext());
- }
-
- return ProcName;
- }
-
- public List<String> getProcessID() {
-
- List<RunningAppProcessInfo> procList = this.getProcessInfo();
- int i = 0;
- Iterator<RunningAppProcessInfo> iterator = procList.iterator();
-
- if(iterator.hasNext()) {
- do {
- RunningAppProcessInfo procInfo = iterator.next();
-
- Log.v("ProcInfo","ProcID = " + procInfo.pid);
-
- ProcID.add(String.valueOf(procInfo.pid));
-
-
- Log.v("ProcName", "ProcID = " + ProcID.get(i++));
-
- }while(iterator.hasNext());
- }
-
- return ProcID;
- }
-
- @Override
- public String killProc(String PID) throws RemoteException {
-
- String cmd = "kill -9 "+PID;
- String reply = "";
-
- Log.v("cmd",cmd);
- try {
-
- CommandHelper.DEFAULT_TIMEOUT = 5000;
- CommandResult result = CommandHelper.exec(cmd);
- if (result != null) {
- if(result.getError()!=null)
- {
- Log.v("Output","Error:" + result.getError());
- reply = result.getError();
- }
- if(result.getOutput()!=null)
- {
- Log.v("Output","Output:" + result.getOutput());
- reply = result.getOutput();
- }
- }
-
- } catch (IOException ex) {
- Log.v("Output","IOException:" + ex.getLocalizedMessage());
- } catch (InterruptedException ex) {
- Log.v("Output","InterruptedException:" + ex.getLocalizedMessage());
- }
- return reply;
-
-
- }
-
- public void exec(String command) throws IOException, InterruptedException {
-
- Runtime.getRuntime().exec(command);
- return ;
- }
-
- public List<RunningAppProcessInfo> getProcessInfo() {
- List<RunningAppProcessInfo> procList = new ArrayList<RunningAppProcessInfo>();
-
-
- ActivityManager activityManager = (ActivityManager) getSystemService("activity");
- procList = activityManager.getRunningAppProcesses();
-
- return procList;
- }
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- Log.v(TAG, "onCreate called");
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.v(TAG, "onDestory() called");
- }
-
- @Override
- public void onStart(Intent intent, int startId) {
- super.onStart(intent, startId);
- Log.v(TAG, "onStart() called");
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- Log.v(TAG, "onBind() called");
- return new PMServiceImpl();
- }
-
- }
AndroidManifest.xml配置文件
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.pmserver"
- android:versionCode="1"
- android:versionName="1.0" >
-
- <uses-sdk
- android:minSdkVersion="8"
- android:targetSdkVersion="17" />
-
- <application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@style/AppTheme" >
- <service
- android:name="com.example.pmserver.PMService"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="com.example.pmserver.IPMService" />
- </intent-filter>
- </service>
- </application>
-
- </manifest>
Service这边不需要显示操作,界面的配置就不需要了。这样,Service这边就OK了。
PMClient的源码
这个是PMClient工程的文件清单,
IPMService.aidl直接从PMService中拷贝过来就好,这里就不列出了。同样,会自动生成IPMService.java
CommandHelper.java和CommandResult.java这里不需要(这里,我只是做测试之用),不需要添加,在com.example.pmclient中,只需添加PMClientActivity.java即可。
PMClientActivity.java
- package com.example.pmclient;
-
- import com.example.pmserver.IPMService;
- import com.example.pmclient.CommandHelper;
- import com.example.pmclient.CommandResult;
-
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import android.os.Bundle;
- import android.os.IBinder;
- import android.app.Activity;
- import android.view.View;
- import android.widget.AdapterView;
- import android.widget.ListView;
- import android.widget.SimpleAdapter;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.os.RemoteException;
- import android.util.Log;
- import android.widget.Toast;
- import android.widget.AdapterView.OnItemClickListener;
-
- public class PMClientActivity extends Activity {
-
- protected static final String TAG = "TestaidlClient";
- private IPMService PMService = null;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.list_main);
- bindService(new Intent(IPMService.class.getName()),serConn, Context.BIND_AUTO_CREATE);
- }
-
- public void onDestroy(){
- super.onDestroy();
- Log.v(TAG, "onDestory() called");
- unbindService(serConn);
- }
-
- private void callService() {
- try {
- double val = PMService.getVal("Liang");
- if(val == 1.0) {
- Toast.makeText(this, "Service is ready!",
- Toast.LENGTH_LONG).show();
- }
- setNameID(PMService.getProcessID(), PMService.getProcessName());
-
- } catch (RemoteException e) {
- Log.e("MainActivity", e.getMessage(), e);
- }
- }
-
- private ServiceConnection serConn = new ServiceConnection() {
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- Log.v(TAG, "onServiceConnected() called");
- PMService = IPMService.Stub.asInterface(service);
- callService();
- }
-
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- Log.v(TAG, "onServiceDisconnected()");
- PMService = null;
- }
- };
-
-
- public void setNameID(final List<String> ProcID, final List<String> ProcName) {
-
- ListView list = (ListView) findViewById(R.id.ListView01);
-
-
- ArrayList<Map<String, String>> Items = new ArrayList<Map<String, String>>();
-
-
- for (int i = 0; i < ProcID.size(); i++)
- {
- Map<String, String> item = new HashMap<String, String>();
- String PIDbuf = "PID: "+ProcID.get(i);
- item.put("ProcID", PIDbuf);
- String PNamebuf = "PName: "+ProcName.get(i);
- item.put("ProcName", PNamebuf);
- Items.add(item);
- }
-
-
- final SimpleAdapter simpleAdapter = new SimpleAdapter(this, Items,
- R.layout.list_proc_info, new String[]{ "ProcID", "ProcName" },
- new int[]{ R.id.ProcID, R.id.ProcName});
-
-
- list.setAdapter(simpleAdapter);
-
- list.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
- long arg3) {
-
- String result = "";
-
- Log.v("ClickInfo", "arg0 = " + arg0 + "arg1 = " + arg1 + " arg2 = " + arg2 +" arg3 = " + arg3);
-
-
- try {
- result = PMService.killProc(ProcID.get(arg2));
- } catch (RemoteException e) {
-
- e.printStackTrace();
- }
- if(result == null)
- {
- result = "success!";
- }
-
- ToastMSG(result);
-
-
- simpleAdapter.notifyDataSetChanged();
- }
- });
-
-
- }
-
- public void ToastMSG(String info)
- {
- Toast.makeText(this, "Info: " + info,
- Toast.LENGTH_LONG).show();
- }
-
- }
再来看布局的设置,一共有2个布局文件,在res/layout/中:
list_main.xml
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout
- android:id="@+id/LinearLayout01"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- xmlns:android="http://schemas.android.com/apk/res/android">
- <ListView android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/ListView01"
- android:background="#A9A9A9"/>
- </LinearLayout>
list_proc_info.xml
- <?xml version="1.0" encoding="utf-8"?>
- <!-- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content">
- <TextView android:id="@+id/ProcID"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:textSize="16dp"
- android:gravity="center_vertical"
- android:paddingLeft="10dp" />
-
- <TextView android:id="@+id/ProcName"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:textSize="16dp"
- android:gravity="center_vertical"
- android:paddingLeft="10dp" />
-
- </LinearLayout>
- -->
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
-
-
- <ImageView android:id="@+id/Procimg"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="5px"
- android:src="@drawable/ic_launcher"/>
-
- <LinearLayout android:orientation="vertical"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <TextView android:id="@+id/ProcID"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="#FFFFFFFF"
- android:textSize="40px" />
- <TextView android:id="@+id/ProcName"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="#FFFFFFFF"
- android:textSize="30px" />
-
- </LinearLayout>
-
-
- </LinearLayout>
最后,是配置文件:
AndroidManifest.xml
- <?xml version="1.0" encoding="utf-8"?>
-
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-
- package="com.example.pmclient"
- android:versionCode="1"
- android:versionName="1.0" >
-
- <uses-sdk
- android:minSdkVersion="8"
- android:targetSdkVersion="17"
- android:sharedUserId="android.uid.system" />
-
- <application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@style/AppTheme" >
- <activity
- android:name="com.example.pmclient.PMClientActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
- </manifest>
关于布局文件的字段名(@XXX),这里没有给出对应的配置文件,因为太简单了!请大家自行设置。
大功告成了!上效果图!
界面比较简单,单击对应的进程即可杀掉进程。这里由于Service的权限不够,导致进程不能结束。另外,杀掉进程后(可以尝试杀掉PMService自己,界面会自刷新)。