红茶一杯话Binder

(传输机制篇_中)

侯 亮

1 谈谈底层IPC机制吧

在上一篇文章的最后,我们说到BpBinder将数据发到了Binder驱动。然而在驱动层,这部分数据又是如何传递到BBinder一侧的呢?这里面到底藏着什么猫腻?另外,上一篇文章虽然阐述了4棵红黑树,但是并未说明红黑树的节点到底是怎么产生的。现在,我们试着回答这些问题。

1.1 概述

在Binder驱动层,和ioctl()相对的动作是binder_ioctl()函数。在这个函数里,会先调用类似copy_from_user()这样的函数,来读取用户态的数据。然后,再调用binder_thread_write()和binder_thread_read()进行进一步的处理。我们先画一张调用关系图:

binder_ioctl()调用binder_thread_write()的代码是这样的:

  1. if (bwr.write_size > 0)
  2. {
  3. ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer,
  4. bwr.write_size, &bwr.write_consumed);
  5. if (ret < 0)
  6. {
  7. bwr.read_consumed = 0;
  8. if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
  9. ret = -EFAULT;
  10. goto err;
  11. }
  12. }

注意binder_thread_write()的前两个参数,一个是binder_proc指针,另一个是binder_thread指针,表示发起传输动作的进程和线程。binder_proc不必多说了,那个binder_thread是怎么回事?大家应该还记得前文提到的binder_proc里的4棵树吧,此处的binder_thread就是从threads树中查到的节点。

  1. thread = binder_get_thread(proc);

binder_get_thread()的代码如下:

  1. static struct binder_thread *binder_get_thread(struct binder_proc *proc)
  2. {
  3. struct binder_thread *thread = NULL;
  4. struct rb_node *parent = NULL;
  5. struct rb_node **p = &proc->threads.rb_node;
  6. // 尽量从threads树中查找和current线程匹配的binder_thread节点
  7. while (*p)
  8. {
  9. parent = *p;
  10. thread = rb_entry(parent, struct binder_thread, rb_node);
  11. if (current->pid < thread->pid)
  12. p = &(*p)->rb_left;
  13. else if (current->pid > thread->pid)
  14. p = &(*p)->rb_right;
  15. else
  16. break;
  17. }
  18. // “找不到就创建”一个binder_thread节点
  19. if (*p == NULL)
  20. {
  21. thread = kzalloc(sizeof(*thread), GFP_KERNEL);
  22. if (thread == NULL)
  23. return NULL;
  24. binder_stats_created(BINDER_STAT_THREAD);
  25. thread->proc = proc;
  26. thread->pid = current->pid;
  27. init_waitqueue_head(&thread->wait);
  28. INIT_LIST_HEAD(&thread->todo);
  29. // 新binder_thread节点插入红黑树
  30. rb_link_node(&thread->rb_node, parent, p);
  31. rb_insert_color(&thread->rb_node, &proc->threads);
  32. thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
  33. thread->return_error = BR_OK;
  34. thread->return_error2 = BR_OK;
  35. }
  36. return thread;
  37. }

binder_get_thread()会尽量从threads树中查找和current线程匹配的binder_thread节点,如果找不到,就会创建一个新的节点并插入树中。这种“找不到就创建”的做法,在后文还会看到,我们暂时先不多说。

在调用binder_thread_write()之后,binder_ioctl()接着调用到binder_thread_read(),此时往往需要等待远端的回复,所以binder_thread_read()会让线程睡眠,把控制权让出来。在未来的某个时刻,远端处理完此处发去的语义,就会着手发回回复。当回复到达后,线程会从以前binder_thread_read()睡眠的地方醒来,并进一步解析收到的回复。

以上所说,都只是概要性的阐述,下面我们要深入一些细节了。

1.2 要进行跨进程调用,需要考虑什么?

我们可以先考虑一下,要设计跨进程调用机制,大概需要考虑什么东西呢?我们列一下:

1) 发起端:肯定包括发起端所从属的进程,以及实际执行传输动作的线程。当然,发起端的BpBinder更是重中之重。

2) 接收端:包括与发起端对应的BBinder,以及目标进程、线程。

3) 待传输的数据:其实就是前文IPCThreadState::writeTransactionData()代码中的binder_transaction_data了,需要注意的是,这份数据中除了包含简单数据,还可能包含其他binder对象噢,这些对象或许对应binder代理对象,或许对应binder实体对象,视具体情况而定。

4) 如果我们的IPC动作需要接收应答(reply),该如何保证应答能准确无误地传回来?

5) 如何让系统中的多个传输动作有条不紊地进行。

我们可以先画一张示意图:

然而这张图似乎还是串接不起整个传输过程,图中的“传输的数据”到底是怎么发到目标端的呢?要回答这个问题,我们还得继续研究Binder IPC机制的实现机理。

1.3 传输机制的大体运作

Binder IPC机制的大体思路是这样的,它将每次“传输并执行特定语义的”工作理解为一个小事务,既然所传输的数据是binder_transaction_data类型的,那么这种事务的类名可以相应地定为binder_transaction。系统中当然会有很多事务啦,那么发向同一个进程或线程的若干事务就必须串行化起来,因此binder驱动为进程节点(binder_proc)和线程节点(binder_thread)都设计了个todo队列。todo队列的职责就是“串行化地组织待处理的事务”。

下图绘制了一个进程节点,以及一个从属于该进程的线程节点,它们各带了两个待处理的事务(binder_transaction):

这样看来,传输动作的基本目标就很明确了,就是想办法把发起端的一个binder_transaction节点,插入到目标端进程或其合适子线程的todo队列去。

可是,该怎么找目标进程和目标线程呢?基本做法是先从发起端的BpBinder开始,找到与其对应的binder_node节点,这个在前文阐述binder_proc的4棵红黑树时已经说过了,这里不再赘述。总之拿到目标binder_node之后,我们就可以通过其proc域,拿到目标进程对应的binder_proc了。如果偷懒的话,我们直接把binder_transaction节点插到这个binder_proc的todo链表去,就算完成传输动作了。当然,binder驱动做了一些更精细的调整。

binder驱动希望能把binder_transaction节点尽量放到目标进程里的某个线程去,这样可以充分利用这个进程中的binder工作线程。比如一个binder线程目前正睡着,它在等待其他某个线程做完某个事情后才会醒来,而那个工作又偏偏需要在当前这个binder_transaction事务处理结束后才能完成,那么我们就可以让那个睡着的线程先去做当前的binder_transaction事务,这就达到充分利用线程的目的了。反正不管怎么说,如果binder驱动可以找到一个合适的线程,它就会把binder_transaction节点插到它的todo队列去。而如果找不到合适的线程,还可以把节点插入目标binder_proc的todo队列。

1.4 红黑树节点的产生过程

另一个要考虑的东西就是binder_proc里的那4棵树啦。前文在阐述binder_get_thread()时,已经看到过向threads树中添加节点的动作。那么其他3棵树的节点该如何添加呢?其实,秘密都在传输动作中。要知道,binder驱动在传输数据的时候,可不是仅仅简单地递送数据噢,它会分析被传输的数据,找出其中记录的binder对象,并生成相应的树节点。如果传输的是个binder实体对象,它不仅会在发起端对应的nodes树中添加一个binder_node节点,还会在目标端对应的refs_by_desc树、refs_by_node树中添加一个binder_ref节点,而且让binder_ref节点的node域指向binder_node节点。我们把前一篇文章的示意图加以修改,得到下图:

图中用红色线条来表示传输binder实体时在驱动层会添加的红黑树节点以及节点之间的关系。

可是,驱动层又是怎么知道所传的数据中有多少binder对象,以及这些对象的确切位置呢?答案很简单,是你告诉它的。大家还记得在向binder驱动传递数据之前,都是要把数据打成parcel包的吧。比如:

  1. virtual status_t addService(const String16& name, const sp<IBinder>& service)
  2. {
  3. Parcel data, reply;
  4. data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
  5. data.writeString16(name);
  6. data.writeStrongBinder(service); // 把一个binder实体“打扁”并写入parcel
  7. status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
  8. return err == NO_ERROR ? reply.readExceptionCode() : err;
  9. }

请大家注意上面data.writeStrongBinder()一句,它专门负责把一个binder实体“打扁”并写入parcel。其代码如下:

  1. status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
  2. {
  3. return flatten_binder(ProcessState::self(), val, this);
  4. }
  1. status_t flatten_binder(const sp<ProcessState>& proc, const sp<IBinder>& binder, Parcel* out)
  2. {
  3. flat_binder_object obj;
  4. . . . . . .
  5. if (binder != NULL) {
  6. IBinder *local = binder->localBinder();
  7. if (!local) {
  8. BpBinder *proxy = binder->remoteBinder();
  9. . . . . . .
  10. obj.type = BINDER_TYPE_HANDLE;
  11. obj.handle = handle;
  12. obj.cookie = NULL;
  13. } else {
  14. obj.type = BINDER_TYPE_BINDER;
  15. obj.binder = local->getWeakRefs();
  16. obj.cookie = local;
  17. }
  18. }
  19. . . . . . .
  20. return finish_flatten_binder(binder, obj, out);
  21. }

看到了吗?“打扁”的意思就是把binder对象整理成flat_binder_object变量,如果打扁的是binder实体,那么flat_binder_object用cookie域记录binder实体的指针,即BBinder指针,而如果打扁的是binder代理,那么flat_binder_object用handle域记录的binder代理的句柄值。

然后flatten_binder()调用了一个关键的finish_flatten_binder()函数。这个函数内部会记录下刚刚被扁平化的flat_binder_object在parcel中的位置。说得更详细点儿就是,parcel对象内部会有一个buffer,记录着parcel中所有扁平化的数据,有些扁平数据是普通数据,而另一些扁平数据则记录着binder对象。所以parcel中会构造另一个mObjects数组,专门记录那些binder扁平数据所在的位置,示意图如下:

一旦到了向驱动层传递数据的时候,IPCThreadState::writeTransactionData()会先把Parcel数据整理成一个binder_transaction_data数据,这个在上一篇文章已有阐述,但是当时我们并没有太关心里面的关键句子,现在我们把关键句子再列一下:

  1. status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
  2. int32_t handle, uint32_t code,
  3. const Parcel& data, status_t* statusBuffer)
  4. {
  5. binder_transaction_data tr;
  6. . . . . . .
  7. // 这部分是待传递数据
  8. tr.data_size = data.ipcDataSize();
  9. tr.data.ptr.buffer = data.ipcData();
  10. // 这部分是扁平化的binder对象在数据中的具体位置
  11. tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t);
  12. tr.data.ptr.offsets = data.ipcObjects();
  13. . . . . . .
  14. mOut.write(&tr, sizeof(tr));
  15. . . . . . .
  16. }

其中给tr.data.ptr.offsets赋值的那句,所做的就是记录下“待传数据”中所有binder对象的具体位置,示意图如下:

因此,当binder_transaction_data传递到binder驱动层后,驱动层可以准确地分析出数据中到底有多少binder对象,并分别进行处理,从而产生出合适的红黑树节点。此时,如果产生的红黑树节点是binder_node的话,binder_node的cookie域会被赋值成flat_binder_object所携带的cookie值,也就是用户态的BBinder地址值啦。这个新生成的binder_node节点被插入红黑树后,会一直严阵以待,以后当它成为另外某次传输动作的目标节点时,它的cookie域就派上用场了,此时cookie值会被反映到用户态,于是用户态就拿到了BBinder对象。

我们再具体看一下IPCThreadState::waitForResponse()函数,当它辗转从睡眠态跳出来时,会进一步解析刚收到的命令,此时会调用executeCommand(cmd)一句。

  1. status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
  2. {
  3. int32_t cmd;
  4. int32_t err;
  5. while (1)
  6. {
  7. if ((err = talkWithDriver()) < NO_ERROR) break;
  8. . . . . . .
  9. switch (cmd)
  10. {
  11. . . . . . .
  12. . . . . . .
  13. default:
  14. err = executeCommand(cmd);
  15. . . . . . .
  16. break;
  17. }
  18. }
  19. . . . . . .
  20. return err;
  21. }

executeCommand()的代码截选如下:

  1. status_t IPCThreadState::executeCommand(int32_t cmd)
  2. {
  3. BBinder* obj;
  4. . . . . . .
  5. switch (cmd)
  6. {
  7. . . . . . .
  8. . . . . . .
  9. case BR_TRANSACTION:
  10. {
  11. binder_transaction_data tr;
  12. result = mIn.read(&tr, sizeof(tr));
  13. . . . . . .
  14. . . . . . .
  15. if (tr.target.ptr)
  16. {
  17. sp<BBinder> b((BBinder*)tr.cookie);
  18. const status_t error = b->transact(tr.code, buffer, &reply, tr.flags);
  19. if (error < NO_ERROR) reply.setError(error);
  20. }
  21. . . . . . .
  22. if ((tr.flags & TF_ONE_WAY) == 0)
  23. {
  24. LOG_ONEWAY("Sending reply to %d!", mCallingPid);
  25. sendReply(reply, 0);
  26. }
  27. else
  28. {
  29. LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);
  30. }
  31. . . . . . .
  32. }
  33. break;
  34. . . . . . .
  35. . . . . . .
  36. default:
  37. printf("*** BAD COMMAND %d received from Binder driver\n", cmd);
  38. result = UNKNOWN_ERROR;
  39. break;
  40. }
  41. . . . . . .
  42. return result;
  43. }

请注意上面代码中的sp<BBinder> b((BBinder*)tr.cookie)一句,看到了吧,驱动层的binder_node节点的cookie值终于发挥它的作用了,我们拿到了一个合法的sp<BBinder>。

接下来,程序走到b->transact()一句。transact()函数的代码截选如下:

  1. status_t BBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
  2. {
  3. . . . . . .
  4. switch (code)
  5. {
  6. . . . . . .
  7. default:
  8. err = onTransact(code, data, reply, flags);
  9. break;
  10. }
  11. . . . . . .
  12. }

其中最关键的一句是调用onTransaction()。因为我们的binder实体在本质上都是继承于BBinder的,而且我们一般都会重载onTransact()函数,所以上面这句onTransact()实际上调用的是具体binder实体的onTransact()成员函数。

Ok,说了这么多,我们大概明白了binder驱动层的红黑树节点是怎么产生的,以及binder_node节点的cookie值是怎么派上用场的。限于篇幅,我们先在这里打住。下一篇文章我们再来阐述binder事务的传递和处理方面的细节。

 

转自http://my.oschina.net/youranhongcha/blog/152963

 

红茶一杯话Binder (传输机制篇_中)的更多相关文章

  1. 红茶一杯话Binder (传输机制篇_下)

    红茶一杯话Binder (传输机制篇_下) 侯 亮 1 事务的传递和处理 从IPCThreadState的角度看,它的transact()函数是通过向binder驱动发出BC_TRANSACTION语 ...

  2. 红茶一杯话Binder(传输机制篇_上)

    红茶一杯话Binder (传输机制篇_上) 侯 亮 1 Binder是如何做到精确打击的? 我们先问一个问题,binder机制到底是如何从代理对象找到其对应的binder实体呢?难道它有某种制导装置吗 ...

  3. 红茶一杯话Binder (ServiceManager篇)

    1.先说一个大概 Android平台的一个基本设计理念是构造一个相对平坦的功能集合,这些功能可能会身处于不同的进程中,然而却可以高效地整合到一起,实现不同的用户需求.这就必须打破过去各个孤立App所形 ...

  4. 红茶一杯话Binder (初始篇)

    1 什么是Binder? 简单地说,Binder是Android平 台上的一种跨进程交互技术.该技术最早并不是由Google公司提出的,它的前身是Be Inc公司开发的OpenBinder,而且在Pa ...

  5. 浅议Grpc传输机制和WCF中的回调机制的代码迁移

    浅议Grpc传输机制和WCF中的回调机制的代码迁移 一.引子 如您所知,gRPC是目前比较常见的rpc框架,可以方便的作为服务与服务之间的通信基础设施,为构建微服务体系提供非常强有力的支持. 而基于. ...

  6. 9.13 Binder系统_Java实现_内部机制_Server端

    logcat TestServer:* TestClient:* HelloService:* *:S &CLASSPATH=/mnt/android_fs/TestServer.jar ap ...

  7. Binder通信机制介绍

    1.Binder通信机制介绍 这篇文章会先对比Binder机制与Linux的通信机制的差别,了解为什么Android会另起炉灶,采用Binder.接着,会根据 Binder的机制,去理解什么是Serv ...

  8. Handler消息机制与Binder IPC机制完全解析

    1.Handler消息机制 序列 文章 0 Android消息机制-Handler(framework篇) 1 Android消息机制-Handler(native篇) 2 Android消息机制-H ...

  9. android binder 进程间通信机制1-binder 驱动程序

    以下内容只大概列个提纲,若要明白其中细节,还请看源码: 申明:本人菜鸟,希望得到大神指点一二,余心足已 binder 设备:/dev/binder binder 进程间通信涉及的四个角色: Clien ...

随机推荐

  1. RPD Volume 168 Issue 4 March 2016 评论2

    Influence of the phantom shape (slab, cylinder or Alderson) on the performance of an Hp(3) eye dosem ...

  2. 【树链剖分】【线段树】bzoj3083 遥远的国度

    记最开始的根为root,换根之后,对于当前的根rtnow和询问子树U而言, ①rtnow==U,询问整棵树 ②fa[rtnow]==U,询问除了rtnow所在子树以外的整棵树 ③rtnow在U的子树里 ...

  3. java-继承-类变量与实例变量

    父类中的类的变量(静态属性)与其子类共享一份. 父类中的实例变量与其子类各自维护各自的.

  4. Nagle算法&&延时确认

    数据流分类 成块数据 交互数据   Rlogin需要远程系统(服务器)回显我们(客户)键入的字符 数据字节和数据字节的回显都需要对方确认 rlogin 每次只发送一个字节到服务器,而Telnet 可以 ...

  5. Linux环境下安卓SDK和ADT下载地址下载地址

    SDK: android-sdk_r15-linux.tgz android-sdk_r23.0.1-linux.tgz android-sdk_r24.1.2-linux.tgz android-s ...

  6. convert image to base64

    ylbtech-Unitity-cs:convert image to base64 convert image to base64 1.A,效果图返回顶部   1.B,源代码返回顶部 1.B.1,c ...

  7. cs-JsonHelper

    ylbtech-Unitity: cs-JsonHelper AjaxResult.cs  FormatJsonExtension.cs 1.A,效果图返回顶部   1.B,源代码返回顶部 1.B.1 ...

  8. 【译】你对position的了解有多少?

    此文根据Steven Bradley的<How Well Do You Understand CSS Positioning?>所译,整个译文带有我自己的理解与思想,如果译得不好或不对之处 ...

  9. Java源码阅读LinkedHashMap

    1类签名与注释 public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> 哈 ...

  10. angularJS中的表单验证(包括自定义验证)

    表单验证是angularJS一项重要的功能,能保证我们的web应用不会被恶意或错误的输入破坏.Angular表单验证提供了很多表单验证指令,并且能将html5表单验证功能同他自己的验证指令结合起来使用 ...