`

Android Binder设计(五)

 
阅读更多
每个进程都有一棵红黑树用于存放创建好的节点,以Binder在用户空间的指针作为索引。每当在传输数据中侦测到一个代表Binder实体的 flat_binder_object,先以该结构的binder指针为索引搜索红黑树;如果没找到就创建一个新节点添加到树中。由于对于同一个进程来说 内存地址是唯一的,所以不会重复建设造成混乱。

5.3.2 Binder 引用在驱动中的表述
和实体一样,Binder的引用也是驱动根据传输数据中的flat_binder_object创建的,隶属于获得该引用的进程,用struct binder_ref结构体表示:

Binder引用描述结构:binder_ref
成员 含义
int debug_id; 调试用
struct rb_node rb_node_desc; 每个进程有一棵红黑树,进程所有引用以引用号(即本结构的desc域)为索引添入该树中。本成员用做链接到该树的一个节点。
struct rb_node rb_node_node; 每个进程又有一棵红黑树,进程所有引用以节点实体在驱动中的内存地址(即本结构的node域)为所引添入该树中。本成员用做链接到该树的一个节点。
struct hlist_node node_entry; 该域将本引用做为节点链入所指向的Binder实体结构binder_node中的refs队列
struct binder_proc *proc; 本引用所属的进程
struct binder_node *node; 本引用所指向的节点(Binder实体)
uint32_t desc; 本结构的引用号
int strong; 强引用计数
int weak; 弱引用计数
struct binder_ref_death *death; 应用程序向驱动发送BC_REQUEST_DEATH_NOTIFICATION或BC_CLEAR_DEATH_NOTIFICATION命令从 而当Binder实体销毁时能够收到来自驱动的提醒。该域不为空表明用户订阅了对应实体销毁的‘噩耗’。


就象一个对象有很多指针一样,同一个Binder实体可能有很多引用,不同的是这些引用可能分布在不同的进程中。和实体一样,每个进程使用红黑树存 放所有该进程正在使用的引用。但Binder的引用可以通过两个键值索引:

对应实体在内核中的地址。注意这里指的是驱动创建于内核中的binder_node结构的地址,而不是Binder实体在用户进程中的地址。实体在内核中 的地址是唯一的,用做索引不会产生二义性;但实体可能来自不同用户进程,而实体在不同用户进程中的地址可能重合,不能用来做索引。驱动利用该红黑树在一个 进程中快速查找某个Binder实体所对应的引用(一个实体在一个进程中只建立一个引用)。

引用号。引用号是驱动为引用分配的一个32位标识,在一个进程内是唯一的,而在不同进程中可能会有同样的值,这和进程的打开文件号很类似。引用号将返回给 应用程序,可以看作Binder引用在用户进程中的句柄。除了0号引用在所有进程里都保留给SMgr,其它值由驱动在创建引用时动态分配。向Binder 发送数据包时,应用程序通过将引用号填入binder_transaction_data结构的target.handle域中表明该数据包的目的 Binder。驱动根据该引用号在红黑树中找到引用的binder_ref结构,进而通过其node域知道目标Binder实体所在的进程及其它相关信 息,实现数据包的路由。

6 Binder 内存映射和接收缓存区管理
  暂且撇开Binder,考虑一下传统的IPC方式中,数据是怎样从发送端到达接收端的呢?通常的做法是,发送方将准备好的数据存放在缓存区中,调用 API通过系统调用进入内核中。内核服务程序在内核空间分配内存,将数据从发送方缓存区复制到内核缓存区中。接收方读数据时也要提供一块缓存区,内核将数 据从内核缓存区拷贝到接收方提供的缓存区中并唤醒接收线程,完成一次数据发送。这种存储-转发机制有两个缺陷:首先是效率低下,需要做两次拷贝:用户空间 ->内核空间->用户空间。Linux使用copy_from_user()和copy_to_user()实现这两个跨空间拷贝,在此过程 中如果使用了高端内存(high memory),这种拷贝需要临时建立/取消页面映射,造成性能损失。其次是接收数据的缓存要由接收方提供,可接收方不知道到底要多大的缓存才够用,只能 开辟尽量大的空间或先调用API接收消息头获得消息体大小,再开辟适当的空间接收消息体。两种做法都有不足,不是浪费空间就是浪费时间。
  Binder采用一种全新策略:由Binder驱动负责管理数据接收缓存。我们注意到Binder驱动实现了mmap()系统调用,这对字符设备是 比较特殊的,因为mmap()通常用在有物理存储介质的文件系统上,而象Binder这样没有物理介质,纯粹用来通信的字符设备没必要支持mmap()。 Binder驱动当然不是为了在物理介质和用户空间做映射,而是用来创建数据接收的缓存空间。先看mmap()是如何使用的:
  fd = open(“/dev/binder”, O_RDWR);
  mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
  这样Binder的接收方就有了一片大小为MAP_SIZE的接收缓存区。mmap()的返回值是内存映射在用户空间的地址,不过这段空间是由驱动 管理,用户不必也不能直接访问(映射类型为PROT_READ,只读映射)。
  接收缓存区映射好后就可以做为缓存池接收和存放数据了。前面说过,接收数据包的结构为binder_transaction_data,但这只是消 息头,真正的有效负荷位于data。buffer所指向的内存中。这片内存不需要接收方提供,恰恰是来自mmap()映射的这片缓存池。在数据从发送方向 接收方拷贝时,驱动会根据发送数据包的大小,使用最佳匹配算法从缓存池中找到一块大小合适的空间,将数据从发送缓存区复制过来。要注意的是,存放 binder_transaction_data结构本身以及表4中所有消息的内存空间还是得由接收者提供,但这些数据大小固定,数量也不多,不会给接收 方造成不便。映射的缓存池要足够大,因为接收方的线程池可能会同时处理多条并发的交互,每条交互都需要从缓存池中获取目的存储区,一旦缓存池耗竭将产生导 致无法预期的后果。
  有分配必然有释放。接收方在处理完数据包后,就要通知驱动释放data。buffer所指向的内存区。在介绍Binder协议时已经提到,这是由命 令BC_FREE_BUFFER完成的。
  通过上面介绍可以看到,驱动为接收方分担了最为繁琐的任务:分配/释放大小不等,难以预测的有效负荷缓存区,而接收方只需要提供缓存来存放大小固 定,可以预测的消息头即可。在效率上,由于mmap()分配的内存是映射在接收方用户空间里的,所有总体效果就相当于对有效负荷数据做了一次从发送方用户 空间到接收方用户空间的直接数据拷贝,省去了内核中暂存这个步骤,提升了一倍的性能。顺便再提一点,Linux内核实际上没有从一个用户空间到另一个用户 空间直接拷贝的函数,需要先用copy_from_user()拷贝到内核空间,再用copy_to_user()拷贝到另一个用户空间。为了实现用户空 间到用户空间的拷贝,mmap()分配的内存除了映射进了接收方进程里,还映射进了内核空间。所以调用copy_from_user()将数据拷贝进内核 空间也相当于拷贝进了接收方的用户空间,这就是Binder只需一次拷贝的‘秘密’。

  7 Binder 接收线程管理
  Binder通信实际上是位于不同进程中的线程之间的通信。假如进程S是Server端,提供Binder实体,线程T1从Client进程C1中 通过Binder的引用向进程S发送请求。S为了处理这个请求需要启动线程T2,而此时线程T1处于接收返回数据的等待状态。T2处理完请求就会将处理结 果返回给T1,T1被唤醒得到处理结果。在这过程中,T2仿佛T1在进程S中的代理,代表T1执行远程任务,而给T1的感觉就是象穿越到S中执行一段代码 又回到了C1。为了使这种穿越更加真实,驱动会将T1的一些属性赋给T2,特别是T1的优先级nice,这样T2会使用和T1类似的时间完成任务。很多资 料会用‘线程迁移’来形容这种现象,容易让人产生误解。一来线程根本不可能在进程之间跳来跳去,二来T2除了和T1优先级一样,其它没有相同之处,包括身 份,打开文件,栈大小,信号处理,私有数据等。
  对于Server进程S,可能会有许多Client同时发起请求,为了提高效率往往开辟线程池并发处理收到的请求。怎样使用线程池实现并发处理呢? 这和具体的IPC机制有关。拿socket举例,Server端的socket设置为侦听模式,有一个专门的线程使用该socket侦听来自Client 的连接请求,即阻塞在accept()上。这个socket就象一只会生蛋的鸡,一旦收到来自Client的请求就会生一个蛋 – 创建新socket并从accept()返回。侦听线程从线程池中启动一个工作线程并将刚下的蛋交给该线程。后续业务处理就由该线程完成并通过这个单与 Client实现交互。
  可是对于Binder来说,既没有侦听模式也不会下蛋,怎样管理线程池呢?一种简单的做法是,不管三七二十一,先创建一堆线程,每个线程都用 BINDER_WRITE_READ命令读Binder。这些线程会阻塞在驱动为该Binder的等待队列上,一旦有来自Client的数据驱动会从队列 中唤醒一个线程来处理。这样做简单直观,省去了线程池,但一开始就创建一堆线程有点浪费资源。于是Binder协议设置了专门命令或消息帮助用户管理线程 池,包括:
  · INDER_SET_MAX_THREADS
  · BC_REGISTER_LOOP
  · BC_ENTER_LOOP
  · BC_EXIT_LOOP
  · BR_SPAWN_LOOPER
  首先要管理线程池就要知道池子有多大,应用程序通过INDER_SET_MAX_THREADS告诉驱动最多可以创建几个线程。以后每个线程在创 建,进入主循环,退出主循环时都要分别使用BC_REGISTER_LOOP,BC_ENTER_LOOP,BC_EXIT_LOOP告知驱动,以便驱动 收集和记录当前线程池的状态。每当驱动接收完数据包返回读Binder的线程时,都要检查一下是不是已经没有闲置线程了。如果是,而且线程总数不会超出线 程池最大线程数,就会在当前读出的数据包后面再追加一条BR_SPAWN_LOOPER消息,告诉用户线程即将不够用了,请再启动一些,否则下一个请求可 能不能及时响应。新线程一启动又会通过BC_xxx_LOOP告知驱动更新状态。这样只要线程没有耗尽,总是有空闲线程在等待队列中随时待命,及时处理请 求。
  关于工作线程的启动,Binder驱动还做了一点小小的优化。当进程P1的线程T1向进程P2发送请求时,驱动会先查看一下线程T1是否也正在处理 来自P2某个线程请求但尚未完成(没有发送回复)。这种情况通常发生在两个进程都有Binder实体并互相对发时请求时。假如驱动在进程P2中发现了这样 的线程,比如说T2,就会要求T2来处理T1的这次请求。因为T2既然向T1发送了请求尚未得到返回包,说明T2肯定(或将会)阻塞在读取返回包的状态。 这时候可以让T2顺便做点事情,总比等在那里闲着好。而且如果T2不是线程池中的线程还可以为线程池分担部分工作,减少线程池使用率。
分享到:
评论

相关推荐

    Android Binder设计与实现——设计篇

    罗升阳在介绍和学习Binder的Blog中推荐:《Android Binder设计与实现》一文,详细地介绍了内核空间的Binder驱动程序的数据结构和设计原理

    Android Binder设计与实现

    详细分析了android binder的设计与实现。

    Android_Binder设计与实现_-_设计篇

    Android_Binder设计与实现_-_设计篇

    Android Binder设计与实现.docx

    Android Binder设计与实现.docxAndroid Binder设计与实现.docxAndroid Binder设计与实现.docxAndroid Binder设计与实现.docxAndroid Binder设计与实现.docxAndroid Binder设计与实现.docxAndroid Binder设计与实现....

    Android Binder设计之道.pptx

    Android Binder设计之道.pptx

    AndroidBinder设计与实现-设计篇

    Binder是Android系统进程间通信(IPC)方式之一。Linux已经拥有管道,systemVIPC,socket等IPC手段,却还要倚赖Binder来实现进程间通信,说明Binder具有无可比拟的优势。深入了解Binder并将之与传统IPC做对比有助于...

    android中binder设计与实现

    android中binder设计与实现 与深入浅出结合,非常好的诠释了android 中的binder机制的实现,各有所长,希望对您有帮助 个人也是网络上找的

    Android Binder 实现原理

    很详细讲解了Binder实现原理,细节,设计思想

    Binder设计与实现

    Binder是Android系统进程间通信(IPC)方式之一。Linux已经拥有管道,system V IPC,socket等IPC手段,却还要倚赖Binder来实现进程间通信,说明Binder具有无可比拟的优势。深入了解Binder并将之与传统 IPC做对比有助...

    Android Binder通信案例

    AIDL实现IPC,手写Binder实现IPC,Binder连接池的设计

    Android10.0 Binder通信原理(九)-AIDL Binder示例

    摘要:本节主要来讲解Android10.0 Binder中如何使用AIDL 阅读本文大约需要花费20分钟。 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! [Android取经之路] 的...

    Android Binder

    本文将对Binder的设计细节做一个全面的阐述,首先通过介绍Binder通信 模型和 Binder通信协议了解Binder的设计需求;然后分别阐述Binder在系统不同部分的表述方式和起的作用;最后还 会解释Binder在数据接收端的设计...

    Android代码-Android

    对于Binder机制的介绍可以看universus写的《Android Binder设计与实现》。另外还两个老外介绍Binder的PPT也不错。 Android Binder设计与实现 - 设计篇 inter-process method invocation in Android (相对入门一点...

    android native层 binder通信机制演示源码

    该压缩包的内容主要是基于android系统演示native层进行binder通信的源码,里面分为bp和bn直接如何设计,可以给那些需要再native层进行binder通信的开发提供一个参考

    Android的设计与实现(卷1)

    android的设计与实现:卷i》是android应用开发工程师和android系统工程师进阶修炼的必读之作。它由资深android内核专家亲自执笔,从源代码角度,系统、深入、透彻剖析android系统框架层(framework)的设计思想和...

    基于Android的家庭理财系统的设计与实现.rar(毕业论文设计+程序源码) android studio导入可直接打开

    2.4.4 Binder 机制 15 2.5本章小结 16 第3章 需求分析与数据库设计 16 3.1需求分析 16 3.1.1功能性需求分析 16 3.12非功能性需求分析 17 3.2系统数据库设计 17 3.2.1数据库概念结构设计 17 3.2.2数据库逻辑结构设计 ...

    Android进程间通信Binder扩展模型的设计与实现.pdf

    Android进程间通信Binder扩展模型的设计与实现.pdf

    Android Binder入门学习笔记

    理解Binder能帮助我们更好的理解Android的系统设计,比如说四大组件,AMS,WMS等系统服务的底层通信机制就都是基于Binder机制的。当然了,Binder机制的底层驱动实现很复杂,本文的目的只是为了理清Binder的使用和在...

Global site tag (gtag.js) - Google Analytics