Android进程间通信—IPC Binder协议总结
背景
为了解决Android不同应用间数据传输以及远程函数调用的需求,比如应用A要给应用B发送它的状态等,或者A要调用B的函数等,android提供了一套IPC通信工具(协议),binder。
名词解释
IPC:Inter-Process Communication,进程间通信,用于进程间数据传输。
RPC:Remote Procedure Call,远程调用,用于进程间函数的调用。
Binder:一种进程间通信/远程调用的工具。
原理
下面以服务注册、检索和使用三个阶段为列,说明binder通信的整个流程。
通信架构
通信分为服务端和客户端,服务端游服务A,B,A中游foo()函数,现在客户端需要远程调用foo()。在进行远程调用的时候,客户端会像调用本地方法一样,需要知道服务端函数的函数名和各个参数,现在假设已经知道了。调用时,客户端在RPC层将需要调用的函数名写入RPC代码,参数写入RPC数据,然后在IPC层将需要调用的服务号(handle)一起封装成binder ipc数据报文,最后通过ioctrl函数将报文发送给binder driver,并写入内核共享内存中。
此时服务端会从内核共享内存中读取binder ipc数据,然后从rpc代码和数据中解析出调用的函数名和参数,然后调用对应的函数。函数调用的结果通过同样的方式写入共享内存,然后客户端读取。
Binder协议详解
在介绍协议前,本文将介绍一下涉及到的一些概念。
Binder Driver:内核中负责传递binder数据进行rpc的驱动;
Context Manager:系统进程,用于管理服务注册、索引、使用的进程,它会为所有注册到的服务配一个服务号,也就是handle,binder driver会根据handle去查找服务(binder寻址)。同时,它自己也是和服务,handle为0,其他服务可用制定handle为0找到它;
binder_proc:binder协议中用于表示不同进程的结构体;
binder_node:binder协议中用于表示不同服务的结构体;
binder_buffer:binder协议中,位于内核的用于交换数据的缓冲区,是binder_proc的成员便变量;
下面以binder_driver的角度分析总结协议流程
服务注册
最先启动的是context manager:
(1)它会调用binder_open函数获取binder driver的文件描述符;
(2)然后使用binder_mmap在binder driver中分配一段共享内存;
(3)调用binder_ioctrl使manager进入等待状态;
(4)(5)然后服务端在某个时刻开始注册,同样使用open、mmap函数在binder driver分配共享内存用于接受响应数据;所分配的内存buffer会保存在binder_buffer结构体中,binder_buffer保存在binder_proc中;
(6)服务端用ioctl注册服务,ipc数据为,handle设置为0,rpc数据为服务名,rpc代码为ADD_SERVICE,binder协议为BC_TRANSACTION(binder commond),如下图所示。
同时binder driver会根据传来的handle查找context manager的binder_node和binder_proc结构体。
(7)binder driver为服务端生成binder_node结构和binder_proc结构,前者表示服务端的服务,会被插入到binder driver的node列表中,且注册到服务端和context manager的binder_proc,用于客户端调用服务时所用。
(8)从(6)中查找到的manager的binder_proc结构里提取binder_buffer,把ipc数据写入,唤醒manager;
(9)binder driver记录服务端的binder_proc;
(10)manager从之前分配的buffer中读取ipc数据,并注册服务名和编号,最后生成ipc应答数据发送回binder driver(还是通过共享内存)。
(11)binder driver会从服务端的binder_proc中获取binder_buffer,写入ipc应答数据,然后唤醒服务端进行数据读。
服务注册完成
注册操作中最关键的就是在binder driver中生存服务端的binder_node和binder_proc,并吧binder_node注册在node list以及manager和服务端自己的binder_proc中,之后服务调用会用到这些关联;
服务检索
注册完成后,客户端要调用服务,首先需要检索到服务;
(1)manager等待;
(2)(3)客户端调用mmap函数,bdriver为客户端初始化binder_proc结构,并生成binder_buffer保存在proc结构中;
(4)客户端调用ioctl,将ipc数据发送到bdriver,ipc数据为,rpc代码为GET_SERVICE,rpc数据为“服务A”,handle为0。bdriver查找manager的binder_proc结构体,获取binder_buffer,把ipc数据写入,唤醒manager读取。同时还会为客户端生成一个binder_proc结构体;
在这里,binder driver只是传数据;
(5)manager获取到ipc数据后,从列表中找到对应的服务,把服务的编号写入ipc应答数据,然后返回给binder;
(6)binder driver从ipc响应数据中读取服务的编号,然后从中node list中拿出相应的服务端的binder_node,并将其注册到客户端的binder_proc结构体中,在使用阶段会用到;
(7)最后binder driver将服务端的node编号填入到ipc响应数据的handle中,写入客户端的binder_buffer,唤醒客户端读取;
索引完成
索引的关键过程就是在binder driver中生成客户端的binder_proc结构,并注册服务端binder_node结构,获取到服务端的handle(node的编号);
服务使用
(1)服务端使用ioctl进入等待状态;
(2)客户端在调用服务函数时,将检索到的handle号填入ipc数据,生成ipc数据后发送到客户端buffer.binder driver根据handle在客户端的binder_proc结构中提取到服务端的binder_node,并由此索引到注册有该node 的服务端binder_proc,从服务单binder_proc拿到buffer,将ipc数据写入到buffer,唤起服务端读取;
(3)让客户端进入等待状态,服务端读取buffer中的ipc数据,提取rpc代码和rpc数据,执行对应的函数,完成后将结果重新封装成ipc应答数据,发送回binder driver,binder driver找到调用方的binder_proc,将ipc应答数据写入buffer,唤起客户端读取结果;
服务使用完成