一、Binder通信示例图
client和服务server通信必须要三个过程:
1、server之所以成为服务,必须向服务管理器SMgr注册
2、client想同server通信,必须先向服务管理器SMgr获取服务的引用号。
3、client发送数据包binder_transaction_data同server开始通讯
二、服务server向服务管理器SMgr注册自己
1、服务拥有自己Binder实体和0号引用,注册的时候,通信流程:
服务中的0号引用->内核中的服务对应的0号引用->MSgr在内核中Binder实体->MSgr中的Binder实体。
2、注册的时候通过进程里的0号引用,向服务管理器发送Binder对象,即flat_binder_object数据结构,其中:
flat_binder_object.type=BINDER_TYPE_BINDER //表示传递的是Binder实体
flat_binder_object.binder = 该服务中binder实体的地址
3、流经驱动时,驱动会根据这个binder值(binder实体的地址),在驱动内核中的服务进程相关的红黑树中进行搜索,搜索其对应内核中的Binder实体。
4、第一次注册,肯定搜索不到,因此需要创建对应的内核binder节点(内核中服务对应的binder实体),也就是要构建内核中的binder_node结构体:
->构建binder_proc结构体来记录server进程的信息填充到binder_node.proc中-->服务进程的信息
->flat_binder_object.binder值赋值给binder_node.ptr做备份---->服务进程binder实体的信息
->设置一些flag和保存cookie---->其他信息
->将该binder节点插入到内核中的服务进程相关的红黑树中,binder_node.rb_node,在红黑树中的节点信息。
->binder_node.refs:该节点的引用链表,向本节点的引用都链接在该队列里。这些引用可能隶属于不同的进程。通过该队列可以遍历指向该节点的所有引用。
5、内核中服务的binder实体创建完后,需要为接收方SMgr创建内核binder引用(结构体binder_ref),其中:
binder_ref.node = 刚创建的内核binder节点的内存地址 -->引用同实体关联
binder_ref.proc = 本引用所属的进程,此为SMgr进程 -->引用同进程关联, 表示该引用属于哪个进程
binder_ref.desc = 本结构的引用号
binder_ref.rb_node_desc =以引用号为索引添加到红黑树中的树节点信息
binder_ref.rb_node_node=以binder节点的内存地址为索引添加到另一颗红黑树中的树节点信息
binder_ref.node_entry :将该引用链接进所指向的内核binder_node.refs //实现引用同节点的一对多的关联
6、对数据包flat_binder_object进行改造:
flat_binder_object.type=BINDER_TYPE_HANDLE//表示传递的是Binder引用
flat_binder_object.handle= binder_ref.desc //内核中binder引用的引用号
7、数据包传递给服务管理器SMgr,SMgr接收到注册的数据包之后,将会把引用号和server名字填进一张表中,以供后面client查询使用,服务server的名字、PID等信息在Binder收发数据包结构:binder_transaction_data中包含,flat_binder_object包含在它的buffer域中
注册过程总结:根据进程中的实体,在内核中建立对应的实体节点,在根据内核中的实体节点创建对应的内核SMgr的binder引用,SMgr把服务填进注册表中
三、client向SMgr获取特定名字服务的引用号
Client要使用服务进程的功能,必须先通过SMgr来获得服务的引用句柄才能发起通信。
1、client也是通过client中的0号引用、内核中的0号引用、内核中的0号实体,同SMgr服务管理器进行通信的
2、Client以0号引用和server的名字为参数传递给SMgr
3、SMgr查表获得对应server进程在本SMgr进程中的引用号,填充数据包返回给client
flat_binder_object.type = BINDER_TYPE_HANDLE
flat_binder_object. Handle = Smgr进程在内核中对应server的引用号,即我们注册时保存的引用号
4、数据包流经驱动时,做如下处理:
-->根据flat_binder_object. Handle,即引用号,在红黑树中查找是否有这个引用,如果没有找到,内核驱动就会认为这个handle是非法的,拒绝发送请求。
-->如果找到这个引用,就会获得这个引用对应的节点binder_ref.node
-->根据这个节点和client的进程信息proc,对应binder_ref.node、binder_ref.proc ,在接收方的进程(client)中的红黑树中搜索对应于该client的binder引用结构体,如果是第一次,那肯定不能搜索到了,这个时候就会为这个client进程线程构建一个binder_ref结构体作为binder引用:
binder_ref.node = 刚创建的内核binder节点的内存地址 -->引用同实体关联
binder_ref.proc = 本引用所属的进程,此为client进程 -->引用同进程关联,表示该引用属于哪个进程
binder_ref.desc = 本结构的引用号
binder_ref.rb_node_desc =以引用号为索引添加到红黑树中的树节点信息
binder_ref.rb_node_node=以binder节点的内存地址为索引添加到另一颗红黑树中的树节点信息
binder_ref.node_entry :将该引用链接进所指向的内核binder_node.refs //实现引用同节点的一对多的关联
5、驱动对数据包改造,并返回给client
flat_binder_object.type = BINDER_TYPE_HANDLE
flat_binder_object. Handle = client进程在内核中对应server的引用号,即我们刚创建的引用的引用号
四、client和server通讯,结合数据包的传输、binder通信协议,以client执行server的1001号函数为例
1、client 通过ioctl(fd, cmd, arg)函数,同服务器server通信,其中:
cmd为BINDER_WRITE_READ:写入或读取数据
arg为binder_write_read结构
2、binder_write_read结构内容:
struct binder_write_read { signed long write_size; //写数据缓冲区大小 signed long write_consumed; unsigned long write_buffer; //写数据缓冲区 signed long read_size; signed long read_consumed; unsigned long read_buffer; };
写数据缓冲区的内容为:写入请求数据命令+传输数据包参数(BC_TRANSACTION+binder_transaction_data)
3、binder传输数据包binder_transaction_data结构内容:
struct binder_transaction_data { union { //引用号,上一部中获取的服务在内核中对应该client的引用号 //通过该引用号来表明数据包的目的server size_t handle; void *ptr; } target; void *cookie; //Server端定义的公共接口函数的编号,此处取为1001 unsigned int code; unsigned int flags; //client的进程信息 pid_t sender_pid; uid_t sender_euid; size_t data_size;//数据大小,此处为函数参数的大小 size_t offsets_size;//不传递binder对象,不关心 union { struct { //1001号函数需要的参数,放在此缓存区中 const void *buffer; const void *offsets; //不传递binder对象,不关心 } ptr; uint8_t buf[8]; } data; };
此处主要关心:引用号handle、要调用的函数编号code、函数需要传递的参数buffer、client进程信息sender_pid\sender_euid,对应的关键字就是:目的、调用函数及参数、源
4、Binder驱动获取这个数据包后,根据引用号和进程信息,到红黑树中查询关于该client在内核中的对应server的引用:binder_ref结构体。进而通过其node域知道对应的内核服务的binder节点,然后再从binder节点的proc域知道目标binder所在的进程和其他相关的信息,实现数据包的路由