android 系统核心机制binder(01)C语言简谈binder
该系列文章总纲链接:专题分纲目录 android 系统核心机制 binder
本章关键点总结 & 说明:
上面的导图描述了Binder框架中主要几个部分,同时本章节主要通过Bctest的案例 对Binder原理进行简单说明。
binder系统概述:Binder系统是一种基于IPC的RPC (远程过程调用) 通信机制
android为什么要引入binder通信机制?
- Binder使用Client-Server通信方式
- 安全性好,简单高效
- 面向对象的设计
- 独特的接收缓存管理和线程池管理方式
这里从C层的binder进行分析,因为这是android原生系统给出的案例,因此就从这里开始分析。实际上无论从哪里分析,原理都是一样的,从servicemanager启动,到服务端注册服务,再到客户端获取服务,无非是在这里面添加了框架,以及C++和Java层,让我们感觉很多地方云里来雾里去。但实际上Binder就是一个通信机制。本节 抛开框架,简单说下binder的原理。
1 从C测试程序开始Binder系统的学习
首先从Binder系统的C程序开始说起,文件位置在frameworks\native\cmds\servicemanager
1.1 Android Binder系统整体简要框架
这里关注了整体流程,下面是servicemanager运行流程:
//service_manager运行流程,service_manager.c :a. binder_openb. binder_become_context_managerc. binder_loop(bs, svcmgr_handler); c.1 res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); c.2 binder_parse // 解析 // 处理 : svcmgr_handler SVC_MGR_GET_SERVICE/SVC_MGR_CHECK_SERVICE : 获取服务 SVC_MGR_ADD_SERVICE : 注册服务 // 回复
下面是 服务端 服务注册流程:
//bctest.c,注册服务的过程:a. binder_openb. binder_call(bs, &msg, &reply, 0, SVC_MGR_ADD_SERVICE) // 含有服务的名字 // 它会含有servicemanager回复的数据 // 0表示servicemanager // code: 表示要调用servicemanager中的"addservice函数"
下面是 客户端获取服务流程:
//获取服务的过程:a. binder_openb. binder_call(bs, &msg, &reply, target, SVC_MGR_CHECK_SERVICE) // 含有服务的名字 // 它会含有servicemanager回复的数据, 表示提供服务的进程 // 0表示servicemanager // code: 表示要调用servicemanager中的"getservice函数"
1.2 明确2个概念IPC与RPC
@1 IPC : Inter-Process Communication, 进程间通信
三个关键要素:源(source)、数据(data)、目的(target),如下所示:
对于binder系统来讲
假设源是服务端A,目的是ServiceManager;A向ServiceManager发送数据,表示注册服务的IPC流程
假设源是客户端B,目的是ServiceManager;B向ServiceManager发送数据,表示查询并获取服务handle的IPC流程
IPC表示进程间通信机制
@2 RPC : Remote Procedure Call, 远程过程调用
关注server端获取的数据中含有的是哪个函数的编号,即传递的什么参数,客户端得到的返回值是什么。
假设客户端B获取服务A成功,得到A的handle,那么使用服务端时就是实现RPC远程过程调用的过程
RPC是基于IPC的远程过程调用,如下所示:
1.3 关注C框架实现Binder的过程,分析binder.c中关键方法binder_call
binder_call的实现,代码与注解如下:
int binder_call(struct binder_state *bs, struct binder_io *msg, struct binder_io *reply, void *target, uint32_t code){ int res; struct binder_write_read bwr; struct { uint32_t cmd; struct binder_txn txn; } writebuf; unsigned readbuf[32]; if (msg->flags & BIO_F_OVERFLOW) { fprintf(stderr,"binder: txn buffer overflow\n"); goto fail; } //根据binder_io、target、code来构造writebuf writebuf.cmd = BC_TRANSACTION; writebuf.txn.target = target; writebuf.txn.code = code; writebuf.txn.flags = 0; writebuf.txn.data_size = msg->data - msg->data0; writebuf.txn.offs_size = ((char*) msg->offs) - ((char*) msg->offs0); writebuf.txn.data = msg->data0; writebuf.txn.offs = msg->offs0; //binder_io转换成binder_write_read bwr.write_size = sizeof(writebuf); bwr.write_consumed = 0; bwr.write_buffer = (unsigned) &writebuf; hexdump(msg->data0, msg->data - msg->data0); for (;;) { bwr.read_size = sizeof(readbuf); bwr.read_consumed = 0; bwr.read_buffer = (unsigned) readbuf; //通过ioctl下发给驱动程序 res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); if (res < 0) { fprintf(stderr,"binder: ioctl failed (%s)\n", strerror(errno)); goto fail; } //解析数据,ioctl写入数据给驱动的同时也从驱动中读取数据,这里将读到的binder_write_read结构转换成binder_io结构的reply发送给对端 res = binder_parse(bs, reply, 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_call的功能是将binder_io结构体转换成binder_write_read并通过ioctl发送给驱动程序,并调用binder_parse[说明:将binder_write_read结构体转换为binder_io结构体]回复对端。
2 C实现的Binder测试程序(这里仅说明实现思路与框架)
2.1 代码文件目录说明:
├── Android.mk
├── Makefile
├── README.md
├── binder.c //系统文件
├── binder.h //系统文件
├── include
│ ├── linux
│ │ └── binder.h //系统文件
│ └── private
│ ├── android_filesystem_capability.h //系统文件
│ └── android_filesystem_config.h 系统文件
├── service_manager.c //系统修改文件,删减了selinux相关代码
├── test_client.c //参考bctest.c代码
├── test_server.c //参考bctest.c代码
└── test_server.h //仅定义一些宏
这里关键实现的是test_server.c和test_client.c,即服务端和客户端的代码实现
2.2 源码实现框架
@1 测试程序的客户端和服务端 逻辑流程解读(这里做了简化,忽略了ServiceManager)
@2 RPC机制的调用流程解读(这里做了简化,忽略了ServiceManager)
这里以test_client中sayhello_to的参数传递为例进行RPC机制调用流程的说明,如下图所示:
2.3 关键源码说明
@1 这里对关键源码进行说明:test_server.c实现如下:
#include #include #include #include #include#include #include #include "binder.h"#include "test_server.h"void sayhello(void){static int cnt = 0;fprintf(stderr, "say hello : %d\n", ++cnt);}int sayhello_to(char *name){static int cnt = 0;fprintf(stderr, "say hello to %s : %d\n", name, ++cnt);return cnt;}int hello_service_handler(struct binder_state *bs, struct binder_transaction_data *txn, struct binder_io *msg, struct binder_io *reply){ uint16_t *s;char name[512]; size_t len; uint32_t handle; uint32_t strict_policy;int i; strict_policy = bio_get_uint32(msg); switch(txn->code) { case HELLO_SVR_CMD_SAYHELLO:sayhello();bio_put_uint32(reply, 0); /* no exception */ return 0; case HELLO_SVR_CMD_SAYHELLO_TO:s = bio_get_string16(msg, &len); //"IHelloService"s = bio_get_string16(msg, &len); // nameif (s == NULL) {return -1;}for (i = 0; i < len; i++)name[i] = s[i];name[i] = '\0';i = sayhello_to(name);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;}int main(int argc, char **argv){ int fd; struct binder_state *bs; uint32_t svcmgr = BINDER_SERVICE_MANAGER; uint32_t handle;int ret; bs = binder_open(128*1024); ... /* add service, bctest.c实现,这里调用了binder_call*/ ret = svcmgr_publish(bs, svcmgr, "hello", hello_service_handler); ... binder_set_maxthreads(bs, 10); binder_loop(bs, hello_service_handler); return 0;}
服务端主要提供了sayhello与sayhello_to的功能实现
@2 test_client.c实现如下:
#include #include #include #include #include#include #include #include "binder.h"#include "test_server.h"struct binder_state *g_bs;uint32_t g_hello_handle;uint32_t g_goodbye_handle;void sayhello(void){ 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, "IHelloService"); if (binder_call(g_bs, &msg, &reply, g_hello_handle, HELLO_SVR_CMD_SAYHELLO)) return ; binder_done(g_bs, &msg, &reply);}int sayhello_to(char *name){ unsigned iodata[512/4]; struct binder_io msg, reply; int ret; int exception; 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); if (binder_call(g_bs, &msg, &reply, g_hello_handle, HELLO_SVR_CMD_SAYHELLO_TO))return 0; exception = bio_get_uint32(&reply); if (exception)ret = -1; ret = bio_get_uint32(&reply); binder_done(g_bs, &msg, &reply); return ret;}/* ./test_client hello * ./test_client hello */int main(int argc, char **argv){ int fd; struct binder_state *bs; uint32_t svcmgr = BINDER_SERVICE_MANAGER; uint32_t handle; int ret; if (argc < 2){ fprintf(stderr, "Usage:\n"); fprintf(stderr, "%s \n", argv[0]); return -1; } bs = binder_open(128*1024); if (!bs) { fprintf(stderr, "failed to open binder driver\n"); return -1; } g_bs = bs; /* get service ,bctest.c实现*/ handle = svcmgr_lookup(bs, svcmgr, "hello"); if (!handle) { fprintf(stderr, "failed to get hello service\n"); return -1; } g_hello_handle = handle; fprintf(stderr, "Handle for hello service = %d\n", g_hello_handle); /* send data to server */ if (!strcmp(argv[1], "hello")) {if (argc == 2) { sayhello();} else if (argc == 3) { ret = sayhello_to(argv[2]); fprintf(stderr, "get ret of sayhello_to = %d\n", ret);} } binder_release(bs, handle); return 0;}
@3 test_server.h关键实现如下:
#define HELLO_SVR_CMD_SAYHELLO 1#define HELLO_SVR_CMD_SAYHELLO_TO 2
2.4 执行与测试
@1 执行编译后,在android系统上执行流程如下所示:
# Android系统中已经有service_manager, 所以不要再次执行它 ./test_server & ./test_client hello ./test_client hello stringbalabala
@2 嵌入式开发环境中执行make即可,在非android系统上执行流程如下所示:
./service_manager &./test_server &./test_client hello./test_client hello stringbalabala
总结下:改测试程序本身就是一个client,一个server。这是在C的层面来看binder,从C++和java层看就是我们熟悉的BpXXX BnXXX。。。等等。
3 最后简单 谈谈 Binder的历史
Android的这套基于Binder的IPC机制,源自于传奇性但又比较悲催的OpenBinderIPC框架。
OpenBinder是由一家叫Be的公司开发,这家传奇性的法国软件公司制造了传奇性的BeOS,当年苹果公司在操作系统研发上遇到困境时,可以用来拯救苹果的操作系统方案,一个是NeXT,另一个便是BeOS。BeOS在构架上和成熟度上比NeXT更具优势,但或许是有优势的东西就会有更高的姿态,要价更高,于是机会便被留给了NeXT,于是有了今天的MacOSX和iOS。BeOS在构架上设计思路上在当年还是很先进的,就比如延用至今的OpenBinder,如果仔细看BeOS的编程文档,会发现整个系统交互与今天的Android有很大的类似之处。可惜后来BeOS最终没有避免破产的命运,BeOS就被作为软件资产,在2001年被Palm收购。
OpenBinder在Palm也曾风光一时。Palm以简洁低功耗设备迅速成长起来之后,也需要一种高效的,类似于Corba的系统级消息互通机制,以构造更复杂的系统。这样的尝试,得到了一个悲情的操作系统,Palm OS Cobalt(Palm OS 6),本来作为Palm OS 5的后继者,这一操作系统被寄予很大期望,但没有产商愿意生产基于它的设备。但得到的好处是,OpenBinder在这种商业应用前景不明的情况最终还是选择了开源,OpenBinder见OpenBinder链接 ,这个网站是OpenBinder创造者Dianne Kyra Hackborn的个人网站。而OpenBinder所依存的操作系统环境很不稳定,需要考虑兼容性又被迫随着市场需求在多种操作系统内核上移植,历经BeOS、Windows、PalmOS Cobalt的微内核、Linux内核,最终使OpenBinder具备强大的可移植性。
虽然OpenBinder在技术上是一种很优秀的方案,其命运却是一再如此悲催,使用OpenBinder技术的操作系统,都没有走入主流然后就销声匿迹。在今天的操作系统世界里,使用OpenBinder的并不多,仅ALP(ACESS Linux Platform)在使用OpenBinder作为其IPC机制,但ALP所占市场份额实在太小,发展前景很不明朗。但革命性的Android操作系统,最终选择了这套方案,使OpenBinder终于发挥了其强大潜力。Android使用OpenBinder的基本构架,但并非完整的OpenBinder,所有只称其为BinderIPC。在Android系统的设计者眼中,Android跟OpenBinder并无直接联系,会强调Android世界里的Binder是独特设计过的,可能是出于法律上的顾虑。但我们对比OpenBinder和Android里的Binder实现,就会发现这两者在本质上是一样的。相对而言,Android的Binder机制是OpenBinder的一种简化版本,学习Android底层开发,如果Binder本身不容易理解,可以参考OpenBinder的文档。
至于Android为什么会选择Binder作为其底层通信机制而不是重新设计或是借用已有方案,坊间谣传是由于Android底层开发人员大都曾是BeOS或是Palm的开发人员,更熟悉这套开发框架。但如果不是Binder机制足够优秀,可能也会在Android系统的发展中被抛弃。 Binder提供了一种功能强大、简洁、高效、面向对象的跨进程传递方式,而到目前为此,还只是Binder能够提供这样的能力。