Android Container
Binder是Android系统新增的进程间通信机制,尽管Linux内核中已经拥有众多的进程间通信机制,Android却还要引入一整套新的机制,说明Binder具有无可比拟的优势。
基于以上,Android需要建立一套新的适合移动设备应用特点的IPC机制。Binder充分考虑了传输性能,安全性,基于C-S模式,传输过程只需一次内存数据拷贝。
Binder通信过程中共有四个角色(进程)参与:Client,Sever, ServiceMangaer,Driver。其中 Server,Client,SMgr运行于用户空间,驱动运行于内核空间。这四个角色的关系和互联网类似:Server是服务器,Client是客户终端,SMgr是域名服务器(DNS),驱动是路由器。
四个角色的关系如下图所示:
Client、Server和Service Manager实现在用户空间中,Binder驱动程序实现在内核空间中
Binder驱动程序和Service Manager在Android平台中已经实现,开发者只需要在用户空间实现自己的Client和Server
Binder驱动程序提供设备文件/dev/binder与用户空间交互,Client、Server和Service Manager通过open和ioctl文件操作函数与Binder驱动程序进行通信
Client和Server之间的进程间通信通过Binder驱动程序间接实现,即每次通信的数据包需要经由Driver处理后转发
Service Manager是一个守护进程,用来管理Server,并向Client提供查询Server接口的能力
Binder Driver
它工作于内核态,提供open(), mmap(), poll(), ioctl()等标准文件操作的;以字符设备中的misc设备类型注册,用户通过/dev/binder可以与其交互; 其负责Binder通信的建立/Binder在进程间的传递/Binder引用计数管理/数据包的传输等。
ServiceMgr
ServiceMgr的作用是将字符形式的Binder名字转化为一个该Binder的引用。Server创建了一个Binder实体,为其起一个名字,将这个名字封装成数据包,传输给Driver,Driver发现这个是新的Binder是来注册的,于是在内核中创建该Binder的实体节点(Binder_node)和一个对该实体的引用(Binder_ref),然后Driver将这个引用传递给ServiceMgr,ServiceMgr接收到数据包后,从中取出该Binder的名字和引用,填入一张查找表中。
Client
Client一开始只知道自己要使用的Binder的名字,和ServiceMgr中Binder实体的0号引用。首先通过0号引用去访问SMgr,获得想要使用的Binder的引用(只知道名字没用,需要获得引用)。SMgr查到引用后,回复给Client。获得引用后,Client就可以在本地像使用本地对象的成员函数一样调用Bidner实体的函数。
匿名Binder
并不是所有Binder都需要到SMgr中注册的。许多情况下Server会将某Binder实体放在数据包中传输给Client,Server会在数据包中表明Binder实体的位置,驱动会为该匿名Binder建立node和ref,并将ref传递给Client。
进程通过ioctl(fd,cmd,arg)函数实现交互。fd指向/dev/binder,cmd为命令,arg为数据。下表列举了所有的命令和对应的数据:
命令 | args | 含义 |
BINDER_WRITE_READ | struct binder_write_read{} | 向Binder中写入和读取数据 |
BINDER_SET_MAX_THREADS | int max_threads | Server告之驱动其线程池中的线程数的最大值 |
BINDER_SET_CONTEXT_MGR | 无 | 当前进程申请注册为SMgr,驱动为其创建无名node和0号ref |
BINDER_THREAD_EXIT | 无 | 告之驱动当前线程退出了,线程在退出时请求驱动释放为其建立的内核数据结构 |
BINDER_VERSION | 无 | 获得Binder驱动的版本号 |
写Binder
Binder写操作的数据格式同样也是(命令+数据)。这时候命令和数据都存放在binder_write_read 结构write_buffer域指向的内存空间里,多条命令可以连续存放。数据紧接着存放在命令后面,格式根据命令不同而不同。下表列举了Binder写 操作支持的命令:
命令 | arg | 含义 |
BC_TRANSACTION | struct binder_transaction_data | 用于写入请求数据,数据存放在transaction_data中 |
BC_REPLY | struct binder_transaction_data | 用于写入回复数据,数据存放在transaction_data中 |
读Binder
读Binder的数据格式采用消息+数据形式,跟写Binder基本一样。多条消息可以连续存放。下表列举了Binder读操作支持的消息:
命令 | arg | 含义 |
BR_TRANSACTION | struct binder_transaction_data | 表明当前数据包是发送方的请求数据 |
BR_REPLY | struct binder_transaction_data | 表明当前数据包是发送方的回复数据 |
struct binder_transaction_data
binder_transaction_data是Binder收发的数据包,了解它具有重要意义。
struct binder_transaction_data {
union {
size_t handle;
void *ptr;
} target;
void *cookie;
unsigned int code;
unsigned int flags;
pid_t sender_pid;
uid_t sender_euid;
size_t data_size;
size_t offsets_size;
union {
struct {
const void __user *buffer;
const void __user *offsets;
} ptr;
uint8_t buf[8];
} data;
};
接收方在收到binder_transaction_data后,首先解析消息头,然后真正的数据存放在data.buffer中
在Android系统中,不同角色的进程中的Binder的功能是不同的,表现形式是不同的,对Binder的理解也是不同的。接下来深入探讨Binder在四个角色中的表现形式和对应的数据结构。
Binder在Server端的表述
首先系统已经存在两个基类:(1)抽象接口类封装Server所有的功能,是一系列虚函数。由于这些函数需要跨进程调用,须为其一一编号,从而Server可以根据收到的编号决定调用哪个函数;(2)Binder抽象类处理来自Client的数据包,其中最重要的成员函数是onTransact()。该函数解析收到的数据包,调用相应的接口函数来处理。
接下来,Server会继承这两个基类,构建自己的Binder类,生成Binder对象实体。Server会实现基类里所有的虚函数。
Binder在Client端的表述
Clinet端的Binder也要继承抽象接口类。采用的是Proxy设计模式,即Clinet利用SMgr发来的远端Binder的引用,在本地重新封装一次远程函数调用。(对于代理模式,看了大话设计模式之代理模式之后就很容易理解了)
由于继承了同样的server接口类,Client Binder类需要和Server Binder实现同样的虚函数,使用户感受不出来Client调用的成员方法是在本地还是在远端。
Clinet Binder类中,公共接口函数的底层实现方式是:创建一个binder_transaction_data数据包,将其对应的编码填入code域,将参数填入data.buffer指向的缓冲区,并设定数据包的目的地(将获得的Binder实体的引用handle填入数据包的target.handle中)。
Binder在传输数据中的表述
前面说了,Binder是塞在数据包中传给接收方的,这些传输中的Binder是用struct flat_binder_object表示的:
struct flat_binder_object {
unsigned long type;
unsigned long flags;
union {
void __user *binder;
signed long handle;
};
void __user *cookie;
};
Binder在驱动中的表述
驱动是Binder通信的核心,系统中所有的Binder实体以及每个实体在各个进程中的引用都登记在驱动中;驱动需要记录Binder引用 ->实体之间多对一的关系;为引用找到对应的实体;为某个进程中的实体创建或查找到对应的引用;记录Binder的归属地(位于哪个进程中);通过 管理Binder的强/弱引用创建/销毁Binder实体等等。
驱动里的Binder是什么时候创建的呢?当每个server为自己的Binder实体向SMgr注册的时候,驱动都会感知到。而ServiceMgr作为系统第一个申请注册的Binder,那时候binder机制还未建立起来,所以内核就给它开了另外一条绿色通道,即创建无名内核binder实体,和创建0号binder实体引用。
随着各种各样Server不断地注册实名binder,不断向SMgr索要Binder的引用,不断将Binder引用从一个进程传递给另一个进程,越来越多的Binder以flat_binder_object形式穿越驱动做进程间的迁徙,里面最重要的是binder实体的引用。
Binder将对每个经过它的binder结构做如下处理:首先检查传输结构的type域,如果是BINDER_TYPE_BINDER或BINDER_TYPE_WEAK_BINDER则创建内核的binder实体;如果是BINDER_TYPE_HANDLE或BINDER_TYPE_WEAK_HANDLE则创建binder的引用。随着越来越多的Binder实体或引用穿过驱动在进 程间传递,驱动会在内核里创建越来越多的节点或引用,当然这个过程对用户来说是透明的。
Binder实体在驱动中的表述
驱动中的Binder实体也叫节点,隶属于提供实体的进程,由struct binder_node结构来表示:
struct binder_node {
int debug_id;
struct binder_work work;
union {
struct rb_node rb_node;
struct hlist_node dead_node;
};
struct binder_proc *proc;
struct hlist_head refs;
int internal_strong_refs;
int local_weak_refs;
int local_strong_refs;
void __user *ptr;
void __user *cookie;
unsigned has_strong_ref:1;
unsigned pending_strong_ref:1;
unsigned has_weak_ref:1;
unsigned pending_weak_ref:1;
unsigned has_async_transaction:1;
unsigned accept_fds:1;
unsigned min_priority:8;
struct list_head async_todo;
};
驱动在内核中为每个进程维护一棵红黑树来存放该进程创建的binder实体。以flat_binder_object中的binder域为索引。如果没有找到就创建一个新节点并添加到红黑树中。
Binder引用在驱动中的表述
和实体一样,Binder的引用也是驱动根据传输数据中的flat_binder_object创建的,隶属于获得该引用的进程,用struct binder_ref表示:
struct binder_ref {
int debug_id;
struct rb_node rb_node_desc;
struct rb_node rb_node_node;
struct hlist_node node_entry;
struct binder_proc *proc;
struct binder_node *node;
uint32_t desc;
int strong;
int weak;
struct binder_ref_death *death;
};
驱动为每个进程维护一棵红黑树来存放该进程所有正在使用的引用。驱动可以通过两个键值来帮助进程索引Binder引用:
Binder采用一种全新策略:由Binder驱动负责管理数据接收缓存。即数据存放在驱动的缓冲区中,同时驱动将这片缓冲区再映射到接收进程的缓冲区中,这样只需将发送方的数据拷贝至内核缓冲区即可,接收方由于已经映射了这片缓冲区,所以也相当与拥有这片数据了。