本文共 20087 字,大约阅读时间需要 66 分钟。
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具有粘结剂的意思,那么它把什么东西粘结在一起呢?在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驱动虽然默默无闻,却是通信的核心。尽管名叫‘驱动’,实际上和硬件设备没有任何关系,只是实现方式和设备驱动程序是一样的:它工作于内核态,提供open(),mmap(),poll(),ioctl()等标准文件操作,以字符驱动设备中的misc设备注册在设备目录/dev下,用户通过/dev/binder访问该它。驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。驱动和应用程序之间定义了一套接口协议,主要功能由ioctl()接口实现,不提供read(),write()接口,因为ioctl()灵活方便,且能够一次调用实现先写后读以满足同步交互,而不必分别调用write()和read()。Binder驱动的代码每个分支位置不一样,再加上我也没有下内核的代码,先给个4.4的Binder.c
的地址,有兴趣的可以自己研究。
和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。
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都需要注册给ServiceManager广而告之的。Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。如果我们是从事application开发,跨进程的自己手写AIDL文件,或者相同进程的bindService自己添加一个继承Binder的子类,那么这个Binder没有向ServiceManager注册名字,所以是个匿名Binder。Client将会收到这个匿名Binder的引用,通过这个引用向位于Server中的实体发送请求。匿名Binder为通信双方建立一条私密通道,只要Server没有把匿名Binder发给别的进程,别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用,向该Binder发送请求。
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对象)。
spreadStrongBinder() const; status_t readStrongBinder(sp * val) const; status_t writeStrongBinder(const sp & val); status_t writeWeakBinder(const wp & 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,创建ServiceManagerProxyServiceManagerProxy 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 { ArrayListservices = new ArrayList (); 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 null
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中去处理
转载地址:http://ibjb.baihongyu.com/