android 系统核心机制binder(01)C语言简谈binder

198 篇文章 98 订阅

该系列文章总纲链接:专题总纲目录 Android Framework 总纲


本章关键点总结 & 说明:

上面的导图描述了Binder框架中主要几个部分,同时本章节主要通过Bctest的案例 对Binder原理进行简单说明。

binder系统概述:Binder系统是一种基于IPC的RPC (远程过程调用) 通信机制

android为什么要引入binder通信机制?

  1. Binder使用Client-Server通信方式
  2. 安全性好,简单高效
  3. 面向对象的设计
  4. 独特的接收缓存管理和线程池管理方式

这里从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 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 <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <linux/types.h>
#include<stdbool.h>
#include <string.h>

#include <private/android_filesystem_config.h>

#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 <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <linux/types.h>
#include<stdbool.h>
#include <string.h>

#include <private/android_filesystem_config.h>

#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 <name>
 */

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 <hello\n", argv[0]);
        fprintf(stderr, "%s <hello\n> <name>\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能够提供这样的能力。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图王大胜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值