写给应用开发的 Android Framework 教程——学穿 Binder 篇之 Binder 程序示例之 C 语言篇

5,155 阅读9分钟

写给应用开发的 Android Framework 教程是一个系列教程,目前已更新以下内容:

本文基于 Android 10 源码环境。

示例程序思路参考自韦东山老师开源代码,在此基础上针对 Android10 平台重写调试适配。

1. 引子

Binder 是一个 RPC(Remote Procedure Call) 框架,翻译成中文就是远程过程调用。也就是说通过 Binder,我们可以在 A 进程中访问 B 进程中定义的函数。进程 B 中的这些等待着被远程调用的函数的集合,我们称其为Binder 服务(Binder Service),进程 A 称之为 Binder 客户端(Binder Client),进程 B 称之为 Binder 服务端(Binder Server)

通常,服务(Service) 需要事先注册到服务管家(ServiceManager),才能被其他进程访问到。服务管家(ServiceManager) 是 Android 系统启动时,启动的一个用于管理 Binder 服务(Binder Service) 的进程。

以上的 Binder 远程过程调用的能力是通过 Binder 驱动来实现的,目前我们关注的重点是应用程序的流程,可以将 Binder 驱动作为一个黑盒来看待,只关心 Binder 驱动对外提供的接口。

通常一个完整的 Binder 程序会涉及到 4 个对象和 4 个流程

4 个对象:

  • Binder 驱动
  • Binder Server
  • Binder Client
  • ServiceManager

4 个流程:

  • 在 Binder Server 端定义好服务
  • 然后向 ServiceManager 注册服务
  • 在 Binder Client 中向 ServiceManager 获取到服务
  • 发起远程调用,调用 Binder Server 中定义好的服务

整个流程都是建立在 Binder 驱动提供的跨进程调用能力之上:

2. Binder 驱动对外提供的接口

目前阶段,我们的重点是 Binder 应用程序的工作流程和相关数据结构,Binder 驱动应该作为一个黑盒来看待,只关心 Binder 驱动对外提供了哪些函数供应用程序使用。

Binder 是一个 Linux 字符驱动,对外提供了以下函数供应用程序使用:

  • open(),用于打开 binder 驱动,返回 Binder 驱动的文件描述符
  • mmap(),用于在内核中申请一块内存,并完成应用层与内核层的虚拟地址映射
  • ioctl,在应用层调用 ioctl 向内核层发送数据或者读取内核层发送到应用层的数据:
ioctl(文件描述符,ioctl命令,数据)

文件描述符是在调用 open 时的返回值,ioctl 命令和第三个参数"数据"的类型是相关联的,具体如下:

ioctl命令数据类型函数动作
BINDER_WRITE_READstruct binder_write_read应用层向内核层收发数据
BINDER_SET_MAX_THREADSsize_t设置最大线程数
BINDER_SET_CONTEXT_MGRint or flat_binder_object设置当前进程为ServiceManager
BINDER_THREAD_EXITint删除 binder 线程
BINDER_VERSIONstruct binder_version获取 binder 协议版本

3. 应用程序编写

可以在这里下载好示例代码再接着往下看。

frameworks/native/cmds/servicemanager 目录下的 binder.cbctest.c 针对应用编写的需求,对open mmap ioctl 等基本操作做了封装,提供了以下几个函数:

  • binder_open:用于初始化 binder 驱动
  • binder_become_context_manager:设置当前进程为 ServiceManager
  • svcmgr_lookup:用于向 ServiceManager 查找服务
  • svcmgr_publish:用于向 ServiceManager 注册服务
  • binder_call:用于发起远程过程调用
  • binder_loop:进入循环,在循环中,获取和解析收到的 binder 数据

3.1 Server 端的实现

接下来,我们就开始写 Server 端的代码,相关的代码在 binder_server.c 中,主要步骤如下:

  • 定义服务
  • 定义服务回调函数
  • 完成 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;
}

可以看出,服务就是若干函数的集合。

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;
}

回调函数的流程结构还是比较清晰明的:

  • 取出 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);
}

完成主函数编写:

  • 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;
}

main 函数主要调用了 binder_opensvcmgr_publishbinder_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;
    }

    //告诉驱动,当前进程是
    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;
}

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;

}

接下来,实现 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");

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",
    ],
}

终端进入系统源码,然后执行:

source build/envsetup.sh
lunch aosp_x86_64-eng
emulator

将源码移动到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

接下来 adb shell 进入模拟器 shell 环境:

adb shell
cd /data/local/tmp
./binderserver
# 从新开一个终端进入 adb shell
cd /data/local/tmp
./binderclient

最后通过 logcat 查看执行结果:

logcat | grep "BinderServer"

11-22 17:08:39.133  6594  6594 W BinderServer: say hello : 1

参考资料

关于

如果你对 Android Framework 感兴趣,可以持续关注:

  • 我的个人微信(可以加我微信进 Android Framework 交流群)