第五章:Binder系统(1)-C程序示例_框架分析

这一章节我们讲解android中的Binder系统,该章节是一个比较难理解的章节,不过没关系,我们可以慢慢的学习。

在Binder系统中,有两大核心,分别为IPC(Information Processing Center:信息处理中心,进程间的通信)与RPC(远程过程控制调用)。

IPC:进程间的通信,远程调用,比如我们的A进程需要打开LED灯,调用led_open/led_ctl方法,但是他是没有权限去操作的,所以进程A通过:1.首先构造一些数据,2.通过IPC发送数据到进程B,然后B进程:1.取出数去,2.调用本地的led_open/led_ctl。表面看起来,我们是通过进程A直接操控LED。

RPC:RPC是在IPC的基础上做了一些封装。可以认为是调用某个进程的函数(A进程去调用B进程的函数)。即AB进程约定好相应的格式,如对函数进行编号

数据的传输有三大要素,分别为源,目的已经数据。找上述的例子中可以如下描述:
源(进程A):发送数据,A向serviceManager查询led服务,获取一个handle(对硬件操作的服务),该handle指向进程B。
目的(进程B):B向serviceManager注册LED服务,以便A进程获取
数据:点亮,或者熄灭闪烁等。

我们可以分析出框架大致如下:
在这里插入图片描述
这是一个大致的框架,下面我们开始分析源码,看系统具体是如何使用或者实现的。

源码总体分析

相关问价你主要集中在SDK/frameworks\native\cmds\servicemanager目录下,其中存在文件service_manager.c,bctest.c(半成品),binder.c(封装好的C库)。根据前面的框图,我们分别来了解client,servicemanager,service分别做了,或者需要做什么。前面提到需要注册服务,那么肯定最先运行的是servicemanager,其主做了以下工作:
servicemanager:
首先先打开驱动,并且告诉驱动他是servicemanager,这样他就有了驱动的操作权限,并且他是是一个while(1)循环,不停的读取数据,解释数据,如果有需要注册服务,则保存注册的服务到链表中,如果有需要获取服务,则从链表查询服务返回,在没有事情做的时候会处于休眠状态。

下面我们看看service:
在这里插入图片描述
首先open打开驱动,然后向servicemanager注册service,只有注册了service,在后续中client才能从servicemanager中获取service,他竟然是service,那么他肯定也是一个循环,不停的等待client发送指令,然后调用对应的驱动函数,读取数据,然后解析数据。

最后我们在来看看client:
在这里插入图片描述
首先也是open打开驱动,然后向servicemanager查询获取服务,最后向handle(service服务)发送数据。

从上面简单的分析可以看出,service在注册服务的时候没事需要向进程servicemanage发送消息的,client也会向servicemanage进程发送消息获取服务,那么他们怎么知道哪个是servicemanage呢?在servicemanage分析中,他会告诉驱动他是servicemanage(上面已经提及过)。下面我们开始追踪andriod源码,也就是上面提到的那几个文件,看到具体是怎么回事

源码追踪

首先我们打开SDK\frameworks\native\cmds\servicemanager\service_manager.c,我们找到最末尾的main函数:

扫描二维码关注公众号,回复: 5261257 查看本文章

service_manager.c

service_manager.c(从main函数开始分析):

//txn->code参数是一个编码,这个编码包含呢很多信息,比如是查找,还是添加服务等等信息,都包含在这个编码中。
svcmgr_handler(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply)
	handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);//根据传入的txn->code,在链表中查找服务,查找到之后返回服务
	do_add_service(bs, s, len, handle, txn->sender_euid,
            allow_isolated, txn->sender_pid) //如果传入的是注册服务,则加入本地链表
            

main()
	binder_open(128*1024); 
		open("/dev/binder", O_RDWR | O_CLOEXEC); //打开驱动binde(前面提到过,进程间的通信都是通过binder)
	binder_become_context_manager(bs); //告诉驱动他是service_manager
	binder_loop(bs, svcmgr_handler); //一个循环,读取处理数据,其中的svcmgr_handler为服务处理函数
		ioctl(bs->fd, BINDER_WRITE_READ, &bwr);  //读取数据
		binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func); //解析数据
			func(bs, txn, &msg, &reply);//处理数据,此处的func就是前面的传入的svcmgr_handler
				

上面的函数调用注解已经很详细了,就不再进行详细的讲解了,下面分析bctest.c(半成品)。

bctest.c

bctest.c中主要实现了两个功能,注册服务和获取服务,首先我们来看看他的注册服务,我们依旧从main函数开始

main()
	binder_open(128*1024);
		open("/dev/binder", O_RDWR | O_CLOEXEC); //打开驱动binde(前面提到过,进程间的通信都是通过binder)
	svcmgr_publish(bs, svcmgr, argv[1], &token);/*注册服务,其中的svcmgr = BINDER_SERVICE_MANAGER(0),打开驱动binde,进行进程间的通信,
	那么你当然需要告诉binder你是和谁通信,要发给谁,这里的BINDER_SERVICE_MANAGER即为service_manager进程,前面的ervice_manager告诉binder他是service_manager就起到了作用*/
		binder_call(bs, &msg, &reply, target, SVC_MGR_ADD_SERVICE) //构建数据之后,调用该函数,msg:含有服务的名字,reply:service_manager返回的数据 ,target:service_manager进程(0),SVC_MGR_ADD_SERVICE添加服务

以上是注册服务的过程,下面分获取服务过程:

main()
	binder_open(128*1024);
		handle = svcmgr_lookup(bs, svcmgr, "alt_svc_mgr");
			binder_call(bs, &msg, &reply, target, SVC_MGR_CHECK_SERVICE) /*构造数据之后调用该函数,msg:含有服务的名字,reply:service_manager返回的数据(即提供服务的进程) ,
			target:service_manager进程(0),SVC_MGR_CHECK_SERVICE:获取服务*/

分析了注册服务和获取服务的大致过程,我们现在来分析其细致原理,即分析binder_call与之前提到svcmgr_handler函数参数txn中的txn->code,binder_call函数时在binder.c中提供的。

binder.c

我们打开binder.c文件,对binder_call进行分析;

int binder_call(struct binder_state *bs,
                struct binder_io *msg, struct binder_io *reply,
                uint32_t target, uint32_t code)

在这里插入图片描述
binder_call为远程调用,那么我们至少要知道调用那个进程,向那个价进程发送数据,调用那个函数,传递什么参数 ,返回值信息保存在哪里。在上图中已经进行了描述。

那我们这个函数怎么用呢?

1. 构造参数:放在buf中,这buf使用struct binder_io *msg进行描述

2. 调用ioctl发送数据:
		res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);/*其中bwr:struct binder_write_read bwr;
		struct binder_io
		{
		    char *data;            /* pointer to read/write from */
		    binder_size_t *offs;   /* array of offsets */
		    size_t data_avail;     /* bytes available in data buffer */
		    size_t offs_avail;     /* entries available in offsets array */
		
		    char *data0;           /* start of data buffer */
		    binder_size_t *offs0;  /* start of offsets buffer */
		    uint32_t flags;
		    uint32_t unused;
		};*/
			
3. ioctl也会接受数据,收到一个binder_write_read。

不知道大家发现一个问题没有,我们构建的是struct binder_io,但是ioctl需要的是struct binder_write_read bwr,那么他们之间是怎么进行转换的呢?那么他们肯定有一个转换过程。

这些都是大致的一个过程,下面我们开始分析源码:

svcmgr_lookup(struct binder_state *bs, uint32_t target, const char *name)//bctest.c文件
	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, name);
	binder_call(struct binder_state *bs,struct binder_io *msg, 	struct binder_io *reply,uint32_t target, uint32_t code)
		------------------------------------------------------
		//对writebu进行构造
		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;
	    --------------------------------------------------------
	    --------------------------------------------------------
		struct binder_write_read bwr;
		bwr.write_size = sizeof(writebuf);
	    bwr.write_consumed = 0;
	    bwr.write_buffer = (uintptr_t) &writebuf;
		--------------------------------------------------------

我们发现调用了很多bio_put去构建&msg,放入使用bio_put,那个获取我们可以使用bio_get。构建好msg然后调用binder_call,在该函数中从把msg中的相关成员赋值给writebuf,最后在让bwr.write_buffer指向writebuf,这样就完成了binder_io ms msg到binder_write_read bwr之间的转换,就能通过ioctl驱动binder。
其中write_buffer定义如下:

    struct {
        uint32_t cmd;
        struct binder_transaction_data txn;
    } __attribute__((packed)) writebuf;
	//其中txn包括了:handle,code,参数。

下面我们总结一下如何使用binder_call:构建struct binder_io *msg,uint32_t target, uint32_t code然后调用binder_call函数即可。

小节结语

分析其内部机制之后,我们归纳一下,应该如何去写应用程序,实现进程之间的通信。

client:
	a.binder_open打开驱动,
	b.获取服务handle,
	c.构造参数binder_io,
	d.调用binder_call(handle,code,binder_io),
	e.分析返回的binder_io,取出返回值。
server:
	a.binder_open打开驱动,
	b.注册服务(service)
	c.ioctrl(read)
	d.解析数据binder_write_read,其中存在成员readbuf.binder_transaction_data.txn(前面提到其中包含handle,code,参数)
	e.根据code,决定调用哪个函数,从binder_io取出需要传递的参数,
	f.把返回值转换为binder_io发送给client。

猜你喜欢

转载自blog.csdn.net/weixin_43013761/article/details/87634100