Android Binder通信01 - 架构介绍

一、进程间通信:

1、概述:

进程间通信就是进程间需要交换数据。我们知道操作系统提供给进程的内存都叫做“虚拟内存”,比如,一个内存为4G的电脑,系统给自己内核保留1GB的内存,那么剩下的3GB内存就可以提供给所有进程使用,1个进程可以访问0~3G,5个进程,每个进程依然可以访问0-3G这片地址。

  • 只有真正保存数据的时候,才将虚拟内存和物理内存关联,此刻才是真正消耗了物理内存(这一过程由MMU完成);
  • 每个进程虚拟地址映射到物理内存的地址是不一样的(隔离的),“共享内存”这种进程间通信方式例外;

2、实现方法:

前面讲过不通进程间一般在物理内存地址是隔离的(也就是进程是管理资源的最小单位),那么想进程间通信怎么办呢?你猜对了,就是找个公共空间,如下图:

在这里插入图片描述

那么,对于一台计算机公共存储空间有什么呢?硬盘?内存?内核?

  • Binder通信和Socket通信就是选择通过内核来完成数据交换;
  • 共享内存就是选择通过内存来通信;
  • 硬盘嘛!唔~!一个写文件,一个读文件??可能有些业务场景会用到,日志管理系统什么的吧,反正读写硬盘效率极低,又有点小蠢!!!

二、Binder通信框架:

Android系统大量使用了binder通信。

  • binder通信的优点:高效(只有一次数据拷贝)、安全、支持多对一;

  • 缺点:不适用于大数据传递、不支持双向发送请求(可以用回调方式协助);

1、基本概念:

RPC:

RPC(Remote Procedure Call,远程过程调用)是一种通信机制,允许一个进程调用另一个地址空间(通常在远程计算机上)的过程或函数,就像调用本地过程一样。

Android源代码到处是这样的调用,我们看下这样调用的思路:

2、框架:

在这里插入图片描述

可以看到,我们用户空间总共有三个进程,提供三种服务。然后驱动层是他们公共空间,可以将数据从拷贝到内核空间达成交换数据的目的;

为什么这么设计?

比如,我们开发有一个进程A,想开发一个功能,让手机的手电筒打开;但是,手电筒只有进程B的 OpenLed 函数可以控制;这在商业项目中非常常见,模块高内聚,低耦合,职责非常单一。要做到进程A直接调用进程B的 OpenLed 函数(前面说的RPC),这个时候,进程A就是Client,进程B是Server;进程A,首先找到Binder的大管家ServiceManager获取进程B的句柄handle(进程B提前已经告诉(注册)大管家自己的句柄id是啥了),接着,进程A将 OpenLed 需要的参数进行打包封装,然后通过ioctl拷贝给内核里面的 Binder驱动,驱动拿到这些数据之后,寻思着这是发给谁的呢?一看数据包裹里面的handle就明白了,于是将数据转给(这儿其实是mmap,先有点印象别计较)进程B,进程B拿到之后,看看进程A是想调用我哪个函数?里面有个code给函数编号了,于是乎,进程B就去调用自己本地的OpenLed ,手电筒就打开了。

当然,进程B打开手电筒之后还可以将函数的执行结果信息(返回值,出参啥的)返回给进程A,这个不影响理解,先不管。

下面看看这三个进程分别干了什么:

  • Client进程:
    • open驱动;
    • 获取服务(向ServiceManager查询服务,获得一个handle);
    • 向handle发数据;
  • ServiceManager进程:(下文简写为sm)
    • open驱动;
    • 告诉驱动,我是service manager,我是大管家,以后所有的服务要想使用binder,必须告诉我他们叫啥,handle是多少。
    • 然后就是循环:
      • 从驱动读取数据;
      • 解析数据;
      • 处理数据;其实我们使用sm的功能相对单一,就是两种:注册服务(将服务名添加到自己的链表中)和获取服务(从链表查找服务,返回服务handle);
  • Server进程:
    • open驱动;
    • 注册服务;将自己的服务名发给大管家sm;
    • 循环:
      • 从驱动读取数据;
      • 解析数据,并调用对应的函数;
      • 返回数据;

三、ServiceManager:

上面已经说了,sm就是一个大管家,负责将每个在它这儿注册过的服务handle(理解成句柄或者id都行吧)记录下来,如果有人想向这个服务发送数据,先得向大管家获取handle,再去发送。还是听绕的,再尝试说明白一些:

内核的binder驱动只认handle,但是这个handle又是一串数字,所以服务自己注册的时候告诉sm说:我叫"二狗",我的handle是0x100;完事,Client向给服务发数据,就先得问sm,你给我查查,“二狗”的handle是多少,sm在自己的小本子上找到给Client即可;

1、代码走读:

注意,我会把不重要的代码都删掉,否则太庞大了!!!!

先进进程的main函数看看:

// 文件路径:.\android10-main\frameworks\native\cmds\servicemanager\service_manager.c
int main(int argc, char** argv)
{
    struct binder_state *bs;
    char *driver;

    driver = "/dev/binder";

    // 打开驱动
    bs = binder_open(driver, 128*1024);
    
    // 告诉驱动,我是"service manager""我是大管家
    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }

    // 循环从驱动读取数据、解析数据、调用(注册服务、获取服务)
    // 传进去的这个 svcmgr_handler 就是处理函数
    binder_loop(bs, svcmgr_handler);

    return 0;
}

我们发现binder驱动节点是"/dev/binder";然后进去看看binder_loop;

// 文件路径:.\android10-main\frameworks\native\cmds\servicemanager\binder.c

void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    uint32_t readbuf[32];

    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;

    readbuf[0] = BC_ENTER_LOOPER;
    binder_write(bs, readbuf, sizeof(uint32_t));

    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;
        // 读取数据
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);

        if (res < 0) {
            ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
            break;
        }
        // 解析并处理数据
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        if (res == 0) {
            ALOGE("binder_loop: unexpected reply?!\n");
            break;
        }
        if (res < 0) {
            ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));
            break;
        }
    }
}

注意:

binder_parser的第三个入参readbuf就是从驱动获取到的数据;

第五个参数func就是注册的处理函数(解析出数据之后用这个处理),我们前面已经看到了,这个func传入的名字叫做svcmgr_handler。

在进去看看binder_parse怎么解析处理数据的:

int binder_parse(struct binder_state *bs, struct binder_io *bio,
                 uintptr_t ptr, size_t size, binder_handler func)
{
    int r = 1;
    uintptr_t end = ptr + (uintptr_t) size;

    while (ptr < end) {
        uint32_t cmd = *(uint32_t *) ptr;
        ptr += sizeof(uint32_t);
        switch(cmd) {
 		// 已经被在下删掉了...
        case BR_TRANSACTION: {
            struct binder_transaction_data_secctx txn;
            if (cmd == BR_TRANSACTION_SEC_CTX) {
                if ((end - ptr) < sizeof(struct binder_transaction_data_secctx)) {
                    ALOGE("parse: txn too small (binder_transaction_data_secctx)!\n");
                    return -1;
                }
                memcpy(&txn, (void*) ptr, sizeof(struct binder_transaction_data_secctx));
                ptr += sizeof(struct binder_transaction_data_secctx);
            } else /* BR_TRANSACTION */ {
                if ((end - ptr) < sizeof(struct binder_transaction_data)) {
                    ALOGE("parse: txn too small (binder_transaction_data)!\n");
                    return -1;
                }
                memcpy(&txn.transaction_data, (void*) ptr, sizeof(struct binder_transaction_data));
                ptr += sizeof(struct binder_transaction_data);

                txn.secctx = 0;
            }

            binder_dump_txn(&txn.transaction_data);
            if (func) {
                unsigned rdata[256/4];
                struct binder_io msg;
                struct binder_io reply;
                int res;

                bio_init(&reply, rdata, sizeof(rdata), 4);
                bio_init_from_txn(&msg, &txn.transaction_data);
                // 调用处理函数处理数据,比如,如果是service manager,会传入一个 svcmgr_handler
                res = func(bs, &txn, &msg, &reply);
                if (txn.transaction_data.flags & TF_ONE_WAY) {
                    binder_free_buffer(bs, txn.transaction_data.data.ptr.buffer);
                } else {
                    // 有必要的话,还要回复数据
                    binder_send_reply(bs, &reply, txn.transaction_data.data.ptr.buffer, res);
                }
            }
            break;
        }
        case BR_REPLY: {
            // 已经被在下删掉了.....
        default:
            ALOGE("parse: OOPS %d\n", cmd);
            return -1;
        }
    }

    return r;
}

发现就是将数据ptr解析出来,然后调用func函数去处理,其实就是svcmgr_handler;

我们再看看svcmgr_handler是如何处理这些数据的:

// 文件路径:.\android10-main\frameworks\native\cmds\servicemanager\service_manager.c
int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data_secctx *txn_secctx,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    struct svcinfo *si;
    uint16_t *s;
    size_t len;
    uint32_t handle;
    uint32_t strict_policy;
    int allow_isolated;
    uint32_t dumpsys_priority;

    struct binder_transaction_data *txn = &txn_secctx->transaction_data;

    if (txn->target.ptr != BINDER_SERVICE_MANAGER)
        return -1;
    // 我删了不少呢....

    switch(txn->code) {
    // 获取服务的时候,从本地链表查找到服务,并将服务的handle放到reply返回
    case SVC_MGR_GET_SERVICE:
    case SVC_MGR_CHECK_SERVICE:
        s = bio_get_string16(msg, &len);
        if (s == NULL) {
            return -1;
        }
        handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid,
                                 (const char*) txn_secctx->secctx);
        if (!handle)
            break;
        bio_put_ref(reply, handle);
        return 0;
    // 添加服务的时候,就是将这个服务的handle加入到本地链表
    case SVC_MGR_ADD_SERVICE:
        s = bio_get_string16(msg, &len);
        if (s == NULL) {
            return -1;
        }
        handle = bio_get_ref(msg);
        allow_isolated = bio_get_uint32(msg) ? 1 : 0;
        dumpsys_priority = bio_get_uint32(msg);
        if (do_add_service(bs, s, len, handle, txn->sender_euid, allow_isolated, dumpsys_priority,
                           txn->sender_pid, (const char*) txn_secctx->secctx))
            return -1;
        break;
            // 我删了不少呢....
    default:
        ALOGE("unknown code %d\n", txn->code);
        return -1;
    }

    bio_put_uint32(reply, 0);
    return 0;
}

其实就是简单的操作链表了;

四、开发自己的App:

经过前面的分析,并且走读了ServiceManager代码,其实,我们app怎么开发已经有个大概思路了;

1、Client:

  • 打开binder驱动,调用binder_open;
  • 获得服务handle;
  • 构造要发送的数据,要求格式是binder_io格式;
  • 调用binder_call,会传入handle(发给谁)、code(调用handle的哪个函数)、binder_io(就是具体数据了);
  • 解析返回的数据(也是binder_io格式),从中拿到返回值;

看看,是不是就向调用自己本地函数一样!!

2、Server:

  • 同样打开binder驱动;
  • 将自己注册给大管家sm;
  • 读数据(通过ioctl);
  • 解析数据(获得发送端打包的参数),获得其中的code和参数;
  • 根据code(一个函数编号)去调用具体的函数;
  • 将函数执行返回值出参什么的打包,又通过驱动发给Client端;

备注:

这里面真正调用binder_call实现这个RPC功能的时候,ioctl用的数据格式是binder_write_read格式,所以,写之前需要将binder_io转换为binder_write_read格式,接收方收到数据,先将数据从binder_write_read转换为binder_io格式。

3、编码:

Server代码:

// 给test函数定义一个code(一定要和client端保持一致)
#define DEMO_SVR_CMD_TEST 0

// 添加服务demo到sm中
int svcmgr_publish(struct binder_state *bs, uint32_t target, const char *param, void *ptr)
{
    int status;
    unsigned iodata[512/4];
    struct binder_io msg, reply;

    bio_init(&msg, iodata, sizeof(iodata), 4);
    bio_put_uint32(&msg, 0);  // strict mode header
    bio_put_string16_x(&msg, SVC_MGR_NAME);
    bio_put_string16_x(&msg, param);
    bio_put_obj(&msg, ptr);

    if (binder_call(bs, &msg, &reply, target, SVC_MGR_ADD_SERVICE)) {
		return -1;
	}
    status = bio_get_uint32(&reply);
    binder_done(bs, &msg, &reply);
    return status;
}

int32_t test(char *param)
{
	// 我们这儿就直接写死返回即可
	return 0xddd;
}

// 处理demo服务收到的所有数据,并将返回值添加到reply
int demo_service_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{	
    uint16_t *s;
	char param[512];
    size_t len;
    uint32_t handle;
	int32_t ret;
	// txn->code就是函数编号,我们这儿就是判断是不是test函数,并进行处理
    switch(txn->code) {
    case DEMO_SVR_CMD_TEST:
		// 数据构成是:长度+内容,分别解析出来
		s = bio_get_string16(msg, &len);
		if (s == NULL) {
			return -1;
		}
		for (i = 0; i < len; i++) {
			param[i] = s[i];
		}
		param[i] = '\0';

		// 将函数入参param传给本地的test,得到返回结果ret
		ret = test(param);

		// 将执行结果ret放入reply,后面返回给client时候要用
		bio_put_uint32(reply, ret);
        break;
    default:
        return -1;
    }
    return 0;
}

int main(int argc, char **argv)
{
    struct binder_state *bs;
	int ret;
	// 打开驱动
    bs = binder_open(128*1024);
    if (!bs) {
        return -1;
    }

	// 添加demo服务到sm中
	ret = svcmgr_publish(bs, BINDER_SERVICE_MANAGER, "demo", (void *)123);
    if (!ret) {
        return -1;
    }
	// 循环读取数据、解析并处理数据
    binder_loop(bs, demo_service_handler);

    return 0;
}

Client代码:

// 给test函数定义一个code
#define DEMO_SVR_CMD_TEST 0

// 从SM中获取demo这个服务放入handle
uint32_t svcmgr_lookup(struct binder_state *bs, uint32_t target, const char *param)
{
    uint32_t handle;
    unsigned iodata[512/4];
    struct binder_io msg, reply;

    bio_init(&msg, iodata, sizeof(iodata), 4);
    bio_put_uint32(&msg, 0);  // strict mode header
    bio_put_string16_x(&msg, SVC_MGR_NAME);
    bio_put_string16_x(&msg, param);

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


struct binder_state *g_bs;
uint32_t g_handle;
// 给demo服务的test函数发送个消息,并等待返回值ret
int test(char *param)
{
	unsigned iodata[512/4];
	struct binder_io msg, reply;
	int ret;

	// 构造binder_call需要的数据格式binder_io
	bio_init(&msg, iodata, sizeof(iodata), 4);
	bio_put_uint32(&msg, 0);  // strict mode header
	// 放入要发送的数据param
    bio_put_string16_x(&msg, param);

	// 执行rpc,也就是调用binder_call
	if (binder_call(g_bs, &msg, &reply, g_handle, DEMO_SVR_CMD_TEST)) {
		return 0;
	}
	
	// 从reply解析返回值为ret
	ret = bio_get_uint32(&reply);
	binder_done(g_bs, &msg, &reply);
	return ret;
	
}

int main(int argc, char **argv)
{
    int fd;
    struct binder_state *bs;
    uint32_t handle;
	int ret;

    bs = binder_open(128*1024);
    if (!bs) {
        return -1;
    }
	
	g_bs = bs;

	/* get service */
	handle = svcmgr_lookup(bs, BINDER_SERVICE_MANAGER, "demo"); // sm的id是0
	if (!handle) {
        return -1;
	}
	g_handle = handle;

	/* send data to server */
	ret = test(argv[2]);
	// test函数返回值就是ret
	
	binder_release(bs, handle);
    return 0;
}

五、binder_call函数:

发现上面多出使用binder_call来完成rpc,我们就看看这个源代码:

// 发起远程调用(rpc)
int binder_call(struct binder_state *bs,
                struct binder_io *msg, struct binder_io *reply,
                uint32_t target, uint32_t code)
{
    int res;
    // 驱动需要的格式是 binder_write_read,而我们的入参是 binder_io,需要转换
    // 通过入参的msg、target、code就能构造一个binder_write_read
    struct binder_write_read bwr;
    struct {
        uint32_t cmd;
        struct binder_transaction_data txn;
    } __attribute__((packed)) writebuf;
    unsigned readbuf[32];

    if (msg->flags & BIO_F_OVERFLOW) {
        fprintf(stderr,"binder: txn buffer overflow\n");
        goto fail;
    }

    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;

    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;
        // 直接写驱动(发起函数调用)
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);

        if (res < 0) {
            fprintf(stderr,"binder: ioctl failed (%s)\n", strerror(errno));
            goto fail;
        }
        // 解析函数返回值
        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;
}

关键步骤我都写了注释,其实主要干了三件事:

  • 数据结构转换;
  • 发起函数调用;
  • 解析函数执行返回结果;

六、总结:

其实这个binder通信就像打电话一样,ServiceManager就相当于运营商,你去向运营商开卡,就相当于注册了服务,这个服务在运营商的网络中可能一直用一个电话卡的ip来标识,由于ip非常难记住,运行商就给起了一个friendly name(友好的名字),也就是电话号码;当你拨通电话号码的过程中,你的拨号软件可能会先去运营商问问,这个这个电话号码的handle(ip地址)是多少,我要发语音信号过去。然后就很好理解了。

关注公众号,及时拿到一手资源:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值