本文基于 AOSP Android10_r41
源码环境。
# 1. 引子
Binder 是一个 RPC(Remote Procedure Call) 框架,翻译成中文就是远程过程调用。也就是说在 A 进程中可以访问 B 进程中的函数。一般来说,要实现这样的效果需要:
- 在 A 进程中按照固定的规则打包数据,这些数据包含了:
- 数据发给那个进程,Binder 中是一个整型变量 Handle
- 要调用目标进程中的那个函数,Binder 中用一个整型变量 Code 表示
- 目标函数的参数
- 要执行具体什么操作,也就是 Binder 协议
- 进程 B 收到数据,按照固定的格式解析出数据,调用函数,并使用相同的格式将函数的返回值传递给进程 A。
Binder 要实现的效果就是,整体上看过去,进程 A 执行进程 B 中的函数就和执行当前进程中的函数是一样的。
接着我们在看一下相关的概念和 Binder 应用层流程:
- 通过 Binder 可以在 A 进程中访问 B 进程中定义的函数
- 进程 B 中的这些等待着被远程调用的函数的集合,我们称其为Binder 服务(Binder Service)
- 进程 A 称之为 Binder 客户端(Binder Client),进程 B 称之为 Binder 服务端(Binder Server)
- 通常,服务(Service) 需要事先注册到服务管家(ServiceManager),才能被其他进程访问到。服务管家(ServiceManager) 是 Android 系统启动时,启动的一个用于管理 Binder 服务(Binder Service) 的进程。
- Binder 的 RPC 能力通过 Binder 驱动实现
通常一个完整的 Binder 程序涉及 3 个进程与 4 个流程:
- 在 Binder Server 进程中定义好服务
- 然后向 ServiceManager 进程注册服务
- 在 Binder Client 进程中向 ServiceManager 进程获取到服务
- Binder Client 进程发起远程调用,调用 Binder Server 中定义好的服务
整个流程都是建立在 Binder 驱动提供的跨进程调用能力之上:
目前,我们的重点放在 Binder 的使用上,主要关注以下三点:
- 学习驱动源码之前,将 Binder 驱动作为一个黑盒来看待,聚焦应用层
- 三个进程交互的流程与协议
- 应用进程与 Binder 驱动交互的数据与协议
# 2. Binder 驱动对外提供的使用接口
Binder 是一个字符驱动,对应的设备文件是 /dev/binder
,和其他驱动一样,是通过 Linux 的文件访问系统调用对外提供具体功能的:
- open(),用于打开 Binder 驱动,返回 Binder 驱动的文件描述符
- mmap(),用于在内核中申请一块内存,并完成应用层与内核层的虚拟地址映射
- ioctl,在应用层调用 ioctl ,从应用层向内核层发送数据或者读取内核层发送到应用层的数据:
ioctl(文件描述符,ioctl命令,数据)
文件描述符是在调用 open 时的返回值,ioctl 命令和第三个参数"数据"的类型是相关联的,具体如下:
ioctl命令 | 数据类型 | 函数动作 |
---|---|---|
BINDER_WRITE_READ | struct binder_write_read | 应用层向内核层收发数据 |
BINDER_SET_MAX_THREADS | size_t | 设置最大线程数 |
BINDER_SET_CONTEXT_MGR | int or flat_binder_object | 设置当前进程为ServiceManager |
BINDER_THREAD_EXIT | int | 删除 binder 线程 |
BINDER_VERSION | struct binder_version | 获取 binder 协议版本 |
# 3. 应用程序编写
很多博客教程会忽略 C 层的分析,相比 libbinder 库的封装,binder.c 的实现(C语言封装)会简单不少 ,方便初学者理解 binder 应用层工作流程。
我们可以模仿源码中的 bctest.c 写一个完整的 Binder 应用层 demo。
这个工作已经有大佬完成了:
https://github.com/weidongshan/APP_0003_Binder_C_App
但是也有一些问题,这个代码是基于 Android5 的,稍微有点老了,我在以上实现的基础上做了一些修改和适配工作,使得代码可以在 Android10 上跑起来:
https://github.com/yuandaimaahao/AndroidFrameworkTutorial/tree/main/3.%E5%AD%A6%E7%A9%BFBinder%E7%AF%87/%E6%BA%90%E7%A0%81/BinderCDemo
在 frameworks/native/cmds/servicemanager
目录下的 binder.c
和 bctest.c
针对应用编写的需求,对open mmap ioctl
等基本操作做了封装,提供了以下几个函数方便我们应用程序的编写:
- binder_open:用于初始化 binder 驱动
- binder_become_context_manager:设置当前进程为 ServiceManager
- svcmgr_lookup:用于向 ServiceManager 查找服务
- svcmgr_publish:用于向 ServiceManager 注册服务
- binder_call:用于发起远程过程调用
- binder_loop:进入循环,在循环中,获取和解析收到的 binder 数据
Android10 native 层除了 service_manager.c 通过以上函数实现,其他 binder 相关的 native 进程均使用 libbinder 库实现,libbinder 是 Android 中一个用于简化 binder 使用的 C++ 库,对 open mmap ioctl 进行了封装,提供了简化编程的类。这些类复杂且繁多,不利于我们理解应用程序与 binder 驱动之间的交互,会让学习者陷入这些类关系与接口设计之中。这是大部分初学者学不懂 binder 的主要原因,对于初学者我们关心的是:
- Binder 程序的工作流程
- 应用程序与 Binder 驱动之间的数据交互
接着我们就通过一个 C 语言的实现来了解以上两点。
# 3.1 Server 端的实现
Binder 应用程序基于 CS 架构,分为 Server 端和 Client 端,接下来,我们就开始写 Server 端的代码,相关的代码在 binder_server.c
中,主要步骤如下:
- 定义服务,服务就是等待着被远程调用的函数
- 定义服务回调函数,当我们的 Server 收到远程调用的数据时,会被回调的函数
- 完成 main 函数流程
- Binder 初始化
- 注册服务
- 进入 loop, 等待 client 请求服务
# 3.1.1 定义服务
这里我们定义一个服务:
//服务提供的函数1
void sayhello(void)
{
static int cnt = 0;
//fprintf(stderr, "say hello : %d\n", ++cnt);
ALOGW("say hello : %d\n", ++cnt);
}
//服务提供的函数2
int sayhello_to(char *name)
{
static int cnt = 0;
//fprintf(stderr, "say hello to %s : %d\n", name, ++cnt);
ALOGW("say hello to %s : %d\n", name, ++cnt);
return cnt;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
可以看出,服务就是若干函数的集合。
# 3.1.2 定义服务回调函数
当 client 发起远程调用时,server 端会收到数据,并将这些数据传递给服务回调函数,这个回调函数需要我们自己来定义:
// server 收到 client 远程函数调用后的回调函数,用于处理收到的信息
// bs 表示 binder 状态
// binder_io 是 c 层定义的数据结构,可以简单理解为一个数据集合
// txn_secctx 和 msg 是收到的数据
// reply 是需要返回给 client 的数据
int hello_service_handler(struct binder_state *bs,
struct binder_transaction_data_secctx *txn_secctx,
struct binder_io *msg,
struct binder_io *reply)
{
struct binder_transaction_data *txn = &txn_secctx->transaction_data;
uint16_t *s;
char name[512];
size_t len;
//uint32_t handle;
uint32_t strict_policy;
int i;
//bio_get_uint32 用于从 binder_io 中取出一个 32 为整型数据
strict_policy = bio_get_uint32(msg);
/* 根据txn->code知道要调用哪一个函数
* 如果需要参数, 可以从msg取出
* 如果要返回结果, 可以把结果放入reply
*/
switch(txn->code) {
//调用函数1
case HELLO_SVR_CMD_SAYHELLO:
sayhello();
bio_put_uint32(reply, 0); /* no exception */
return 0;
//调用函数2
case HELLO_SVR_CMD_SAYHELLO_TO:
/* 从msg里取出字符串 */
s = bio_get_string16(msg, &len); //"IHelloService"
s = bio_get_string16(msg, &len); // name
if (s == NULL) {
return -1;
}
for (i = 0; i < len; i++)
name[i] = s[i];
name[i] = '\0';
/* 处理 */
i = sayhello_to(name);
/* 把结果放入reply */
bio_put_uint32(reply, 0); /* no exception */
bio_put_uint32(reply, i);
break;
default:
fprintf(stderr, "unknown code %d\n", txn->code);
return -1;
}
return 0;
}
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
52
53
54
55
56
57
58
59
60
61
回调函数的流程结构还是比较清晰明的:
- 取出 code 值
- 根据 code 值调用对应的函数
- 函数如果有参数,从 msg 中取出
- 函数的返回值写入到 reply 中
# 3.1.3 Server 主函数
定义 binder_loop 回调函数:
//存在多个服务时做统一调度管理
int test_server_handler(struct binder_state *bs,
struct binder_transaction_data_secctx *txn_secctx,
struct binder_io *msg,
struct binder_io *reply)
{
struct binder_transaction_data *txn = &txn_secctx->transaction_data;
int (*handler)(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply);
//svcmgr_publish 传入的函数指针 == target.ptr
handler = (int (*)(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply))txn->target.ptr;
return handler(bs, txn, msg, reply);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
完成主函数编写:
- Binder 初始化
- 注册服务
- 进入 loop, 等待 client 请求服务
int main(int argc, char **argv)
{
struct binder_state *bs;
uint32_t svcmgr = BINDER_SERVICE_MANAGER;
uint32_t handle;
int ret;
//Binder 初始化
bs = binder_open("/dev/binder", 128*1024);
if (!bs) {
fprintf(stderr, "failed to open binder driver\n");
return -1;
}
//svcmgr 的值是 0,表示发送信息给 servicemanager
//注册服务
ret = svcmgr_publish(bs, svcmgr, "hello", hello_service_handler);
if (ret) {
fprintf(stderr, "failed to publish hello service\n");
return -1;
}
//进入 loop, 等待 client 请求服务
binder_loop(bs, test_server_handler);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
main 函数主要调用了 binder_open
、svcmgr_publish
和 binder_loop
三个函数来完成了 binder 的初始化,服务注册,进入 looper 三个阶段的功能。
# 3.2 ServiceManager 的实现
ServiceManager 由系统实现,并在系统启动时启动。
其源码在 frameworks/native/cmds/servicemanager/service_manager.c
:
主函数的内容如下
int main(int argc, char** argv)
{
struct binder_state *bs;
char *driver;
if (argc > 1) {
driver = argv[1];
} else {
driver = "/dev/binder";
}
//binder 驱动初始化
bs = binder_open(driver, 128*1024);
//删减了 VENDORSERVICEMANAGER 相关内容
if (!bs) {
ALOGE("failed to open binder driver %s\n", driver);
return -1;
}
//告诉驱动,当前进程是 context_manager
if (binder_become_context_manager(bs)) {
ALOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
//删减了 selinux 相关内容
binder_loop(bs, svcmgr_handler);
return 0;
}
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
main 函数主要流程如下:
- 调用 binder_open 函数完成 binder 驱动的初始化
- 调用 binder_become_context_manager 函数告诉 binder 驱动当前进程是
context_manager
- 调用 binder_loop 函数,进入 looper ,等到远程调用,其收到数据后的回调函数是
svcmgr_handler
# 3.3 Client
编写 Client 程序的主要流程如下:
- 实现远程调用函数
- open 初始化 binder 驱动
- 查询服务,获取到服务的句柄 handle
- 通过 handle 调用远程调用函数
首先定义好远程调用函数:
void sayhello(void)
{
unsigned iodata[512/4];
//binder_io 可以理解为一个数据集合,用于存取数据
struct binder_io msg, reply;
/* 构造binder_io */
bio_init(&msg, iodata, sizeof(iodata), 4);
bio_put_uint32(&msg, 0); // strict mode header
bio_put_string16_x(&msg, "IHelloService");
/* 放入参数 */
/* 调用 binder_call 发起远程调用 */
if (binder_call(g_bs, &msg, &reply, g_handle, HELLO_SVR_CMD_SAYHELLO))
return ;
/* 从reply中解析出返回值 */
binder_done(g_bs, &msg, &reply);
}
int sayhello_to(char *name)
{
unsigned iodata[512/4];
struct binder_io msg, reply; 解析
int ret;
int exception;
/* 构造binder_io */
bio_init(&msg, iodata, sizeof(iodata), 4);
bio_put_uint32(&msg, 0); // strict mode header
bio_put_string16_x(&msg, "IHelloService");
/* 放入参数 */
bio_put_string16_x(&msg, name);
/* 调用binder_call 发起远程调用*/
if (binder_call(g_bs, &msg, &reply, g_handle, HELLO_SVR_CMD_SAYHELLO_TO))
return 0;
/* 从reply中解析出返回值 */
exception = bio_get_uint32(&reply);
if (exception)
ret = -1;
else
ret = bio_get_uint32(&reply);
binder_done(g_bs, &msg, &reply);
return ret;
}
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
52
53
54
接下来,实现 main 函数:
int g_handle = 0;
struct binder_state *g_bs;
int main(int argc, char **argv)
{
int fd;
struct binder_state *bs;
uint32_t svcmgr = BINDER_SERVICE_MANAGER;
int ret;
//初始化 binder 驱动
bs = binder_open("/dev/binder", 128*1024);
if (!bs) {
fprintf(stderr, "failed to open binder driver\n");
return -1;
}
g_bs = bs;
//查找服务,获取到服务的句柄 handle,用于找到 Server 进程
g_handle = svcmgr_lookup(bs, svcmgr, "hello");
if (!g_handle) {
ALOGW("binder client 查找服务 hello 失败");
return -1;
} else {
ALOGW("binder client 查找服务成功 handle = %d", g_handle);
}
//通过 handle 调用服务
sayhello();
sayhelloto("hello binder");
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
# 3.4 编译运行程序
项目目录下添加 Android.bp :
cc_defaults {
name: "bindertestflags",
cflags: [
"-Wall",
"-Wextra",
"-Werror",
"-Wno-unused-parameter",
"-Wno-missing-field-initializers",
"-Wno-unused-parameter",
"-Wno-unused-variable",
"-Wno-incompatible-pointer-types",
"-Wno-sign-compare",
],
product_variables: {
binder32bit: {
cflags: ["-DBINDER_IPC_32BIT=1"],
},
},
shared_libs: ["liblog"],
}
cc_binary {
name: "binderclient",
defaults: ["bindertestflags"],
srcs: [
"client.cpp",
"binder.c",
],
}
cc_binary {
name: "binderserver",
defaults: ["bindertestflags"],
srcs: [
"server.cpp",
"binder.c",
],
}
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
终端进入系统源码,然后执行:
source build/envsetup.sh
lunch aosp_x86_64-eng
emulator
2
3
将源码移动到aosp源码下任意目录,进入 BinderCDemo,编译模块:
mm
编译完成后,将程序上传到模拟器并执行。
传输可执行文件到模拟器:
adb push aosp/out/target/product/generic_x86_64/vendor/bin/binderserver /data/local/tmp
adb push aosp/out/target/product/generic_x86_64/vendor/bin/binderclient /data/local/tmp
2
3
接下来 adb shell
进入模拟器 shell
环境:
adb shell
cd /data/local/tmp
./binderserver
# 从新开一个终端进入 adb shell
cd /data/local/tmp
./binderclient
2
3
4
5
6
最后通过 logcat 查看执行结果:
logcat | grep "BinderServer"
11-22 17:08:39.133 6594 6594 W BinderServer: say hello : 1
2
3