Android Binder通信01 - 架构介绍

207 阅读13分钟

一、进程间通信:

1、概述:

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

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

2、实现方法:

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

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

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

二、Binder通信框架:

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

  • binder通信的优点:高效(只有一次数据拷贝)、安全、支持多对一;
  • 缺点:不适用于大数据传递、不支持双向发送请求(可以用回调方式协助);

1、基本概念:

RPC:

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

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

2、框架:

image.png

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

为什么这么设计?

比如,我们开发有一个进程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", demo_service_handler);
     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地址)是多少,我要发语音信号过去。然后就很好理解了。