该系列文章总纲链接:专题分纲目录 android 系统核心机制 binder
上面的导图描述了Binder框架中主要几个部分,同时本章节主要通过Bctest的案例 对Binder原理进行简单说明。
binder系统概述:Binder系统是一种基于IPC的RPC (远程过程调用) 通信机制
android为什么要引入binder通信机制?
这里从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_open
b. binder_become_context_manager
c. 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_open
b. binder_call(bs, &msg, &reply, 0, SVC_MGR_ADD_SERVICE)
// 含有服务的名字
// 它会含有servicemanager回复的数据
// 0表示servicemanager
// code: 表示要调用servicemanager中的"addservice函数"
下面是 客户端获取服务流程:
//获取服务的过程:
a. binder_open
b. 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.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); // name
if (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能够提供这样的能力。