binder-概念以及底层流程

概念

IPC (Inter-Process Communication) 进程间通信

RPC (Remote Procedure Call) 远程过程调用

比如说A进程想把数据原原本本地发给进程B,就是IPC

如果A进程想调用B进程中的某个函数,就涉及到RPC

RPC场景运用了IPC手段,而android里的IPC手段就是binder机制

假设A进程现在想打开闪光灯,而闪光灯属于硬件模块,和A不同的进程,那么A会先发数据(IPC)给B,然后B接受数据,接受到后调用B进程的本地函数(RPC,A调用B本地函数的过程),函数的作用是打开闪光灯

IPC

IPC三大要素:

  • 源: 进程A
  • 目的: 进程B
  • 数据: A传给B的数据,像 char buff[]

那进程A他是如何确定我调用的方法就是打开闪光灯,如果他想降低音量之类的,他应该向哪个进程发送数据

因此目的中应该包含两个部分

目的:

  • 进程B向serviceManager注册服务
  • 进程A向servicemanager获取服务打开闪光灯的服务,获取到一个handle(引用),他指向这个闪光灯服务

serviceManager就是管理服务的一个类

因此结构图应该长这样

Binder系统会涉及到四个要素

那么,server进程会有很多,client怎么知道向哪个server发送数据?

那么就引入了serviceManager,他们之间的通讯是通过binder驱动实现的

RPC

RPC:可以简单理解为调用其他进程的某个函数的这个过程

那么就会存在几个问题

  • 调用哪个函数?
  • 传给他什么参数?
  • 返回值是什么?

调用哪个函数?

android系统中,server他内部会将函数进行编号,你要调用什么函数,把编号传给他即可,比如 1 或者 2,他是按照顺序来的,按照你注册服务的顺序

传给他什么参数?

这个参数就是放在IPC三大要素中的数据中

返回值是什么?

同理,进程B处理完数据之后,也会通过IPC将数据传给进程A,也就是传返回值给A

从源代码来简单理解

首先去 androidxref.com找到源码,直接搜索service_manager.c

你可以直接在这里找到 servicemanager

你可以去google的git仓库,直接把 servicemanager 下的所有文件下载过来,如果你机器上有android的源码那最好

认识下源码下的一些文件

test_部分的文件是我编写的测试代码,后续读者也可以动手手动来编写,加深理解

binder.c 文件是封装好的函数,我们写测试类都要用到该类,包括与binder驱动的交互

service_manager.c 就是上图中的中间件,提供服务

bindertest.c 是android提供给我们理解的测试类,我们可以模仿他来进行代码测试的编写

那么,Binder系统的四大要素中,各自的职责和流程又是什么?

这里只做简单介绍,读者大可不必每个步骤都理解,后续再做分析

Binder驱动

主要负责各个进程之间的底层通信工作,后续的文章中会详细讲解binder驱动

serviceManager

  • 打开驱动
  • 告诉binder驱动,我就是serviceManager(他的句柄是 0U ),如果谁要注册服务,就找我这个进程,因为如果不告知,client就不知道应该把数据发送给哪个进程
  • while循环
    • 读驱动数据
    • client没发送数据就休眠
    • 被唤醒,读取数据
    • 解析数据
    • 调用
      • 注册服务:在链表中记录服务名
      • 获取服务
        • 链表中查询是否有这个服务
        • 返回server进程的handle,(获取到这个handle,client就会根据这个handle整数来获取对应的进程)

server

  • 打开驱动
  • 注册服务
    • 向serviceManager发送服务名称
  • while循环,既然是一个服务端,那自然需要一个循环来等待客户端的请求
    • 读驱动
    • 解析数据
    • 调用对应本地函数(比如打开闪光灯)

client:

  • 打开驱动
  • 获取服务(主要是确定,我要把数据发送给谁,发送给哪个进程)
    • 向serviceManager查询服务
    • 获得一个代表该进程的handle
    • 向handle发数据(发送数据后,server就会读取数据,然后server调用本地函数)

注意他们之间的通信工作都是通过binder驱动来实现的

源码验证流程

servicemanager流程

找到main方法,看到
bs = binder_open(driver, 128*1024);
这个就是打开驱动

继续会发现
binder_become_context_manager(bs)
这个就是告诉binder驱动,谁是serviceManager

然后就是一个循环binder_loop(bs, svcmgr_handler)循环的作用上面讲过,读取数据,解析数据,调用对应的处理函数

进入binder_loop函数,可以发现

res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);读取数据,ioctl,最为关键的函数

res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);解析数据

binder_parse方法中,他的参数中有一个函数func

1
2
3
4
5
6
res = func(bs, txn, &msg, &reply);
if (txn->flags & TF_ONE_WAY) {
binder_free_buffer(bs, txn->data.ptr.buffer);
} else {
binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
}

这里就是处理数据,如果需要返回值的话,就会通过binder_send_reply来返回数据

前面binder_loop方法中,有一个重要的参数svcmgr_handler

就是传入的函数,这个方法很重要,因为如果要自己写一个服务的话,和这里边的代码逻辑是类似的

源码中可以看到switch(txn->code)就是根据client传过来的code来决定要如何处理,serviceManager中就是获取服务和注册服务以及获取服务列表

注册服务过程

找到刚才地址中的bctest.c文件,里面提供了如何注册服务

找到main方法
同样也是先打开驱动
然后找到svcmgr_publish就是注册服务的函数

进入svcmgr_publish函数

可以看到
binder_call(bs, &msg, &reply, target, SVC_MGR_ADD_SERVICE)

从名称就可以看出来各个参数的大致作用是什么了
这个target从调用上看他其实是
uint32_t svcmgr = BINDER_SERVICE_MANAGER;
#define BINDER_SERVICE_MANAGER 0U
也就是之前说的,他是 0
如果你想把数据发给 0 的时候,那么binder驱动就会知道,你是想要发数据给serviceManager

  • msg 含有服务的名字
  • reply 含有serviceManager回复的数据
  • target 0 表示serviceManager,向哪个进程发数据
  • code 调用进程中哪个函数

获取服务的过程

同样代码可以在bctest.c中找到

同样也是先打开驱动
然后找到svcmgr_lookup
可以发现他调用的也是binder_call
不同点就是他们传的code不一样,因此调用的函数也不一样

binder.c分析

从刚才分析,我们可以知道函数binder_call是很重要的
他也是提供远程调用功能的一个函数

那么远程调用就会涉及到一些问题

  • 向谁发数据
  • 调用哪个函数
  • 提供什么参数
  • 返回值是什么

从这些入手来分析binder_call

1
2
3
4
5
6
7
8
9
10
int binder_call(
struct binder_state *bs,
//提供什么参数
struct binder_io *msg,
//返回值是什么
struct binder_io *reply,
//向谁发数据
uint32_t target,
//向哪个函数发数据
uint32_t code)

binder_call流程

  • 构造数据
  • 调用ioctl发送数据
  • 使用ioctl接收数据

整个代码逻辑你可以不关心,这个方法做的一件事就是接收数据和发送数据,也是核心的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
int binder_call(struct binder_state *bs,
struct binder_io *msg, struct binder_io *reply,
uint32_t target, uint32_t code)
{
int res;
struct binder_write_read bwr;
struct {
//命令 系统存在很多命令,binder根据传过来的命令调用不同函数
uint32_t cmd;
//所有的数据实际上放在这里
struct binder_transaction_data txn;
} __attribute__((packed)) writebuf;
unsigned readbuf[32];

......

//根据target binder_io code 三个构造数据(writebuf)
writebuf.cmd = BC_TRANSACTION;
writebuf.txn.target.handle = target;
writebuf.txn.code = code;
writebuf.txn.flags = 0;
writebuf.txn.data_size = msg->data - msg->data0;
writebuf.txn.offsets_size = ((char*) msg->offs) - ((char*) msg->offs0);
writebuf.txn.data.ptr.buffer = (uintptr_t)msg->data0;
writebuf.txn.data.ptr.offsets = (uintptr_t)msg->offs0;
......
//binder_io 转换为 binder_write_read 能够让binder驱动使用
//也就是将包含binder_io数据的 writebuf 给bwr
bwr.write_size = sizeof(writebuf);
bwr.write_consumed = 0;
bwr.write_buffer = (uintptr_t) &writebuf;
hexdump(msg->data0, msg->data - msg->data0);
for (;;) {

bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (uintptr_t) readbuf;

//调用ioctl来发送数据
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);

res = binder_parse(bs, reply, (uintptr_t) readbuf, bwr.read_consumed, 0);
if (res == 0) return 0;
if (res < 0) goto fail;
}

fail:
memset(reply, 0, sizeof(*reply));
reply->flags |= BIO_F_IOERROR;
return -1;
}

可以看到 ioctl 方法中有个参数是 bwr ,结构体如下

1
2
3
4
5
6
7
8
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;
};

那么,我们传入的是一个 binder_io,也就是我们的应用层他的类型是 binder_io ,而底层他需要的是 binder_write_read ,那么必然就会进行一个转换

同样,在iotrl接收数据的时候,接收到的是 binder_write_read ,那么他必然就会转换为 binder_io 给应用层

如何使用binder_call

在之前的bctest.c中找到 svcmgr_lookup 函数,他的作用是寻找服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
uint32_t svcmgr_lookup(struct binder_state *bs, uint32_t target, const char *name)
{
uint32_t handle;
unsigned iodata[512/4];
//构造binder_io
struct binder_io msg, reply;

//初始化binder_io的缓冲区,实际上binder_io就是对缓冲区 iodata 的管理
bio_init(&msg, iodata, sizeof(iodata), 4);
//在缓冲区中放入数据 有put 那么肯定就会有get
bio_put_uint32(&msg, 0); // strict mode header
bio_put_string16_x(&msg, SVC_MGR_NAME);
bio_put_string16_x(&msg, name);

//调用binder_call
if (binder_call(bs, &msg, &reply, target, SVC_MGR_CHECK_SERVICE))
return 0;

handle = bio_get_ref(&reply);

if (handle)
binder_acquire(bs, handle);

binder_done(bs, &msg, &reply);

return handle;
}