Android Binder 设计思想

Android Binder 设计思想

来自此处《Android Bander设计与实现 - 设计篇》的笔记

1引言

1.1 传统IPC有何弊端?为什么要新建Binder?

1.1.1传输性能

  • socket做为通用接口,传输效率低,开销大,主要用于在跨网络的进程间通讯和本机低速网络通讯,两次拷贝过程
  • 消息队列和管道采用存储-转发方式,发送方到内核缓存区,内核缓存区到接收方缓存区,至少两次拷贝过程
  • 共享内存无需拷贝,但控制复杂,难以使用

1.1.2安全性考虑

  • 传统IPC没法获取发送方可靠的UID/PID, binder能够在内核中添加可靠的身份表示
  • 传统IPC访问接入点是开发的,没法建议私有通道,例如socket的ip地址都是开放的,没法阻止恶意程序猜想接收方地址,从而获取链接

1.2 Binder是什么?

  • 基于Client-Sever通讯模式的IPC机制
  • 传输过程只需1次拷贝
  • 为发送方添加UID/PID身份,同时支持实名Binder和匿名Binder,安全性高

2 面向对象的 Binder IPC

2.1 Client-Server通讯模型

必要条件

  • server必需要有肯定的访问接入点(或者说地址)来接受Client的请求,而且Client能够经过某种途径获知Server地址
  • 指定Command-Reply协议来传输数据

网络通讯

  • Server接入点就是Server主机IP+端口号
  • 传输协议为TCP协议

binder

  • 对于Server而言,binder是Server提供的某个特定服务的访问接入点,Clitent经过这个“地址”向Server发送请求来使用该服务
  • 对于Client而言,binder是通向Server的管道入口,想要和某个Server通讯,必须创建这个管道并获取管道入口

2.2 Binder 面向对象思想

  • Binder实体是位于Server进程中的对象,该对象提供了一套方法用以实现对服务的请求,就像类的成员函数。
  • 遍及于client进程中的入口,能够当作是Binder实体的“指针”,“引用”,“代理”或者说是“句柄。Client经过binder引用访问Server。
  • 面向对象的思想将进程间通讯转化为“使用binder引用来访问binder实体对象的方法”。binder实体是一个能够跨进程引用的对象。binder实体位于某个进程中,而binder引用遍及于系统的各个进程中。
  • 形形色色的binder对象以及星罗棋布的应用仿佛粘连各个应用程序的胶水,这也是Binder的英文原意。

2.3 Binder驱动

Binder驱动和内核其余模块同样,使用C语言实现,为面向对象的进程间通讯提供底层支持。
复制代码

3 Binder通讯模型

3.0 Binder框架四个角色--类比互联网:java

  • 运行于用户空间:Server(服务器)、Client(客户端)、ServerManager(DNS)
  • 运行于内核空间:Binder驱动(路由器)

3.1 Binder驱动

  • 通讯核心,工做于内核态,提供open(),mmap(),ioctl()等标准文件操做
  • 非硬件设备,只是实现方式与设备驱动程序同样,以字符驱动设备中的misc设备注册在dev下,经过/dev/binder访问
  • 负责进程间Binder通讯的创建,binder在进程中的传递,binder引用计数管理,数据包在进程间的传递与交互等一系列底层支持。
  • 驱动与应用程序之间定义了一套接口协议,主要功能由ioctl()接口实现
  • 驱动代码位于linux目录的drivers/misc/binder.c中

3.2 ServiceManager与实名Binder(注册)

  • SM的做用是将字符形式的Binder名称转化成Client中对该Binder的引用。
  • 注册了名字的Binder叫实名Binder,就像网站有ip,也有地址。
  • Server建立Binder实体,并取一个字符名称。将Binder连同名称以数据包的形式经过Binder驱动发送给SM
  • Binder驱动为此Binder建立位于内核中的实体节点和SM对实体的引用。SM收到数据包后,从中取出名字和引用填入一张查找表中,即注册过程。
  • SM是Server端,有本身的Binder实体。特殊的是没有名字,无需注册。一个进程使用BINDER_SET_CONTEXT_MGR命令时,那此进程就会将自身注册成SM,同时驱动会自动为其建立Binder实体。此Binder的引用在全部Client中都固定为0。相似网络通讯,0号引用比如域名服务器地址,必须预先配置好。

3.3 Client获取实名Binder引用(查找)

  • 经过前一阶段的注册,Client能够直接经过名字获取Binder引用。但咱们一般经过0号引用即SM,来获取某个binder引用,此获取过程一样须要binder名称。
  • SM中始终留有一个Binder引用,每一个Client经过查找都会获得一个新的binder引用。如同java对象引用,同时也都是强引用。

3.4匿名Binder

并非全部进程中的binder都须要注册给SM广而告之。例如某个client进程能够经过已经创建的Binder链接,将本身的binder实体引用发送给Server进程。Server进程使用该引用请求Client进程,此过程进程双方的角色就发生了互换。例如应用启动过程当中的ApplicationThread
复制代码

4 Binder协议

4.0概述

基本格式:命令+数据。使用ioctl(fd,cmd,arg)交互。node

4.1 BINDER_WRITE_READ之写操做

数据格式一样为:命令+数据。均存放在write_buffer域指向的内存空间,多条命令可连续存放。数据紧跟着命令后面,数据格式会有所不一样.
复制代码
Binder写操做命令字 arg
BC_TRANSACTION 最经常使用命令之一,Client经过驱动向Server发送请求数据, struct binder_transaction_data 利用binder_transaction_data中的flag域区分同步异步。flag域中的TF_ONE_WAY为1,则为异步,Client无需等待BC_REPLY数据包。不然须要等到数据包接收,才算完成一次交互。
BC_REPLY 最经常使用命令之二Server经过驱动向Client发送应答数据
BC_ACQUIRE_RESULT
BC_ATTEMPT_ACQUIRE
BC_FREE_BUFFER 释放一块映射的内存
BC_INCREFS BC_ACQUIRE BC_RELEASE BC_DECREFS 管理Binder引用计数 32位Binder引用号
BC_INCREFS_DONE BC_ACQUIRE_DONE Binder实体处理引用计数后,给Binder驱动的反馈
BC_REGISTER_LOOPER 通知binder驱动,Server端建立了一个线程 Server端线程池管理
BC_ENTER_LOOPER 通知binder驱动,Server端某个线程进入主循环,能够接收数据
BC_EXIT_LOOPER 通知binder驱动,Server端某个线程退出主循环,再也不接收数据
BC_REQUEST_DEATH_NOTIFICATION client要求binder驱动在Binder实体销毁时,client能获得通知 参数1:uint32 *ptr; 须要获得死亡通知的Binder引用 参数2:void * *cookie: 与死亡通知相关的信息,驱动会在发出死亡通知时返回给发出请求的进程。
BC_DEAD_BINDER_DONE client收到实体死亡通知,删除引用,并告知驱动

4.2 BINDER_WRITE_READ 从Binder中读出数据

格式与写操做一致,一样能连续存放。linux

Binder读操做命令字
BR_ERROR 内部错误?
BR_OK
BR_NOOP
操做成功,与BR_TRANSACTION_COMPLETE区别在哪?
BR_SPAWN_LOOPER 驱动发现接收方线程不够用,要求接收方建立线程
BR_TRANSACTION
BR_REPLY
BR_ACQUIRE_RESULT
BR_ATTEMPT_ACQUIRE
BRFINISHED
BR_DEAD_REPLAY 交互过程当中若是发现对方进程或线程已经死亡则返回该消息
BR_TRANSACTION_COMPLETE 驱动告知发送方“发送成功”,与接收方是否返回请求数据无关
BR_INCREFS
BR_ACQUIRE
BR_RELEASE
BR_DECREFS
引用与计数管理
BR_DEAD_BINDER
BR_CLEAR_DEATH_NOTIFICATION_DONE
告知Client进程,Binder实体死亡通知处理相关
BR_FAILED_REPLY 发送非法引用号,则返回此

4.3 struct binder_transaction_data 收发数据包结构

成员定义
union {
   size_t handle;
   void *ptr;
} target;
对于发送方 target指向目的地。target.handle(即句柄)存放binder引用
当数据包到达接收方时,target已被驱动赋予了binder实体对象内存的指针,存放在target.ptr
void *cookie; 发送方忽略该成员,binder实体建立时,接收方自定义的任意数值,与binder指针相关的额外信息,驱动也基本不关心该成员
unsigned int code; Server端定义的公共接口函数编号(业务码?)
unsigned int flags; 交互相关标志位,其中最重要的是TF_ONE_WAY位
pid_t sender_pid; 发送方进程ID和用户ID,由驱动负责填入
uid_t sender_euid;
size_t data_size; Server定义的公共接口函数关心的数据相关,传输中的Binder,以flat_binder_object的形式包含在buffer中.
buffer指向部分会copy到映射区
size_t offsets_size;
union{
  struct { *buffer;*offsets} ptr; 
  uint8_t buf[8];
}data;

5 Binder的表述

Binder存在于系统的这些部分:设计模式

  • 应用程序进程:Server进程和Client进程
  • Binder驱动:分别管理为Server端Binder实体和Client端引用
  • 传输数据:Binder能够跨进程传递,须要在传输数据中予以表述

在不一样的部分,Binder实现的功能不一样,表现形式也不同。咱们须要关心其扮演的角色和使用的数据结构。 缓存

5.1 Binder在应用程序中的表述

Binder本质是底层通讯的方式,与具体服务无关。Server必须提供一套接口函数,以便Client远程访问。
这时一般采用Proxy设计模式,将接口函数定义在一个抽象类中安全

  • Server端,实现全部接口函数,是正在的功能实现
  • Client端一样实现全部接口函数,但倒是对这些函数远程调用请求的包装

如何将Binder和Proxy设计模式结合是应用程序实现面向对象Binder通讯的根本问题 服务器

5.1.1 在server端表述--Binder实体

Binder实体类实现两个类的全部虚函数:markdown

  • 公共接口函数类,具体服务实现
  • BBinder:IBinder抽象类,提供onTransact(),用于跨进程调用。输入就是binder_tansaction_data结构上的数据包,case-by-case解析data中的code值和函数参数,分发给具体接口函数的实现。

5.1.2在client端表述--Binder引用

Client中会有代理类实现这两个类:cookie

  • 公共接口函数类,函数调用封装为binder_transaction_data数据包
  • BpBinder:IBinder抽象类,提供transact(),用于跨进程调用. 

5.2 Binder 在传输数据中的表述

Binder传输结构为flat_binder_object 网络

5.2.1 flat_binder_object结构

属性 含义
unsigned long type; binder类型:
BINDER_TYPE_BINDER
表示传递的是Binder实体,而且指向该实体的都是强引用;
BINDER_TYPE_WEAK_BINDER
表示传递的是Binder实体,而且指向该实体的都是弱引用;
BINDER_TYPE_HANDLE
表示传递的是Binder强引用;
BINDER_TYPE_WEAK_HANDLE
表示传递的是Binder弱引用;
BINDER_TYPE_FD
表示传递的是文件形式Binder;
unsigned long flags 只对第一次传递Binder实体时有效,由于是用于驱动在内核中建立Binder实体节点,
内容暂不关心了就。
union{
  void * binder;
 signed long handle;
};
void * binder;
指向Binder实体在Server进程中的内存地址,传递Binder实体时使用。
signed long handle;
存放Binder在进程中的引用号,传递Binder引用时使用。
void * cookie; 只对Binder实体有效,存放Binder相关附加信息

5.2.2 驱动对flat_binder_object的操做

不管是Binder实体仍是引用都从属某个进程,因此不能透明的在进程间传递,必须通过驱动“翻译”。

binder类型 在发送方的操做 在接收方的操做
BINDER_TYPE_BINDER BINDER_TYPE_WEAK_BINDER 只有实体所在的进程才能发送该类型的Binder。第一次发送时,驱动为其在内核中建立节点,并保存binder,cookie,flag域 若是是第一次接收该Binder则建立实体在内核中的引用;将handle域替换为新建的引用号;将type域替换为INDER_TYPE_(WEAK_)HANDLE
BINDER_TYPE_HANDLE BINDER_TYPE_WEAK_HANDLE 驱动根据handle域提供的引用号查找binder在内核中的引用。若是找不到,则不合法。 1.若是收到的binder实体位于接收进程:将ptr域替换为保存在节点中的binder值,cookie值替换,type替换为BINDER_TYPE_(WEAK_)BINDER   
2.若是收到的Binder实体不在接收进程中:若是第一次接收则建立实体在内核中的引用;将handle域替换为新建的引用号
BINDER_TYPE_FD

5.3 Binder 在驱动中的表述

驱动是Binder通讯的核心:

  • 全部Binder实体及其在各个进程中的引用都登记在驱动中
  • 驱动记录Binder引用与实体间多对一的关系
  • 为引用找到对应实体,为实体建立或者查找对应引用
  • 记录Binder的归属进程
  • 管理Binder引用,建立or销毁Binder实体

5.3.1 Binder实体在驱动中的表述

Binder实体在驱动中,称为“节点”,由binder_node 结构表示。根据传输数据中的flat_binder_object,构建binder_node节点。

binder_node结构
属性 含义
int debug_id; 用于调试
struct binder_work work;
union{
struct rb_node rb_node;
struct hlist_node dead_node;
};
内核为每一个进程维护一颗红黑树,存放此进程的全部binder实体的用户空间指针(ptr).rb_node即为binder_node在红黑树中的形式。
待销毁节点,待切断全部引用后,完全销毁。
struct binder_proc *proc; 该节点所属进程
struct hlist_head refs; 该节点全部引用队列
int internal_strong_refs; 强指针计数器
int local_strong_refs; 驱动为传输中的Binder设置的强引用计数
int local_weak_refs; 驱动为传输中的Binder设置的弱引用计数
void __user * ptr; 指向用户空间内存地址,来自flat_binder_object的binder成员
void __user *cookie;
unsigned pending_strong_ref;
unsigned has_strong_ref;
unsigned has_weak_ref;
unsigned pending_weak_ref;
引用计数相关
unsigned has_async_transaction; 表面该节点在to-do队列中有异步交互还没有完成。to-do队列是驱动为接收进程或线程开辟的,用于暂存发往接收端的数据包。
若是to-do中有还没有完成的异步交互,则将新到的异步交互,存放在async队列中。为同步交互让路,避免长时间阻塞发送端
struct list_head aysnc_todo; 异步交互等待队列;分流发往本节点的异步交互包
unsigned accept_fds; 文件binder相关,略
int min_priority; 设置处理请求的线程的最低优先级,值来自于flat_binder_object中的flags成员
区别在哪?

5.3.2 Binder引用在驱动中的表述

表述为binder_ref,根据传输数据中的flat_binder_object构建。

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实体销毁时可以收到来自驱动的提醒。该域不为空代表用户订阅了对应实体销毁的‘噩耗’。

5.3.3 小结:

Binder实体会有不少引用,这些引用分布在不一样的进程中。

  • 每一个进程使用红黑树存放实体
  • 每一个进程使用红黑树存放引用

Binder引用能够经过两个键值索引:

  • 实体在内核中的地址:

驱动建立于内核中的binder_node结构地址,非实体在用户进程中的内存地址。内核地址是惟一的,而用户进程地址可能会重合

  • 引用号:

驱动为引用分配的32位标识,Binder引用在用户进程中的句柄,单个进程内是惟一的。经过引用号在红黑树中找到binder_ref,经过ref的node域找到Binder实体相关信息。

6. Binder内存映射和接收缓存区管理

  • 传统IPC数据拷贝:用户空间-->内核空间-->用户空间
  • Binder数据拷贝:Client用户空间--> 内核空间:Server用户空间
  • Binder内存映射mmap()方法:
    • 接收方开辟缓存区,mmap(),返回内存映射在用户空间地址
    • 映射类型为只读,用户不能直接访问缓存区,缓存区由驱动管理
    • 惟有binder_transaction_data.data.buffer会进入缓存区。其他部分(理解为消息头)依然须要接收方提供。
  • Binder驱动职责:
    • 驱动为接收方分担了最为繁琐的任务:分配/释放大小不等,难以预测的有效负荷缓存区。接收方只需存放大小固定,空间可预测的消息头便可。经过映射,省略了在内核中暂存。

7 Binder接收线程管理(略)

Binder会预先建立一堆线程,这些线程会阻塞在等待队列上。一旦有数据请求,驱动会从队列中唤醒一个线程来处理。
Binder如何管理线程:

  • 应用程序经过BINDER_SET_MAX_THREADS,告诉驱动最大线程数
  • 之后每一个线程建立,进入/退出循环都会告知驱动,以便驱动记录当前线程池状态(BC_REGISTER_LOOP,BC_ENTER_LOOP,BC_EXIT_LOOP)
  • 其余线程利用率优化

8 数据包接收队列与等待队列管理

1.队列用以缓解请求与处理的“供需矛盾”:

  • 接收队列,也叫to-do队列,存放数据包,分为:进程全局队列& 线程私有接收队列
  • 等待队列,等待to-do处理后的数据,分为:进程全局等待队列 & 线程私有等待队列

2.数据包进入进程or线程to-do队列规则:

  • 规则1:Client发送给Server的请求数据包进入全局to-do队列。(除非发送线程T1与Server的线程T2有交互,且T1正等待读取返回包)
  • 规则2:同步请求的返回数据包,送入发送请求的线程的私有to-do队列

3.驱动递交同步交互和异步交互规则:

为了减小异步交互对同步发送端的阻塞。异步要为同步让步。即一旦to-do队列中有异步在处理,新提交的异步只能特制的async_todo队列。而新提交的同步,依然能够进入to-do队列

9 总结

性能:单次拷贝<br />	安全性:PID/UID可控<br />	易用性:面向对象设计
复制代码
相关文章
相关标签/搜索