Binder通信模型
Binder的优势
实现方式
Binder使用Client-Server通信方式:一个进程作为Server提供诸如视频/音频解码,视频捕获,地址本查询,网络连接等服务;多个进程作为Client向Server发起服务请求,获得所需要的服务。要想实现Client-Server通信据必须实现以下两点:一是server必须有确定的访问接入点或者说地址来接受Client的请求,并且Client可以通过某种途径获知Server的地址;二是制定Command-Reply协议来传输数据。例如在网络通信中Server的访问接入点就是Server主机的IP地址+端口号,传输协议为TCP协议。对Binder而言,Binder可以看成Server提供的实现某个特定服务的访问接入点, Client通过这个‘地址’向Server发送请求来使用该服务;对Client而言,Binder可以看成是通向Server的管道入口,要想和某个Server通信首先必须建立这个管道并获得管道入口。
性能优化
如果是传统的Linux IPC方式中,socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。
举个例子如,Client要将一块内存数据传递给Server,一般的做法是,Client将这块数据从它的进程空间拷贝到内核空间中,然后内核再将这个数据从内核空间拷贝到Server的进程空间,这样,Server就可以访问这个数据了。但是在这种方法中,执行了两次内存拷贝操作。所以Binder设计时采用了折衷的方式,只需要把Client进程空间的数据拷贝一次到内核空间,然后Server与内核共享这个数据就可以了,整个过程只需要执行一次内存拷贝,提高了效率。同时这样更有C/S架构的模型,方便管理。
Binder 通信模型
从英文字面上意思看,Binder具有粘结剂的意思,那么它把什么东西粘结在一起呢?在Android系统的Binder机制中,由一系统组件组成,分别是Client、Server、Service Manager和Binder驱动程序,其中Client、Server和Service Manager运行在用户空间,Binder驱动程序运行内核空间。Binder就是一种把这四个组件粘合在一起的粘结剂了,其中,核心组件便是Binder驱动程序了,Service Manager提供了辅助管理的功能,Client和Server正是在Binder驱动和Service Manager提供的基础设施上,进行Client-Server之间的通信。这四个角色的关系和互联网类似:Server是服务器,Client是客户终端,Service Manager是域名服务器(DNS),Binder驱动是路由器。
Binder驱动
和路由器一样,Binder驱动虽然默默无闻,却是通信的核心。尽管名叫‘驱动’,实际上和硬件设备没有任何关系,只是实现方式和设备驱动程序是一样的:它工作于内核态,提供open(),mmap(),poll(),ioctl()等标准文件操作,以字符驱动设备中的misc设备注册在设备目录/dev下,用户通过/dev/binder访问该它。驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。驱动和应用程序之间定义了一套接口协议,主要功能由ioctl()接口实现,不提供read(),write()接口,因为ioctl()灵活方便,且能够一次调用实现先写后读以满足同步交互,而不必分别调用write()和read()。Binder驱动的代码每个分支位置不一样,再加上我也没有下内核的代码,这里先给个4.4的Binder.c
的地址,有兴趣的可以自己研究。
ServiceManager 与实名Binder
和DNS类似,ServiceManager的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。注册了名字的Binder叫实名Binder,就象每个网站除了有IP地址外还有自己的网址。Server创建了Binder实体,为其取一个字符形式,可读易记的名字,将这个Binder连同名字以数据包的形式通过Binder驱动发送给ServiceManager,通知ServiceManager注册一个名叫张三的Binder,它位于某个Server中。驱动为这个穿过进程边界的Binder创建位于内核中的实体节点以及ServiceManager对实体的引用,将名字及新建的引用打包传递给ServiceManager。ServiceManager收数据包后,从中取出名字和引用填入一张查找表中。
细心的读者可能会发现其中的蹊跷:ServiceManager是一个进程,Server是另一个进程,Server向ServiceManager注册Binder必然会涉及进程间通信。当前实现的是进程间通信却又要用到进程间通信,这就好象蛋可以孵出鸡前提却是要找只鸡来孵蛋。Binder的实现比较巧妙:预先创造一只鸡来孵蛋:ServiceManager和其它进程同样采用Binder通信,ServiceManager是Server端,有自己的Binder对象(实体),其它进程都是Client,需要通过这个Binder的引用来实现Binder的注册,查询和获取。ServiceManager提供的Binder比较特殊,它没有名字也不需要注册,当一个进程使用BINDER_SET_CONTEXT_MGR命令将自己注册成ServiceManager(会用到ioctl(fd, cmd, arg)函数,cmd为BINDER_SET_CONTEXT_MGR)时Binder驱动会自动为它创建Binder实体(这就是那只预先造好的鸡)。其次这个Binder的引用在所有Client中都固定为0而无须通过其它手段获得。也就是说,一个Server若要向ServiceManager注册自己Binder就必需通过0(即NULL指针)这个引用号和ServiceManager的Binder通信。类比网络通信,0号引用就好比域名服务器的地址,你必须预先手工或动态配置好。要注意这里说的Client是相对ServiceManager而言的,一个应用程序可能是个提供服务的Server,但对ServiceManager来说它仍然是个Client。
Client 获得实名Binder的引用
Server向ServiceManager注册了Binder实体及其名字后,Client就可以通过名字获得该Binder的引用了。Client也利用保留的0号引用向ServiceManager请求访问某个Binder:我申请获得名字叫张三的Binder的引用。ServiceManager收到这个连接请求,从请求数据包里获得Binder的名字,在查找表里找到该名字对应的条目,从条目中取出Binder的引用,将该引用作为回复发送给发起请求的Client。从面向对象的角度,这个Binder对象现在有了两个引用:一个位于ServiceManager中,一个位于发起请求的Client中。如果接下来有更多的Client请求该Binder,系统中就会有更多的引用指向该Binder,就象java里一个对象存在多个引用一样。而且类似的这些指向Binder的引用是强类型,从而确保只要有引用Binder实体就不会被释放掉。通过以上过程可以看出,ServiceManager象个火车票代售点,收集了所有火车的车票,可以通过它购买到乘坐各趟火车的票-得到某个Binder的引用。
匿名 Binder
并不是所有Binder都需要注册给ServiceManager广而告之的。Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。如果我们是从事application开发,跨进程的自己手写AIDL文件,或者相同进程的bindService自己添加一个继承Binder的子类,那么这个Binder没有向ServiceManager注册名字,所以是个匿名Binder。Client将会收到这个匿名Binder的引用,通过这个引用向位于Server中的实体发送请求。匿名Binder为通信双方建立一条私密通道,只要Server没有把匿名Binder发给别的进程,别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用,向该Binder发送请求。
Binder传递数据的载体Parcel
Parcel是一种数据的载体,用于承载希望通过IBinder发送的相关信息(包括数据和对象引用),正是基于Parcel这种跨进程传输数据的能力,进程间的IPC通信才能更加平滑可靠。
Parcel具备打包和重组的能力,它提供了非常丰富的接口以方便应用程序的使用。
class Parcel {
friend class IPCThreadState;
public:
class ReadableBlob;
class WritableBlob;
Parcel();
~Parcel();
const uint8_t* data() const;
size_t dataSize() const;//获取当前已经存储的数据大小
size_t dataAvail() const;//当前Parcel中的可读数据的大小
size_t dataPosition() const;//数据的当前位置值,有点类似于游标
size_t dataCapacity() const;//当前Parcel的存储能力
status_t setDataSize(size_t size);
void setDataPosition(size_t pos) const;
status_t setDataCapacity(size_t size);//设置Parcel空间的大小,显然存储的数据不
//能大于这个值
status_t setData(const uint8_t* buffer, size_t len);
status_t appendFrom(const Parcel *parcel,
size_t start, size_t len);
int compareData(const Parcel& other);
bool allowFds() const;
bool pushAllowFds(bool allowFds);
void restoreAllowFds(bool lastValue);
Parcel提供的读写操作时配套的,用哪种方式写入的数据就要用相应的方式正确读取。数据是按照host cpu的字节序来读写的
Primitive Arrays的读写操作通常是先写入用4个字节表示的数据大小值,接着写入数据本身。
如果写入数据时系统发现已经超出了Parcel的存储能力,它会自动申请所需的内存空间,并扩展dataCapacity;而且每次写入都是从dataPosition开始的。
遵从Parcelable协议的对象可以通过Parcel来存取。
Active Objects
Parcel的另一个强大武器就是可以读写Active Object.通常我们存入Parcel中的是对象的内容,而Active Objects写入的则是它们的特殊标志引用。所以从Parcel中读取这些对象时,大家看到的并不是重新创建的对象实例,而是原来那个被写入的实例。能够以这种方式传输的对象主要有两类:
1. Binder
Binder是Android系统IPC通信的核心机制之一,同时它也是一个对象。利用Parcel将Binder对象写入,读取时就能得到原始的Binder对象,或者是它的特殊代理实例(最终操作的还是原始的Binder对象)。
sp<IBinder> readStrongBinder() const;
status_t readStrongBinder(sp<IBinder>* val) const;
status_t writeStrongBinder(const sp<IBinder>& val);
status_t writeWeakBinder(const wp<IBinder>& val);
2. FileDescriptor。FileDescriptor是Linux中的文件描述符,可以通过Parcel的如下方式进行传递
// Place a file descriptor into the parcel. The given fd must remain
// valid for the lifetime of the parcel.
// The Parcel does not take ownership of the given fd unless you ask it to.
status_t writeFileDescriptor(int fd, bool takeOwnership = false);
// Place a file descriptor into the parcel. A dup of the fd is made, which
// will be closed once the parcel is destroyed.
status_t writeDupFileDescriptor(int fd);
应用程序如何使用Parcel
通过Parcel.obtain()接口来获取一个Parcel对象
status_t mError;
uint8_t* mData;
size_t mDataSize;
size_t mDataCapacity;
mutable size_t mDataPos;
binder_size_t* mObjects;
size_t mObjectsSize;
size_t mObjectsCapacity;
mutable size_t mNextObjectHint;
mutable bool mFdsKnown;
mutable bool mHasFds;
bool mAllowFds;
release_func mOwner;
void* mOwnerCookie;
ServiceManagerNative.java (base\core\java\android\os)
class ServiceManagerProxy implements IServiceManager {
public ServiceManagerProxy(IBinder remote) {
mRemote = remote;
}
public IBinder asBinder() {
return mRemote;
}
public IBinder getService(String name) throws RemoteException {
Parcel data = Parcel.obtain();//获取一个Parcel对象
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);//用于写入IBinder接口标志,所带
的参数是String类型的,如:IServiceManager.descriptor = 'android.os.IServiceManager'
data.writeString(name);//写入需要向ServiceManager查询的Service名称
mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();
return binder;
}
Parcel在整个IPC的传递过程中比较繁琐,但有一点是不变的,那就是写入方和读取方所使用的协议必须是完全一致的
// Write RPC headers. (previously just the interface token)
status_t Parcel::writeInterfaceToken(const String16& interface)
{
writeInt32(IPCThreadState::self()->getStrictModePolicy() |
STRICT_MODE_PENALTY_GATHER);
// currently the interface identification token is just its name as a string
return writeString16(interface);
}
读取方式Service_Manager.c中,这个ServiceManager是用C/C++编写的,也特别提醒的是,servicemanager所对应的c文件是service_manager.c和binder.c。源码工程目录中还有其他诸如ServiceManager.cpp的文件存在,但并不属于SM程序。
ServiceManager(Binder Server)就像TCP/IP协议中的DNS服务器,其IP地址就是默认的0;
int main(int argc, char** argv)
{
struct binder_state *bs;
union selinux_callback cb;
char *driver;
if (argc > 1) {
driver = argv[1];
} else {
driver = "/dev/binder";
}
bs = binder_open(driver, 128*1024);
if (!bs) {
#ifdef VENDORSERVICEMANAGER
ALOGW("failed to open binder driver %s\n", driver);
while (true) {
sleep(UINT_MAX);
}
#else
ALOGE("failed to open binder driver %s\n", driver);
#endif
return -1;
}
if (binder_become_context_manager(bs)) {//将自己设置为Binder大管家,整个Android系统只允许一个ServiceManager存在,若后面还有人调用这个函数就会失败
ALOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
cb.func_log = selinux_log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
#ifdef VENDORSERVICEMANAGER
sehandle = selinux_android_vendor_service_context_handle();
#else
sehandle = selinux_android_service_context_handle();
#endif
selinux_status_open(true);
if (sehandle == NULL) {
ALOGE("SELinux: Failed to acquire sehandle. Aborting.\n");
abort();
}
if (getcon(&service_manager_context) != 0) {
ALOGE("SELinux: Failed to acquire service_manager context. Aborting.\n");
abort();
}
binder_loop(bs, svcmgr_handler);//进入循环,等待客户的请求
return 0;
}
int svcmgr_handler(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply)
{
struct svcinfo *si;
uint16_t *s;
size_t len;
uint32_t handle;
uint32_t strict_policy;
int allow_isolated;
//ALOGI("target=%p code=%d pid=%d uid=%d\n",
// (void*) txn->target.ptr, txn->code, txn->sender_pid, txn->sender_euid);
if (txn->target.ptr != BINDER_SERVICE_MANAGER)
return -1;
if (txn->code == PING_TRANSACTION)
return 0;
// Equivalent to Parcel::enforceInterface(), reading the RPC
// header with the strict mode policy mask and the interface name.
// Note that we ignore the strict_policy and don't propagate it
// further (since we do no outbound RPCs anyway).
strict_policy = bio_get_uint32(msg);
s = bio_get_string16(msg, &len);
if (s == NULL) {
return -1;
}
if ((len != (sizeof(svcmgr_id) / 2)) ||
memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {
fprintf(stderr,"invalid id %s\n", str8(s, len));
return -1;
}
if (sehandle && selinux_status_updated() > 0) {
struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle();
if (tmp_sehandle) {
selabel_close(sehandle);
sehandle = tmp_sehandle;
}
}
switch(txn->code) {
case SVC_MGR_GET_SERVICE:
case SVC_MGR_CHECK_SERVICE:
s = bio_get_string16(msg, &len);
if (s == NULL) {
return -1;
}
handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
if (!handle)
break;
bio_put_ref(reply, handle);
return 0;
case SVC_MGR_ADD_SERVICE:
这其中包含Binder驱动与用户空间的各种交互,后续讲解
Binder Server(SM)是基于Native代码实现的,那么获取SM服务是不是也必须使用Native语言来实现呢?其实它们之间没有必然的联系。所有的Binder Client或者Binder Server都是基于Binder驱动展开的,因此只要能正常使用Binder驱动,采用哪种编程语言都是可以的。
如果要访问SM(Binder server)的服务,无非就是下面几部
1.打开Binder设备
2.执行mmap
3.通过Binder驱动向SM发送请求(SM的handle为0)
4.获得结果
真正实现的时候,不是每个线程都要打开binder驱动执行mmap,其后果是消耗的系统资源会越来越多,直到崩溃。有效的解决方法就是每个进程只允许打开一次Binder设备,且只做一次内存映射,所有需要使用Binder驱动的线程共享这一资源
这就涉及到ProcessState和IPCThreadState这两个类
这两个类在MediaPlayerService中有简单的分析
首先需要对SM的服务进行封装,叫做ServiceManagerProxy 其代码位于ServiceManagerNative.java (base\core\java\android\os) ServiceManagerProxy获得WindowsManagerService的服务,假设一个应用程序要通过SMProxy,获得WMS的Binder句柄,可以用以下方式(真实的实现不是这样的,Android系统中真实的实现与此基本类似,只不过它在ServiceManagerProxy上又加了一层封装,即ServicManager.java)
/*应用程序获取SM服务的示例*/
//step 1,创建ServiceManagerProxy
ServiceManagerProxy sm = new ServiceManagerProxy(new BpBinder(HANDLE));
//step 2,通过ServiceManagerProxy 获取SM的某项服务
IBinder wms_binder = sm.gerService("window")
ServiceManagerProxy所提供的服务和服务端的SM必须是一致的。把这些方法提取出来,就是ServiceManagerProxy的接口,取个名字叫做IServiceManager。
public interface IServiceManager extends IInterface
{
/**
* Retrieve an existing service called @a name from the
* service manager. Blocks for a few seconds waiting for it to be
* published if it does not already exist.
*/
public IBinder getService(String name) throws RemoteException;
/**
* Retrieve an existing service called @a name from the
* service manager. Non-blocking.
*/
public IBinder checkService(String name) throws RemoteException;
/**
* Place a new @a service called @a name into the service
* manager.
*/
public void addService(String name, IBinder service, boolean allowIsolated)
throws RemoteException;
/**
* Return a list of all currently running services.
*/
public String[] listServices() throws RemoteException;
/**
* Assign a permission controller to the service manager. After set, this
* interface is checked before any services are added.
*/
public void setPermissionController(IPermissionController controller)
throws RemoteException;
static final String descriptor = "android.os.IServiceManager";
int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
int CHECK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;
int ADD_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;
int LIST_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
int CHECK_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
int SET_PERMISSION_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;
}
class ServiceManagerProxy implements IServiceManager {
public ServiceManagerProxy(IBinder remote) {
mRemote = remote;
}
public IBinder asBinder() {
return mRemote;
}
public IBinder getService(String name) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);
mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();
return binder;
}
public IBinder checkService(String name) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);
mRemote.transact(CHECK_SERVICE_TRANSACTION, data, reply, 0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();
return binder;
}
public void addService(String name, IBinder service, boolean allowIsolated)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);
data.writeStrongBinder(service);
data.writeInt(allowIsolated ? 1 : 0);
mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
reply.recycle();
data.recycle();
}
public String[] listServices() throws RemoteException {
ArrayList<String> services = new ArrayList<String>();
int n = 0;
while (true) {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeInt(n);
n++;
try {
boolean res = mRemote.transact(LIST_SERVICES_TRANSACTION, data, reply, 0);
if (!res) {
break;
}
} catch (RuntimeException e) {
// The result code that is returned by the C++ code can
// cause the call to throw an exception back instead of
// returning a nice result... so eat it here and go on.
break;
}
services.add(reply.readString());
reply.recycle();
data.recycle();
}
String[] array = new String[services.size()];
services.toArray(array);
return array;
}
public void setPermissionController(IPermissionController controller)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeStrongBinder(controller.asBinder());
mRemote.transact(SET_PERMISSION_CONTROLLER_TRANSACTION, data, reply, 0);
reply.recycle();
data.recycle();
}
private IBinder mRemote;
}
应用程序使用ServiceManager更加方便,连ServiceManagerProxy都不用创建了,因为ServiceManager的所有接口都是static的,可以直接使用ServiceManager的功能
ServiceManager.java (base\core\java\android\os)
/**
* Returns a reference to a service with the given name.
*
* @param name the name of the service to get
* @return a reference to the service, or <code>null</code> if the service doesn't exist
*/
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);//查询缓存
if (service != null) {
return service;//从缓存中找到结果,直接返回
} else {
return Binder.allowBlocking(getIServiceManager().getService(name));//向SM发起查询
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
return sServiceManager;// 返回一个IServiceManager对象
}
// Find the service manager
sServiceManager = ServiceManagerNative
.asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
return sServiceManager;
}
public abstract class ServiceManagerNative extends Binder implements IServiceManager
{
/**
* Cast a Binder object into a service manager interface, generating
* a proxy if needed.
*/
static public IServiceManager asInterface(IBinder obj)
{
if (obj == null) {
return null;
}
IServiceManager in =
(IServiceManager)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
return new ServiceManagerProxy(obj);
}
public ServiceManagerNative()
{
attachInterface(this, descriptor);
}
ServiceManagerProxy终于出现了,作为SM的代理,其必定要与Binder驱动进行通信,其只是简单记录了传入的Binder对象
public ServiceManagerProxy(IBinder remote) {
mRemote = remote;
}
public IBinder getService(String name) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);
mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);//利用IBinder对象执行命令
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();
return binder;
}
IBinder.transact:利用IBinder的transact将请求发出去,而不用理会Binder驱动Open mmap以及一大堆具体的Binder协议中的命令。所以这个IBinder一定会再内部使用ProcessState和IPCThreadState来与Binder驱动进行通信。
int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
public interface IBinder {
/**
* The first transaction code available for user commands.
*/
int FIRST_CALL_TRANSACTION = 0x00000001;
Client要与Server所使用的业务码一致,如上面的GET_SERVICE_TRANSACTION
所以这个业务码是1,那Service_Manager.c中的业务码说明
enum {
/* Must match definitions in IBinder.h and IServiceManager.h */
PING_TRANSACTION = B_PACK_CHARS('_','P','N','G'),
SVC_MGR_GET_SERVICE = 1,
SVC_MGR_CHECK_SERVICE,
SVC_MGR_ADD_SERVICE,
SVC_MGR_LIST_SERVICES,
};
最终会到Service_Manager中去处理