本章主要内容
进程间通信是指什么?
Linux下进程之间如何通信?
Android中进程间通信采用什么方式?
Android为什么要引入Binder机制?
Binder机制包括哪些部分?
Binder的工作原理是什么?
如何实现Binder驱动?
如何使用Binder机制来完成进程间通信?
Android的多媒体服务MediaService如何工作?
如何实现自己的核心服务?
在Linux系统中,是以进程为单位分配和管理资源的。出于保护机制,一个进程不能直接访问另一个进程的资源,也就是说,进程之间互相封闭。但是,在一个复杂的应用系统中,通常会使用多个相关的进程来共同完成一项任务,因此要求进程之间必须能够互相通信,从而共享资源和信息。所以,操作系统内核必须提供进程间的通信机制(IPC)。在Linux中,进程间的通信机制有很多种,例如可以采用命名管道(named pipe)、消息队列(message queue)、信号(signal)、共享内存(share memory)、socket等方式,它们都可以实现进程间的通信。但是,在Android终端上的应用软件的通信几乎看不到这些IPC通信方式,取而代之的是Binder方式。Android同时为Java环境和C/C++环境提供了Binder机制。本章主要介绍C/C++环境下的Binder机制,主要包括Binder驱动的实现、运作原理、IPC机制的实现、接口等,所以本章可能会涉及部分系统运行库的源代码,我们将详细讲解。
3.1 Binder概述
应用程序虽然是以独立的进程来运行的,但相互之间还是需要通信,比如,在多进程的环境下,应用程序和后台服务通常会运行在不同的进程中,有着独立的地址空间,但是因为需要相互协作,彼此间又必须进行通信和数据共享,这就需要进程通信来完成。在Linux系统中,进程间通信的方式有socket、named pipe、message queue、signal、share memory等;Java系统中的进程间通信方式也有socket、named pipe等,所以Android可以选择的进程间通信的方式也很多,但是它主要包括以下几种方式:
标准Linux Kernel IPC接口
标准D-BUS接口
Binder接口
3.1.1 为什么选择Binder
在上面这些可供选择的方式中,Android使用得最多也最被认可的还是Binder机制。为什么会选择Binder来作为进程之间的通信机制呢?因为Binder更加简洁和快速,消耗的内存资源更小吗?不错,这些也正是Binder的优点。当然,也还有很多其他原因,比如传统的进程间通信可能会增加进程的开销,而且有进程过载和安全漏洞等方面的风险,Binder正好能解决和避免这些问题。Binder主要能提供以下一些功能:
用驱动程序来推进进程间的通信。
通过共享内存来提高性能。
为进程请求分配每个进程的线程池。
针对系统中的对象引入了引用计数和跨进程的对象引用映射。
进程间同步调用。
3.1.2 初识Binder
Binder是通过Linux的Binder Driver来实现的,Binder操作类似于线程迁移(thread migration),两个进程间通信看起来就像是一个进程进入另一个进程去执行代码,然后带着执行的结果返回。Binder的用户空间为每一个进程维护着一个可用的线程池,线程池用于处理到来的IPC以及执行进程的本地消息,Binder通信是同步的而不是异步的。同时,Binder机制是基于OpenBinder①来实现的,是一个OpenBinder的Linux实现,Android系统的运行都将依赖Binder驱动。
Binder通信也是基于Service与Client的,所有需要IBinder通信的进程都必须创建一个IBinder接口。系统中有一个名为Service Manager的守护进程管理着系统中的各个服务,它负责监听是否有其他程序向其发送请求,如果有请求就响应,如果没有则继续监听等待。每个服务都要在Service Manager中注册,而请求服务的客户端则向Service Manager请求服务。在Android虚拟机启动之前,系统会先启动Service Manager进程,Service Manager就会打开Binder驱动,并通知Binder Kernel驱动程序,这个进程将作为System Service Manager,然后该进程将进入一个循环,等待处理来自其他进程的数据。因此,我们也可以将Binder的实现大致分为:Binder驱动、Service Manager、Service、Client这几个部分,下面将分别对这几个部分进行详细分析。
3.2 Binder驱动的原理和实现
通过上一节的介绍,大家应该对Binder有了基本的认识了。任何上层应用程序接口和用户操作都需要底层硬件设备驱动的支持,并为其提供各种操作接口。本节首先从Binder的驱动实现入手,分析其原理和它提供给用户层使用的接口。
3.2.1 Binder驱动的原理
为了完成进程间通信,Binder采用了AIDL(Android Interface Definition Language)来描述进程间的接口。在实际的实现中,Binder是作为一个特殊的字符型设备而存在的,设备节点为/dev/binder,其实现遵循Linux设备驱动模型,实现代码主要涉及以下文件:
kernel/drivers/staging/binder.h
kernel/drivers/staging/binder.c
在其驱动的实现过程中,主要通过binder_ioctl函数与用户空间的进程交换数据。BINDER_WRITE_READ用来读写数据,数据包中有一个cmd域用于区分不同的请求。binder_thread_write函数用于发送请求或返回结果,而binder_thread_read函数则用于读取结果。在binder_thread_write函数中调用binder_transaction函数来转发请求并返回结果。当收到请求时,binder_transaction函数会通过对象的handle找到对象所在的进程,如果handle为空,就认为对象是context_mgr,把请求发给context_mgr所在的进程。请求中所有的Binder对象全部放到一个RB树中,最后把请求放到目标进程的队列中,等待目标进程读取。数据的解析工作放在binder_parse()中实现;关于如何生成context_mgr,内核中提供了BINDER_SET_CONTEXT_ MGR命令来完成此项功能。下面我们就来看看Binder驱动究竟是如何实现的。
3.2.2 Binder驱动的实现(1)
上面我们已经对Binder驱动的原理进行了分析,在开始分析驱动的实现之前,我们还是通过一个例子来说明Binder在实际应用中应该如何运用,以及它能帮我们解决什么样的问题。这样会更容易帮助大家理解Binder驱动的实现。比如,A进程如果要使用B进程的服务,B进程首先要注册此服务,A进程通过Binder获取该服务的hanlde,通过这个handle,A进程就可以使用该服务了。此外,你可以把handle理解成地址。A进程使用B进程的服务还意味着二者遵循相同的协议,这个协议反映在代码上就是二者要实现IBinder接口。
1.“对象”与“引用”
Binder不仅是Android系统中的一个完善的IPC机制,它也可以被当作Android系统的一种RPC(远程过程调用)机制,因为Binder的功能就是在本地“执行”其他进程的功能。因此,进程在通过Binder获取将要调用的进程服务时,可以是一个本地对象,也可以是一个远程服务的“引用”。这一点可能比较难以理解,稍候就会为大家分析,这里就先记住Binder不仅可以与本地进程通信,还可以与远程进程通信;这里的本地进程就是我们所说的本地对象,而远程进程则是我们所说的远程服务的一个“引用”。①
Binder的实质就是要把对象从一个进程映射到另一个进程中,而不管这个对象是本地的还是远程的。如果是本地对象,更好理解;如果是远程对象,就按照我们上面所讲的来理解,即将远程对象的“引用”从一个进程映射到另一个进程中,于是当使用这个远程对象时,实际上就是使用远程对象在本地的一个“引用”,类似于把这个远程对象当作一个本地对象在使用。这也就是Binder与其他IPC机制不同的地方。
这个本地“对象”与远程对象的“引用”有什么不同呢?本地“对象”表示本地进程的地址空间的一个地址,而远程对象的“引用”则是一个抽象的32位句柄。它们之间是互斥的:所有的进程本地对象都是本地进程的一个地址(address、ptr、binder),所有的远程进程的对象的“引用”都是一个句柄。对于发送者进程来说,不管是“对象”还是“引用”,它都会认为被发送的Binder对象是一个远程对象的句柄(即远程对象的“引用”)。但是,当Binder对象的数据被发送到远端接收进程时,远端接收进程则会认为该Binder对象是一个本地对象地址(即本地对象)。正如我们之前说的,当Binder对象被接收进程接收后,不管该Binder对象是本地的还是远程的,它都会被当作一个本地进程来处理。因此,从第三方的角度来说,尽管名称不同,对于一次完整的Binder调用,都将指向同一个对象,Binder驱动则负责两种不同名称的对象的正确映射,这样才能把数据发送给正确的进程进行通信。这个映射关系也是进程间引用对象的基础,对一个对象的引用,在远程是句柄,在本地则是地址(即本地对象的地址)。
下面我们先介绍分析该机制中所使用的数据结构体,然后再对整个流程进行分析。
2.binder_work
首先来看一个最简单也是最基础的结构体binder_work,其定义如代码清单3-1所示。
代码清单3-1 binder_work定义
其中entry被定义为list_head,用来实现一个双向链表,存储所有binder_work的队列;此外,还包含一个enum类型的type,表示binder_work的类型,后文会对这些类型进行详细分析,大家就会觉得它更像是一个用来表示状态的enum。
3.Binder的类型
Binder的类型是使用定义在binder.h头文件中的一个enum来表示的,定义如代码清单3-2所示。
代码清单3-2 Binder类型
从上面的代码可以看出,Binder被分成了5个不同的类型,但是仔细一看却是3个不同的大类,它们分别是:本地对象(BINDER_TYPE_BINDER、BINDER_TYPE_WEAK_BINDER)、远程对象的“引用”(BINDER_TYPE_HANDLE、BINDER_TYPE_WEAK_HANDLE)和文件(BINDER_TYPE_FD)。前面两种都是我们刚刚分析过的,下面主要分析最后一种——文件(BINDER_TYPE_FD)。如果传递的是BINDER_TYPE_FD类型,其实还是会将文件映射到句柄上,根据此fd找到对应的文件,然后在目标进程中分配一个fd,最后把这个fd赋值给返回的句柄。
4.Binder对象
我们把进程之间传递的数据称之为Binder对象(Binder Object),它在对应源码中使用flat_binder_object结构体(位于binder.h文件中)来表示,其定义如代码清单3-3所示。
代码清单3-3 flat_binder_object定义
该结构体中的type字段描述的是Binder的类型,传输的数据是一个复用数据联合体。对于Binder类型,数据就是一个Binder本地对象;HANDLE类型,就是一个远程的handle句柄。本地Binder对象和远程handle句柄比较难以理解,这里我们再次举例说明:假如A有个对象O,对于A来说,O就是一个本地的Binder对象;如果B想访问A的O对象,对于B来说,O就是一个handle。因此,从根本上来说,handle和Binder都指向O。如果是本地对象,Binder还可以带有额外的数据,这些数据将被保存到cookie字段中。flags字段表示传输方式,比如同步和异步等,其值同样使用一个enum来表示,定义如代码清单3-4所示。
代码清单3-4 transaction_flags定义
其中TF_ONE_WAY表示单向传递,是异步的,不需要返回;TF_ROOT_OBJECT表示内容是一个组建的根对象,对应类型为本地对象Binder;TF_STATUS_CODE表示内容是一个32位的状态码,将对应类型为远程对象的“引用”(即句柄handle);TF_ACCEPT_FDS表示可以接收一个文件描述符,对应的类型为文件(BINDER_TYPE_FD),即handle中存储的为文件描述符。
3.2.2 Binder驱动的实现(2)
5.binder_transaction_data
其实我们并没有从flat_binder_object结构体中看到Binder对象所传递的实际内容,因为Binder对象所传递的实际内容是通过另外一个结构体binder_transaction_data来表示的,其定义如代码清单3-5所示。
代码清单3-5 binder_transaction_data定义
该结构体是理解Binder驱动实现的关键,下面将详细地对一个重要的数据进行分析。其中target字段又是一个复合联合体对象,target字段中的handle是要处理此事件的目标对象的句柄,根据此handle,Binder驱动可以找到应该由哪个进程处理此事件,并且把此事件的任务分发给一个线程,而那个线程也正在执行ioctl的BINDER_WRITE_READ操作,即正在等待一个请求(见3.1节和3.2.1节),处理方法将稍候分析。target的ptr字段与handle对应,对于请求方,使用handle来指出远程对象;对于响应方,使用ptr来寻址,以便找到需要处理此事件的对象。所以handle和ptr是一个事物的两种表达(正如前面所说的本地对象和远程对象的“引用”),handle和ptr之间的翻译(解析)关系正是Binder驱动需要维护的(在binder_transaction函数中,稍候分析)。
另外,该结构体中的cookie字段表示target对象所附加的额外数据;code是一个命令,它描述了请求Binder对象执行的操作;flags字段描述了传输的方式与flat_binder_object中的flags字段对应;sender_pid和sender_euid表示该进程的pid和uid;data_size表示数据的大小字节数;offsets_size表示数据的偏移量字节数;最后一个union数据data表示真正的数据,其中ptr表示与target->ptr对应的对象的数据,buf表示与handle对象对应的数据,data中的ptr中的buffer表示实际的数据,而offsets则表示其偏移量。
6.binder_write_read
上面我们说过,当Binder驱动找到处理此事件的进程之后,Binder驱动就会把需要处理的事件的任务放在读缓冲(binder_write_read)里,返回给这个服务线程,该服务线程则会执行指定命令的操作;处理请求的线程把数据交给合适的对象来执行预定操作,然后把返回结果同样用binder_transaction_data结构封装,以写命令的方式传回给Binder驱动,并将此数据放在一个读缓冲(binder_write_read)里,返回给正在等待结果的原进程(线程),这样就完成了一次通信。这里所说的BINDER_WRITE_READ命令和读写缓冲区分别什么呢?从下面这个宏中可以看到结果:
该命令是ioctl函数中的一个操作,从上面的宏中可以看出,其所对应的参数是binder_ write_read结构体,该结构体正好就是我们所说的读写缓冲区,将用它来存储发送的任务信息和接收返回的结果信息,其定义如代码清单3-6所示。
代码清单3-6 binder_write_read定义
上面的代码中分别指定了读缓冲区和写缓冲区,对于写操作,write_buffer包含了一系列请求线程执行的Binder命令;对于读(返回结果)操作,read_buffer包含了一系列线程执行后填充的返回值。这些读和写的操作命令都需要遵循一定的Binder协议。write_size和read_size分别表示写入和读取的数据的大小;write_consumed和read_consumed则分别表示被消耗的写数据和读数据的大小。下面我们看看BINDER_WRITE_READ命令具体有哪些,如代码清单3-7所示。
代码清单3-7 BINDER_WRITE_READ命令协议
其中最重要的就是BC_TRANSACTION和BC_REPLY命令,它们被作为发送操作的命令,其数据参数都是binder_transaction_data结构体;前者用于翻译和解析将要被处理的事件数据,后者则是事件处理完成之后对返回“结果数据”的操作命令。我们稍后在分析binder_ioctl的实现时,会对这些命令的作用进行详细的分析。
3.2.2 Binder驱动的实现(3)
7.binder_proc
binder_proc结构体用于保存调用Binder的各个进程或线程的信息,比如线程ID、进程ID、Binder状态信息等,定义如代码清单3-8所示。
代码清单3-8 binder_proc定义
对于其中几个重要的字段,我们都给出了注解,在具体的使用过程中,我们还会进一步分析。其中proc_node字段用于实现双向链表,threads则用于储存所有的线程信息。紧接着我们就需要分析Binder节点和Binder线程的具体定义了。
8.binder_node
该结构体表示一个Binder节点,其定义如代码清单3-9所示。
代码清单3-9 binder_node定义
9.binder_thread
我们已经知道,binder_thread结构体用于存储每一个单独的线程的信息,其定义如代码清单3-10所示。
代码清单3-10 binder_thread定义
其中,proc字段表示当前线程属于哪一个Binder进程(binder_proc指针);rb_node是一个红黑树节点;pid表示线程的pid;looper表示线程的状态信息;transaction_stack则定义了要接收和发送的进程和线程信息,其结构体为binder_transaction,稍候我们会进行详细的分析;todo用于创建一个双向链表;return_error和return_error2为返回的错误信息代码;wait是一个等待队列头;stats用于表示Binder状态信息,其定义如代码清单3-11所示。
代码清单3-11 binder_stats定义
其中,br用来存储BINDER_WRITE_READ的写操作命令协议(Binder Driver Return Protocol);bc则对应存储着BINDER_WRITE_READ的写操作命令协议(Binder Driver Command Protocol);obj_created用于保存BINDER_STAT_COUNT的对象计数,当一个对象被创建时,就需要同时调用该成员来增加相应的对象计数,而obj_deleted则正好相反。
另外,looper所表示的线程状态信息主要包括以下几种,如代码清单3-12所示。
代码清单3-12 Binder looper线程状态信息
其中主要包括了注册、进入、退出、销毁、等待、需要返回这几种状态信息。
3.2.2 Binder驱动的实现(4)
10.binder_transaction
该结构体主要用来中转请求和返回结果,保存接收和要发送的进程信息,其定义如代码清单3-13所示。
代码清单3-13 binder_transaction定义
其中work为一个binder_work;from和to_thread都是一个binder_thread对象,用于表示接收和要发送的进程信息,另外还包括了接收和发送进程信息的父节点from_parent和to_thread;to_proc是一个binder_proc类型的结构体。其中还包括flags、need_reply、优先级(priority)等数据。这里我们需要重点说明的是最后一个sender_euid,Linux系统中每个进程都有2个ID——用户ID和有效用户ID,UID一般表示进程的创建者(属于哪个用户创建),而EUID表示进程对于文件和资源的访问权限,因此这里的sender_euid表示要发送进程对文件和资源的操作权限。最后,该结构体中还包含类型类inder_buffer的一个buffer,用来表示binder的缓冲区信息,其定义如代码清单3-14所示。
代码清单3-14 binder_buffer的定义
该结构体主要用来储存Binder的相关信息,entry同样用于构建一个双向链表;rb_node为一个红黑树节点;transaction表示上面我们刚刚说到的binder_transaction,用于中转请求和返回结果;target_node是一个目标节点;data_size表示数据的大小;offsets_size是一个偏移量;data[0]用于存储实际数据。
11.binder_init
和其他驱动一样,我们先从初始化操作开始分析,大家可以在binder.c中找到该初始化函数,具体实现如代码清单3-15所示。
代码清单3-15 binder_init的实现
其中,binder_init是Binder驱动的初始化函数,初始化函数一般需要设备驱动接口来调用。Android Binder设备驱动接口函数是device_initcall,这与上一章提到的设备驱动的接口函数是module_init和module_exit不一样。通常来说,使用module_init和module_exit是为了同时兼容支持静态编译的驱动模块(buildin)和动态编译的驱动模块(module),但是Binder选择使用device_initcall的目的就是不让Binder驱动支持动态编译,而且需要在内核(Kernel)做镜像。initcall用于注册进行初始化的函数;如果你的确需要将Binder驱动修改为动态的内核模块,可以直接将device_initcall修改为module_init,但不要忘了增加module_exit的驱动卸载接口函数。
首先,初始化函数使用proc_mkdir创建了一个Binder的proc文件系统的根节点(binder_ proc_dir_entry_root,/proc/binder),如果根节点创建成功,紧接着为binder创建binder proc节点(binder_proc_dir_entry_proc,/proc/binder/proc);然后,Binder驱动使用misc_register把自己注册为一个Misc设备,其设备节点位于/dev/binder,该节点由init进程在handle_device_fd(device_fd)函数中调用handle_device_event(&uevent)函数执行其中uevent-netlink事件在"/dev/"目录下创建。
最后,调用create_proc_read_entry创建以下只读proc文件:
/proc/binder/state
/proc/binder/stats
/proc/binder/transactions
/proc/binder/transaction_log
/proc/binder/failed_transaction_log
在创建这些文件的过程中,同时也指定了操作这些文件的函数及其参数,如表3-1所示。
表3-1 /proc/binder/目录下对应的文件操作函数及其参数
另外,在注册Binder驱动为Misc设备时,指定了Binder驱动的miscdevice,具体实现如代码清单3-16所示。
代码清单3-16 binder_miscdev的实现
Binder设备的主设备号为10,此设备号是动态获得的,.minor被设置为动态获得设备号MISC_DYNAMIC_MINOR;.name代表设备名称。最后,指定了该设备的file_operations结构体,定义如代码清单3-17所示。
代码清单3-17 file_operations的定义
任何驱动程序都有向用户空间的程序提供操作接口这一主要功能,这个接口是标准的,对于Android Binder驱动来说,除了提供如表3-1所示的操作其只读文件的接口之外,还提供了操作设备文件(/dev/binder)的接口。正如binder_fops所描述的file_operations结构体一样,其中主要包括了binder_poll、binder_ioctl、binder_mmap、binder_open、binder_flush、binder_release等标准操作接口。
下面我们将分别对Android Binder驱动所提供的这些操作接口进行分析。
3.2.2 Binder驱动的实现(5)
12.binder_open
binder_open函数用于打开Binder设备文件/dev/binder,对于Android驱动,任何一个进程及其内的所有线程都可以打开一个Binder设备,其打开过程的实现如代码清单3-18所示。
代码清单3-18 binder_open的实现
从上述代码中我们可以看出:
1)需要创建并分配一个binder_proc空间来保存Binder数据。
2)增加当前线程/进程的引用计数,并赋值给binder_proc的tsk字段。
3)初始化binder_proc队列,其中主要包括使用INIT_LIST_HEAD初始化链表头todo,使用init_waitqueue_head初始化等待队列wait,设置默认优先级(default_priority)为当前进程的nice值(通过task_nice得到当前进程的nice值)。
4)增加BINDER_STAT_PROC的对象计数,并通过hlist_add_head把创建的binder_proc对象添加到全局的binder_proc哈希表中,这样一来,任何一个进程就都可以访问到其他进程的binder_proc对象了。
5)把当前进程(或线程)的线程组的pid(pid指向线程id)赋值给proc的pid字段,可以理解为一个进程id(thread_group指向线程组中的第一个线程的task_struct结构),同时把创建的binder_proc对象指针赋值给filp的private_data对象并保存起来。
6)在binder proc目录中创建只读文件/proc/binder/proc/$pid,用来输出当前binder proc对象的状态,文件名以pid命名,但需要注意的是,该pid字段并不是当前进程/线程的id,而是线程组的pid,也就是线程组中第一个线程的pid(因为我们上面是将current->group_leader->pid赋值给该pid字段的)。另外,在创建该文件时,同样也指定了操作该文件的函数接口为binder_read_proc_proc,其参数正是我们创建的binder_proc对象proc,对于该接口的实现,后文将进行分析。
13.binder_release
binder_release函数与binder_open函数的功能相反,当Binder驱动退出时,需要使用它来释放在打开以及其他操作过程中分配的空间并清理相关的数据信息,其实现如代码清单3-19所示。
代码清单3-19 binder_release的定义
该函数的实现非常简单,首先取得private_data数据的使用权,找到当前进程、线程的pid,这样就能得到在open过程中创建的以pid命名的用来输出当前binder proc对象的状态的只读文件;然后调用remove_proc_entry函数来进行删除;最后通过binder_defer_work函数和其参数BINDER_DEFERRED_RELEASE来释放整个binder_proc对象的数据和分配的空间。你可能会问,为什么不直接在这里释放,而是通过调用一个workqueue来释放呢?因为Binder的release和flush等操作比较复杂,而且也没有必要在系统调用里完成它,因此最好的方法是延迟执行这个任务,所以这里选择使用workqueue(deferred)来提高系统的响应速度和性能。BINDER_DEFERRED_RELEASE参数位于一个enum中,如代码清单3-20所示。
代码清单3-20 BINDER_DEFERRED_RELEASE参数的定义
也就是说,我们除了在release时使用该方式之外,还可以在执行putfile和flush等操作时使用该方式。关于释放binder_proc对象的函数binder_defer_work,我们将在完成对设备文件操作接口的分析之后为大家详细地分析。
3.2.2 Binder驱动的实现(6)
14.binder_flush
flush操作接口将在关闭一个设备文件描述符复制时被调用。该函数的实现十分简单,如代码清单3-21所示。
代码清单3-21 binder_flush的实现
正如上面所说的,通过调用一个workqueue来执行BINDER_DEFERRED_FLUSH操作,从而完成该flush操作,但是最终的处理还是交给了binder_defer_work函数。
15.binder_poll
poll函数是非阻塞型IO的内核驱动实现,所有支持非阻塞IO操作的设备驱动都需要实现poll函数。Binder的poll函数仅支持设备是否可以非阻塞地读(POLLIN),这里有两种等待任务:一种是proc_work,另一种是thread_work。同其他驱动的poll实现一样,这里也是通过调用poll_wait函数来实现的:具体实现如代码清单3-22所示。
代码清单3-22 binder_poll的实现
首先需要取得当前进程/线程的信息,由于所有的线程信息都存储在binder_proc结构体的threads队列中,所以我们可以通过binder_get_thread(proc)来找到当前进程/线程的相关信息,稍后会详细分析查找原理。proc_work和thread_work方式如代码清单3-23所示。
代码清单3-23 proc_work和thread_work的实现
其中,主要是通过检测线程队列是否为空、线程的循环状态以及返回信息来判断所要采用的等待方式,最后通过调用poll_wait函数来实现poll操作。
16.binder_get_thread
该函数可以用于在threads队列中查找当前的进程信息,实现如代码清单3-24所示。
代码清单3-24 binder_get_thread的实现
实现原理是通过在队列中比较各个线程的pid与当前线程的pid是否相同,如果找到了,把它的线程信息返回;如果没找到,新建一个线程并把它加入到队列中,然后初始化就绪线程队列等。代码的注释已经把每一个步骤都讲清楚了。
3.2.2 Binder驱动的实现(7)
17.binder_mmap
mmap(memory map)用于把设备内存映射到用户进程地址空间中,这样就可以像操作用户内存那样操作设备内存。Binder设备对内存映射是有限制的,比如,Binder设备最大能映射4MB的内存区域,Binder不能映射具有写权限的内存区域等。不同于一般的设备驱动,大多设备映射的设备内存是设备本身具有的,或者是在驱动初始化时由vmalloc或kmalloc等内核内存函数分配的,Binder的设备内存是在mmap操作时分配的。分配的方法是:先在内核虚拟映射表上获取一个可以使用的区域,然后分配物理页,并把物理页映射到获取的虚拟空间上。由于设备内存是在mmap操作中实现的,因此每个进程/线程只能执行一次映射操作,其后的操作都会返回错误,具体实现如代码清单3-25所示。
代码清单3-25 binder_mmap的实现
首先介绍该函数的第二个参数vm_area_struct结构体,它在mmp的实现中会用到。为了优化查找方法,内核维护了VMA的链表和树形结构,vm_area_struct中很多成员函数都是用来维护这个结构的。VMA的作用是管理进程地址空间中不同区域的数据结构。该函数首先对内存映射进行检查(主要包括:映射内存的大小、flags以及是否已经映射过了),判断其映射条件是否合法;然后,通过内核函数get_vm_area从系统中申请可用的虚拟内存空间(注意:不是物理内存空间),即在内核中申请并保留一块连续的内核虚拟内存空间区域;接着,将binder_proc的用户地址偏移(即用户进程的VMA地址与Binder申请的VMA地址的偏差)存放到proc->user_buffer_offset中;再接着,使用kzalloc函数根据请求映射的内存空间大小,分配Binder的核心数据结构binder_proc的pages成员,它主要用来保存指向申请的物理页的指针;最后,为VMA指定了vm_operations_struct操作,并且将vma->vm_private_data指向了核心数据proc。到这里,我们就可以真正地开始分配物理内存(page)了,其物理内存的分配是通过binder_update_page_range来实现的。binder_update_page_range的实现主要是一些内核操作函数,我们就不再贴出代码了,只是重点介绍一下该函数所完成的工作:
alloc_page 分配页面
map_vm_area 为分配的内存做映射关系
vm_insert_page 把分配的物理页插入到用户VMA区域
最后,对Binder的binder_mmap进行一个简单的总结,实现步骤如下:
1)检查内存映射条件,包括映射内存大小(4MB)、flags、是否是第一次mmap等。
2)获得地址空间,并把此空间的地址记录在进程信息(buffer)中。
3)分配物理页面(pages)并记录下来。
4)将buffer插入到进程信息的buffer列表中。
5)调用binder_update_page_range函数将分配的物理页面和vm空间对应起来。
6)通过binder_insert_free_buffer函数把此进程的buffer插入到进程信息中。
这里我们再来看一下vm_operations_struct操作的定义,如代码清单3-26所示。
代码清单3-26 binder_vm_ops定义
其中只包括了一个打开操作和一个关闭操作,由于篇幅关系,这里就不再贴出代码了,它们的具体实现大家可以分别参考源码中指定的函数binder_vma_open和binder_vma_close。需要说明的是,打开操作中使用了一个dump_stack函数,它主要用来输出内核的一些相关信息(比如堆栈信息等),这样方便调试;而在关闭函数中再次使用了binder_defer_work函数,但是这里使用的参数则是BINDER_DEFERRED_PUT_FILES。
3.2.2 Binder驱动的实现(8)
18.binder_ioctl
到这里,终于进入Binder最核心的部分了,Binder的功能就是通过ioctl命令来实现的。Binder的ioctl命令共有7个,定义在ioctl.h 头文件中,如代码清单3-27所示。
代码清单3-27 Binder的ioctl命令
在这7个命令中,BINDER_SET_IDLE_TIMEOUT和INDER_SET_IDLE_PRIORITY还没有被实现;BINDER_WRITE_READ是所有Binder的基础,即读写操作;BINDER_SET_ MAX_THREADS用于设置最大线程数目;BINDER_SET_CONTEXT_MGR则被Service Manager用于设置自己作为context manager节点,凡是为0的handle都是指这个master节点;BINDER_THREAD_EXIT用于删除线程信息;BINDER_VERSION很简单,用于返回版本信息。ioctl在前面的驱动分析中我们已经提到过,这里将对其进行更深入的分析。
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等。它的调用函数是“int ioctl(int fd, ind cmd, ...);”,其中,fd就是用户程序打开设备时使用open函数返回的文件标识符;cmd就是用户程序对设备的控制命令;至于后面的省略号,那是一些补充参数,一般最多一个,有或没有与cmd的意义相关。ioctl函数是文件结构中的一个属性分量,也就是说,如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。
如果不用ioctl,也可以实现对设备的I/O通道的控制,但那就太复杂了。例如,我们可以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有,那么后面就在后面附加控制命令(一般在socket编程中常常这样做)。但是,如果这样做,就会导致代码分工不明确,程序结构混乱。所以,我们就使用ioctl来实现控制的功能。要记住,用户程序所做的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情,稍候我们将分析在驱动中如何实现这些命令。
在ioctl函数体内有一个switch{case}结构,每一个case对应一个命令码,它们用于执行相应的操作。因为在ioctl中,命令码是唯一联系用户程序和驱动程序的途径。下面我们来分析命令码是如何组成的。
一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的命令发给错误的设备,这些错误都会导致不可预料的事情发生。所以,在Linux内核中这样定义一个命令码,如表3-2所示。
表3-2 命令码构成
这样一来,一个命令就变成了一个整数形式的命令码。但是,命令码非常不直观,所以Linux内核中提供了一些宏,这些宏可以根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串,以标明这个命令对应的设备类型、设备序列号、数据传送方向和传输数据大小。幻数是一个字母,数据长度也是8,所以就用一个特定的字母来标明设备类型,这与使用数字的原理是一样的,只是更加利于记忆和理解。
正如上面所述,每个命令都被定义成一个_IOW宏,关于_IOW宏的定义大家可以在“bionic\libc\kernel\common\asm-generic\Ioctl.h”中找到,如代码清单3-28所示。
代码清单3-28 _IOW宏的定义
现在我们应该已经明白了ioctl的深层次含义,下面就来对这些命令的实现一一进行分析。
(1)BINDER_VERSION
该命令的实现过程如代码清单3-29所示:
代码清单3-29 BINDER_VERSION命令的实现
其中BINDER_CURRENT_PROTOCOL_VERSION表示当前的版本信息,定义于binder.h中。binder_version表示版本信息的一个结构体,其成员protocol_version则表示具体的版本信息。
3.2.2 Binder驱动的实现(9)
(2)BINDER_SET_MAX_THREADS
该命令用于设置进程的Binder对象所支持的最大线程数,进程会根据该数目来决定线程池的容量。设置的值保存在binder_proc结构的max_threads成员里,实现如代码清单3-30所示。
代码清单3-30 BINDER_SET_MAX_THREADS命令的实现
(3)BINDER_THREAD_EXIT
该命令用于释放相应的线程信息,其具体过程在binder_free_thread函数中实现,用来终止并释放binder_thread对象及其binder_transaction事务。由于该接口涉及一些事务相关的内容,因此我们在后面具体为大家分析。
(4)BINDER_SET_CONTEXT_MGR
如果一个进程(或线程)能被成功设置成binder_context_mgr_node对象,那么称这个进程为Context Manager(context_mgr)。该命令就是将一个线程/进程设置为Context Manager,也就是设置驱动中的全局变量binder_context_ mgr_uid为当前进程的uid,并初始化一个binder_node并赋值给全局变量binder_context_mgr_ node。该命令一般是在系统启动时初始化Binder驱动的过程中被调用,并且也只有创建binder_context_mgr_node对象的Binder上下文管理进程/线程才有权限重新设置这个对象。该命令的实现如代码清单3-31所示:
代码清单3-31 BINDER_SET_CONTEXT_MGR命令的实现
从上面的代码中可以看出,首先检测binder_context_mgr_node是否为NULL,然后检测binder_context_mgr_uid是否存在。如果存在,继续检测它是否是当前进程的uid(这里主要检查当前进程是否有操作该命令的权限);如果不存在,就对binder_context_mgr_uid进行赋值,取得当前进程对文件和资源的操作权限,即euid。最后,通过binder_new_node函数创建一个binder_node节点,并对该binder_node节点执行初始化操作。下面我们分析如何创建这个binder_node节点,其具体实现如代码清单3-32所示。
代码清单3-32 binder_new_node的实现
binder_proc的成员node是binder_node的根节点,这是一棵红黑树(一种平衡二叉树)。该函数首先根据规则找到第一个叶节点作为新插入的节点的父节点,然后创建binder_node节点并插入。这里需要说明一下,rb_link_node和rb_insert_color都是内核红黑树函数。rb_link_node是一个内联函数,它用于将新节点插入到红黑树中的指定父节点下。rb_insert_color则是把已经插入到红黑树中的节点调整并融合到红黑树中。最后,执行数据初始化和初始化该节点的链表头,其中node->proc保存着binder_proc对象指针。
3.2.2 Binder驱动的实现(10)
(5)BINDER_WRITE_READ
该命令才是Binder最核心的部分,Binder的IPC机制就是通过这个接口来实现的,具体实现如代码清单3-33所示。
代码清单3-33 BINDER_WRITE_READ命令的实现
该部分的实现很简单,首先检查其数据是否完整,然后从用户空间复制数据到binder_write_read结构体中,最后通过write_size和bwr.read_size来判断需要执行的操作。当然,最终的操作会通过inder_thread_write和binder_thread_read函数来实现。稍候我们将分析具体的读写操作,处理完成之后再将数据复制到用户空间即可。
现在我们已经熟悉了这些命令的操作,在ioctl函数中是先通过wait_event_interruptible函数来修改task的状态为TASK_INTERRUPTIBLE,使得该进程将不会继续运行,直到它被唤醒,然后添加到等待队列binder_user_error_wait(该函数的第一个参数)中。
wait_event_interruptible的实现也很简单,如代码清单3-34所示。
代码清单3-34 wait_event_interruptible的实现
判断是否满足condition条件,如果是,则直接返回0;否则,调用函数__wait_event_interruptible(),并用__ret来存放返回值。
现在我们就来分析前面提到的一些处理方法和其他几个比较底层的函数。首先是前面已经说过多次的释放binder_proc对象的函数binder_defer_work,其定义如代码清单3-35所示。
代码清单3-35 binder_defer_work的实现
操作一目了然,这里使用了schedule_work来调度一个线程去执行我们需要执行的操作。接下来分析释放线程的函数binder_free_thread,其定义如代码清单3-36所示。
3.2.2 Binder驱动的实现(11)
代码清单3-36 binder_free_thread的实现
该函数所执行的操作是:首先,将当前要删除的线程从红杉树上删除;然后,取得其binder_transaction数据,并且通过to_thread和fram来判断线程的类型。如果需要回复,那么在释放完成之后就通过binder_send_failed_reply发送一个失败的回复,在释放binder_transaction时需要释放每个节点。这里,释放binder_work需要使用binder_release_work函数,其定义如代码清单3-37所示。
代码清单3-37 binder_release_work的实现
其中,同样对其类型进行了判断,如果是BINDER_WORK_TRANSACTION类型,则需要发送BR_DEAD_REPLY回复;如果为BINDER_WORK_TRANSACTION_COMPLETE类型,则直接释放,然后改变其状态即可。
通过前面的学习,我们对Binder的整个工作流程有了一个深入的认识,关于Binder的实现,还涉及有很多的细节,大家可以仔细阅读Binder驱动的源代码进一步了解。
3.3 Binder的构架与实现
上一节分析了Android中的IPC机制——Binder驱动的实现。我们知道Binder在Android中占据着重要地位,需要完成进程之间通信的所有应用程序都使用了Binder机制,所以Android也对Binder驱动所提供的接口进行了封装,同时还在Android的工具库中提供了一套Binder库,这正是本节需要分析的内容。
3.3.1 Binder的系统构架
在分析Binder的系统构架之前,首先举例说明Binder的用处。在Android的设计中,每个Activity都是一个独立的进程,每个Service也是一个独立的进程,而Activity要与Service进行通信,就是跨进程的通信,这时就需要使用Binder机制了。这里可以把Activity看作一个客户端,把Service看作一个服务端,实际上也就是一个客户端与服务端之间的通信,具体的通信流程则由Binder来完成。
1.Binder机制的组成
Android的Binder机制就是一个C/S构架,客户端和服务端直接通过Binder交互数据,打开Binder写入数据,通过Binder读取数据,这样通讯就可以完成了。数据的读写是由上一节所介绍的Binder驱动完成的,除了Binder驱动外,整个机制还包括以下几个组成部分:
(1)Service Manager
Service Manager主要负责管理Android系统中所有的服务,当客户端要与服务端进行通信时,首先就会通过Service Manager来查询和取得所需要交互的服务。当然,每个服务也都需要向Service Manager注册自己提供的服务,以便能够供客户端进行查询和获取。
(2)服务(Server)
这里的服务即上面所说的服务端,通常也是Android的系统服务,通过Service Manager可以查询和获取某个Server。
(3)客户端
这里的客户端一般是指Android系统上面的应用程序。它可以请求Server中的服务,比如Activity。
(4)服务代理
服务代理是指在客户端应用程序中生成的Server代理(proxy)。从应用程序的角度来看,代理对象和本地对象没有差别,都可以调用其方法,方法都是同步的,并且返回相应的结果。服务代理也是Binder机制的核心模块。
2.Binder的系统构架
在Android源码中,有关Binder的实现在各个层析都有,主要的Binder库由本地原生代码实现,Java和C++层都定义有同样功能的Binder接口,供应用程序使用,它们实际上都是调用原生Binder库的实现。Binder的系统构架如图3-1所示:
图3-1 Android Binder系统构架 |
其中,Binder 驱动在前面已经介绍了,它用于实现Binder的设备驱动,主要负责组织Binder的服务节点,调用Binder相关的处理线程,完成实际的Bainder传输等,它位于Binder结构的最底层(即Linux内核层)。Binder Adapter层是对Binder驱动的封装,主要用于操作Binder驱动,即应用程序不必直接接触Binder驱动程序,实现包括IPCThreadState.cpp和ProcessState.cpp,以及Parcel.cpp中的部分内容。Binder核心库是Binder框架的核心实现,主要包括IBinder、Binder(服务器端)和BpBinder(客户端);位最上面两层的Binder框架和具体的客户端/服务端都分别有Java和C++两种实现方案,主要供应用程序使用,比如摄像头和多媒体等,它们通过调用Binder的核心库来实现。由于这几个部分关系紧密,不便于单独分析每个模块,因此需要结合Binder的本地实现一起进行分析。
3.3.2 Binder的机制和原理(1)
作为Android系统的核心机制,Binder几乎贯穿整个Android系统,本节将从Binder所涉及的Service Manager、服务、客户端、服务端(代理对象)等各个部分进行分析,在分析之前首先需要明确Binder的工作流程:
1)客户端首先获得服务器端的代理对象。所谓的代理对象实际上就是在客户端建立一个服务端的“引用”,该代理对象具有服务端的功能,使其在客户端访问服务端的方法就像访问本地方法一样。
2)客户端通过调用服务器代理对象的方式向服务器端发送请求。
3)代理对象将用户请求通过Binder驱动发送到服务器进程。
4)服务器进程处理用户请求,并通过Binder驱动返回处理结果给客户端的服务器代理对象。
5)客户端收到服务器端的返回结果。
经过这样一个流程,Binder就完成了一次通信。可以看出,这里与Binder通信的对象服务端就是服务,下面我们首先分析Android中的服务。
1.服务
服务的本质就是响应客户端的请求。要提供服务,就必须建立接收请求、处理请求、应答客户端的框架。因此,任何一个服务都必然存在一个循环监听、处理请求的过程,如图3-2所示。
图3-2 服务的基本框架 |
在Android中一共包括三种服务,它们分别是:
Native服务
Android服务
Init空间的服务
Navite服务实际上就是完全在C++空间完成的服务,主要是指系统一开始初始化时,通过Init.rc脚本启动的服务,例如Service Manger service、Zygote service、Media service、ril_demon service等。Android服务是指在JVM空间完成的服务,虽然也要使用Navite上的框架,但是服务主体存在于Android空间中。Android服务是第二阶段初始化(Init2)时建立的服务。Init空间的服务主要用于完成属性设置,其通信采用Socket方式。
不管是Native Service,还是Android Service,都要用Android中的Binder驱动来完成与客户端之间的通信。上一节介绍过,对于与Binder驱动间的通信,Android专门提供了一个Binder Adapter层来负责。
Binder Adapter实际上就是对Binder驱动的封装,用于完成Binder库与Binder内核驱动的交互。主要实现包括IPCThreadState和ProcessState,它们位于Android源代码的“frameworks\base\inculde\binder”和“frameworks\base\libs\binder”两个文件夹中,其中ProcessState类中包含了通信细节,利用open_binder打开Linux设备dev\binder。通过ioctrl建立基本的通信框架,每个进程只有一个ProcessState对象,每一个线程中都会有一个IPCThreadState对象,它主要负责Binder数据读取、写入和请求处理框架。
ProcessState是一个singleton类型,其作用是维护当前进程中的所有Service代理。一个客户端进程可能需要多个Service的服务,这样可能会创建多个Service代理,客户端进程中的ProcessState对象就负责维护这些Service代理。
当一个服务启动时,会调用ProcessState::self()函数来获得一个ProcessState实例,该函数的实现如代码清单3-38所示。
代码清单3-38 ProcessState::self()的实现
ProcessState放置在全局变量gProcess中,如果gProcess为NULL,则新建一个ProcessStat,其构造函数如代码清单3-39所示。
代码清单3-39 ProcessState::ProcessState()的实现
该构造函数首先通过open_driver()打开Binder设备驱动(/dev/binder),然后通过ioctrl建立基本的通信框架。
3.3.2 Binder的机制和原理(2)
另外,ProcessState中还维护了一个线程池,可以通过函数ProcessState::startThreadPool()来开启线程池。该线程池会维护所有的服务端代理,当需要创建一个服务端代理对象时,就会调用getStrongProxyForHandle来实现,如代码清单3-40所示。
代码清单3-40 getStrongProxyForHandle的实现
该函数首先调用lookupHandleLocked函数,查询当前进程维护的Service代理对象的列表,查看要创建的Service代理对象是否已经在当前进程中创建。如果已经创建过了,则直接返回其引用就可以了;否则,将会在Service代理对象的列表中增加相应的位置,保存将要创建的代理对象。这里大家已经看到的所谓的服务端代理对象,其实就是BpBinder对象,稍后会详细分析。
在构造ProcessSate的时候,使用open_binder打开/driver/binder,并将句柄记录在mDriverFD中。但是在ProcessState中并不使用这个句柄,真正使用这个Binder设备句柄的是IPCThreadState,所有关于Binder的操作都放置在IPCThreadState中,其中几个重要的函数如下:
talkWithDriver() 读取/写入
executeCommand(...) 请求处理
joinThreadPool() 循环结构
其中,talkWithDriver负责读取和写入,实际上就是通过ioctl对ProcessState打开的句柄进行读写,如代码清单3-41所示。用户可以不直接通过ioctl来操作Binder设备,通过IPCThreadState对象来代理即可。
代码清单3-41 IPCThreadState::talkWithDriver()的实现片段
不管是客户端进程还是Service进程,都需要用IPCThreadState来与Binder设备通信。如果是客户端进程,则通过服务代理对象BpBinder调用transact函数,把客户端的请求写入Binder设备另一端的Service进程(具体请参阅IPCThreadState类的transact方法);如果是Service进程,当它完成初始化工作之后,就需要进入循环状态等待客户端的请求,Service进程调用它的IPCThreadState对象的joinThreadPool方法,开始轮询Binder设备,等待客户端请求的到来。
2.Service Manager
从名字可以看出Service Manager是所有服务的管理器,因此,所有Server(System Server)都需要向它注册,应用程序需要向其查询相应的服务。其实现位于Android源代码的“frameworks\base\cmds\servicemanager\service_manager.c”文件中,在init.rc中可以看到如代码清单3-42所示的代码。
代码清单3-42 init.rc中的servicemanager片段
说明,当Android启动时就会自动运行的一个核心进程,其入口函数的实现如代码清单3-43所示。
代码清单3-43 Service Manager的main函数
该函数首先调用binder_open打开Binder设备(/dev/binder),其次,它调用了binder_become_ context_manager函数,将自己设置为Service Manager。因为Service Manager本身就是一个服务,只是它比较特殊,会管理其他所有的服务,也正是binder_become_context_manager函数将其变为服务管理器的,如代码清单3-44所示。
代码清单3-44 binder_become_context_manager的实现
3.3.2 Binder的机制和原理(3)
这里是通过Binder的ioctl和BINDER_SET_CONTEXT_MGR向Binder驱动申明自己是服务管理器,作为服务管理器就需要为客户端提供查询和获取服务。因此,在main函数最后调用binder_loop进入循环状态,并设置一个回调函数,等待用户的请求。该回调函数svcmgr_handler的实现如代码清单3-45所示。
代码清单3-45 svcmgr_handler的实现
当有新的服务需要添加或者客户端要获得某个已经添加的服务时都会触发该回调函数,添加函数(SVC_MGR_ADD_SERVICE)操作则会调用do_add_service来完成,如代码清单3-46所示。
代码清单3-46 do_add_service的实现
添加过程为:首先,检查是否有权限注册Service;然后,检查是否已经注册过Service,注册过的Service将不能再次注册;接下来构造一个svcinfo对象,并将其加入到一个全局链表(svclist)中;最后,通知Binder设备有一个Service注册进来了。添加Binder设备之后会为每一个服务都维护一个句柄,当查询和获得某个服务时就会使用这个句柄。当然,Service Manager的句柄在调用了binder_become_context_manager之后就变为0了,作为服务管理器。当客户端需要获得一个服务时,就会触发SVC_MGR_GET_SERVICE命令,再调用do_find_service来查询指定的服务,查找过程就是在服务列表中查找即可,找到之后写入reply中返回给客户端。
3.Binder的机制
通过前面的介绍我们大致明白了,Binder机制实际上就是一个类似于C/S的构架:客户端进程要想与服务端进程通信就必须在客户端建立一个服务端进程代理对象,然后将请求发送到代理对象上;代理对象通过Binder驱动将请求转发给服务端进程处理;当处理完成之后,再次通过Binder驱动传回给代理对象,客户端从代理对象获取响应信息。
(1)IBinder
Android对Binder机制进行了抽象,定义了IBinder接口,该接口是对跨进程对象的抽象,在C/C++和Java层都有定义。IBinder定义了一套使用Binder机制来实现客户程序与服务器的通信协议,它位于“frameworks\base\inculde\binder\IBinder.h”文件中。
一个普通对象只能在当前进程中被访问,如果希望它能被其他进程访问,就必须实现IBinder接口。IBinder接口可以指向本地对象,也可以指向远程对象,关键就在于IBinder接口中的transact函数。如果IBinder指向的是一个服务端代理,那么transact只是负责把请求发送给服务器;如果IBinder指向的是一个服务端,那么transact只负责提供服务即可。因此,不管是服务端还是服务端代理对象,都必须实现该接口,这样才能进行Binder通信。
(2)服务端代理对象BpBinder
BpBinder是服务端代理对象,即远程对象在当前进程的代理。实际上,它也是Binder通信存在于客户端的进程,它实现了IBinder接口,它的transact函数的实现如代码清单3-47所示。
代码清单3-47 BpBinder::transact的实现
该函数的参数分别为请求的ID号、请求的参数、返回的结果、额外的标识(通常为0)。它实际上只是简单地调用了IPCThreadState::self()的transact函数,将请求通过内核模块发送给了服务端,服务端处理完请求之后,沿原路返回结果给调用者。
注意 transact方法是同步方法,将会挂起客户进程的当前线程,直到Service把请求处理完成并返回结果。
3.3.2 Binder的机制和原理(4)
(3)服务端BBinder
服务端同样需要实现IBinder接口,这里我们以Android默认的服务端实现类(BBinder)为例进行介绍,其中transact的实现如代码清单3-48所示。
代码清单3-48 BBinder::transact()的实现片段
其中,PING_TRANSACTION请求用来检查对象是否还存在,这里简单地把 pingBinder的返回值返回给调用者,其他的请求交给onTransact处理。onTransact是Bbinder中声明的一个protected类型的虚函数,这个要求它的子类去实现,在下一节分析Binder的具体实现时会详细介绍。
本小节分析了Binder的机制和原理,涉及其所有模块,对于具体的实现方式,需要结合具体的实例进行分析,下一节将以MediaService为例对Binder的实现进行详细分析。
(4)MediaService的实现
前面我们分析了Binder的机制,我们应该对Binder的通信流程有比较清楚的认识了,但是我们应该如何使用Binder机制呢?这里我们以MediaService为例来分析Binder的具体实现和使用方法。MediaService是Android的多媒体服务(比如播放和录制等),Android的媒体播放功能分成两部分:媒体播放应用和媒体播放服务(MediaServer,在系统启动时由init启动,可参考init.rc文件),这两部分分别运行在不同的进程中。媒体播放应用包括Java程序和部分C++代码;媒体播放服务是C++代码;并且需要调用外部模块Opencore来实现真正的媒体播放。媒体播放应用和媒体播放服务之间需要通过Binder机制来相互调用,这些调用包括:
媒体播放应用向媒体播放服务发送控制指令。
媒体播放服务向媒体播放应用发送事件通知(notify)。
在Android源代码中,有关MediaService的部分,主要包括以下目录:
frameworks\base\include\media
frameworks\base\media
下面我们就从MediaService的源码入手进行分析,首先,MediaService的入口函数的实现位于“framework\base\media\mediaServer\main_mediaserver.cpp”中,如代码清单3-49所示。
代码清单3-49 main_mediaserver.cpp的实现片段
该函数首先通过ProcessState::self()获得一个ProcessState实例,然后通过defaultService Manager()获得一个ServiceManager实例,其具体实现过程位于“framework\base\libs\binder\ IServiceManager.cpp”文件中,如代码清单3-50所示。
代码清单3-50 defaultServiceManager()的实现