前言
Linus Benedict Torvalds : RTFSC – Read The Funning Source Code
概述
Binder是一个进程间通信的机制,实质就是把对象从一个进程映射到另一个进程中。
它主要能提供的功能:
1:用驱动程序来推进进程间的通信。
2:通过共享内存来提高性能。
3:位进程请求分配每个进程的线程池。
4:针对系统中的对象引入引用计数和跨进程的对象引用映射。
5:进程间同步调用。
最后为什么它那么重要,因为Android系统的运行都将依赖Binder驱动。
服务端
一个Binder服务器端就是一个Binder类的对象。当创建一个Binder对象后,内部就会开启一个线程,这个线程用于接收binder驱动发送的信息,收到消息后,会执行相关的服务代码。
做为Proxy设计模式的基础,首先定义一个抽象接口类封装Server所有功能,其中包含一系列纯虚函数留待Server和Proxy各自实现。由于这些函数需要跨进程调用,须为其一一编号,从而Server可以根据收到的编号决定调用哪个函数。其次就要引入Binder了。Server端定义另一个Binder抽象类处理来自Client的Binder请求数据包,其中最重要的成员是虚函数onTransact()。该函数分析收到的数据包,调用相应的接口函数处理请求。
接下来采用继承方式以接口类和Binder抽象类为基类构建Binder在Server中的实体,实现基类里所有的虚函数,包括公共接口函数以及数据包处理函数:onTransact()。这个函数的输入是来自Client的binder_transaction_data结构的数据包。前面提到,该结构里有个成员code,包含这次请求的接口函数编号。onTransact()将case-by-case地解析code值,从数据包里取出函数参数,调用接口类中相应的,已经实现的公共接口函数。函数执行完毕,如果需要返回数据就再构建一个binder_transaction_data包将返回数据包填入其中。
那么各个Binder实体的onTransact()又是什么时候调用呢?这就需要驱动参与了。前面说过,Binder实体须要以Binde传输结构flat_binder_object形式发送给其它进程才能建立Binder通信,而Binder实体指针就存放在该结构的handle域中。驱动根据Binder位置数组从传输数据中获取该Binder的传输结构,为它创建位于内核中的Binder节点,将Binder实体指针记录在该节点中。如果接下来有其它进程向该Binder发送数据,驱动会根据节点中记录的信息将Binder实体指针填入binder_transaction_data的target.ptr中返回给接收线程。接收线程从数据包中取出该指针,reinterpret_cast成Binder抽象类并调用onTransact()函数。由于这是个虚函数,不同的Binder实体中有各自的实现,从而可以调用到不同Binder实体提供的onTransact()。
客户端
客户端要想访问Binder的远程服务,就必须获取远程服务的Binder对象在binder驱动层对应的mRemote引用。当获取到mRemote对象的引用后,就可以调用相应Binder对象的服务了。
做为Proxy设计模式的一部分,Client端的Binder同样要继承Server提供的公共接口类并实现公共函数。但这不是真正的实现,而是对远程函数调用的包装:将函数参数打包,通过Binder向Server发送申请并等待返回值。为此Client端的Binder还要知道Binder实体的相关信息,即对Binder实体的引用。该引用或是由SMgr转发过来的,对实名Binder的引用或是由另一个进程直接发送过来的,对匿名Binder的引用。
由于继承了同样的公共接口类,Client Binder提供了与Server Binder一样的函数原型,使用户感觉不出Server是运行在本地还是远端。Client Binder中,公共接口函数的包装方式是:创建一个binder_transaction_data数据包,将其对应的编码填入code域,将调用该函数所需的参数填入data.buffer指向的缓存中,并指明数据包的目的地,那就是已经获得的对Binder实体的引用,填入数据包的target.handle中。注意这里和Server的区别:实际上target域是个联合体,包括ptr和handle两个成员,前者用于接收数据包的Server,指向 Binder实体对应的内存空间;后者用于作为请求方的Client,存放Binder实体的引用,告知驱动数据包将路由给哪个实体。数据包准备好后,通过驱动接口发送出去。经过BC_TRANSACTION/BC_REPLY回合完成函数的远程调用并得到返回值。
驱动层
原理
Binder采用AIDL来描述进程间的接口。
主要方法:
1、binder_ioctl:驱动与用户空间进程交换数据。
2、binder_thread_write:发送请求或返回结果。
3、binder_thread_read:读取结果。
4、binder_transaction:转发请求并返回结果。
5、binder_parse:数据的解析。
实现
主要数据结构体
1、binder_work
2、Binder类型
3、传输方式
4、Binder对象
5、Binder实际内容的结构体
6、binder_write_read
作用:当Binder驱动找到处理此事件的进程后,就会把需要处理的时间的任务放在读缓冲(binder_write_read)里,返回给这个服务线程,该服务线程则会执行指定命令的操作。处理请求的线程把数据交给合适的对象来执行预定操作,然后把返回结果同样用binder_transaction_data结构封装,以写命令的方式传回Binder,并将此数据放在一个读缓冲(binder_write_read)里,返回给正在等待结果的原线程。
7、binder_proc
用于保存调用Binder的各个进程或线程的信息。
8、binder_node
Binder节点。(略)
9、binder_thread
存储每一个单独的线程的信息。
10、binder_transaction
中转请求和返回结果,保存接收和要发送的进程信息。
11、binder_buffer
表示binder的缓冲区信息。(略)
主要方法
1、binder_init
Binder驱动初始化函数,一般需要设备驱动接口调用。Android Binder设备驱动接口函数是device_initcall,目的是不让Binder驱动支持动态编译,而且需要内核做镜像。
misc_register注册自己为一个Misc设备,节点位于/dev/binder。创建只读proc文件:1./proc/binder/state;2./proc/binder/stats;3./proc/binder/transactions;4./proc/binder/transaction_log;5./proc/binder/failed_transaction_log;
2、binder_open
打开Binder设备文件/dev/binder。
3、binder_release
释放在打开以及其他操作过程中分配的空间并清理相关数据信息。binder_defer_work执行的操作比较复杂,采用了延迟执行的方法来提高系统的响应速度和性能。
4、binder_flush
在关闭一个设备文件描述符复制时被调用。
5、binder_poll
非阻塞型IO内核驱动函数。仅支持设备是否可以飞阻塞地读(POLLIN),有两种等待任务:proc_work和thread_work。最后都是调用poll_wait函数实现poll操作。
6、binder_get_thread
在threads队列中查找当前的进程信息。(略)
7、binder_mmap
mmap(memory map)用于把设备内存映射到用户进程地址空间中,就可以像操作用户内存那样操作设备内存。
由于设备内存是在mmap操作中分配的,每个进程或线程只能执行一次映射操作。
步骤总结:
1:检查内存映射条件。
2:获取地址空间,并把次空间的地址记录在进程信息(buffer)中。
3:分配物理页面并记录下来。
4:将buffer插入到进程信息的buffer列表中。
5:调用binder_update_page_range函数将分配的物理页面和vm空间对应起来。
6:通过binder_insert_free_buffer函数把此进程的buffer插入到进程信息中。
8、binder_ioctl
Binder的ioctl命令:
1:首先检查数据是否完整。
2:从用户空间复制数据到binder_write_read结构体重。
3:通过write_size 和 bwr.read_size判断需要执行的操作。
最终的操作是inder_thread_write和binder_thread_read函数实现。
核心是BINDER_WRITE_READ:
IPC机制就是通过此接口实现。读代码吧!
ServiceManager
主要负责管理Android系统中所有的服务。对于客户端,要与服务端进行通信时,首先要通过它来查询和取得所需要交互的服务。对于服务端,创建的同时就要向ServiceManager注册自己提供的服务,以便客户端能够进行查询和获取。
因为这个篇章重点在于Binder,所以更多ServiceManager细节在浅析ServiceManager篇章里讲解。