一、进程间通信:
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地址)是多少,我要发语音信号过去。然后就很好理解了。
关注公众号,及时拿到一手资源: