1、binder原理
纵观现有市面上所有讲binder的文章,都存在一个最大的问题:没有讲清楚binder对象是什么?
不清楚binder对象是什么,那就不能理解handle是什么?不能理解什么时候是binder什么时候是handle,那就不能真正理解整个IPC的通讯过程。
我们首先回到binder的目的,就是IPC(Inter-Process Communication)进程间通讯。那么怎么样实现进程间通讯呢?要素有三个:
- 函数指针;
- 函数参数;
- 函数返回值;
binder通讯的本质实际上非常简单,就是client、server双方在共享内存的基础上封装成自定义api函数,并无神奇之处。我们看看他是怎么和IPC三要素对应上的:
1.1、IPC函数指针
binder的service_server可以向service_client提供service服务,但反过来不行。所以binder service其实是单向的,只有service_server端才能提供service函数,且函数只能在service_server端运行。
大部分情况下:service_server端提供的一组IPC服务本地函数,就是binder对象。
例如,mediaserver注册的一系列service中的一个”media.player”:
1 | /frameworks/av/media/mediaserver/main_mediaserver.cpp: |
service_server提供了一组可以在server本地运行的函数,即binder对象。如下:
1 | /frameworks/av/media/libmedia/IMediaPlayerService.cpp: |
在service_client端可以通过handle来引用这个binder对象,还封装了一系列与之对应的函数来组织数据。但是这些函数实际上是通讯用的,函数的实际功能并不能在client本地执行:
1 | /frameworks/av/media/libmedia/IMediaPlayerService.cpp: |
所以理解binder对象和handle是非常关键的。service_server端需要在本地执行函数,所以执行时函数调用的3要素(函数、参数、返回值)都必须是本地的,所以它必须拥有一组函数的binder对象;service_client端不需要在本地执行,所以它没有函数集的binder对象,它只有函数集的远端引用handle。
binder通讯的3个主角:service_mannager、service_server、service_client。在各种场景下,分别的binder对象和handle关系如下:
场景 | client | server |
---|---|---|
service_manage初始化 | service_manager: 本地的binder对象为svcmgr_handler()函数集; 通过ioctl BINDER_SET_CONTEXT_MGR命令把该binder对象注册成全局handle0; | binder device: 创建handle0引用,指向service_manager |
service_server的addService | service_server: target handle:handle0 data:binder对象为一组本地service函数集; | service_manager: binder驱动会创建对该binder对象的引用handle 通过SVC_MGR_ADD_SERVICE命令把该handle加入到service_manage的handle链表中; |
service_client的get_service | service_client: target handle:handle0 data:service name。整个过程中没有binder对象的参与。 向service_manager获取service_server的service函数的handle; | service_manage: SVC_MGR_GET_SERVICE命令,通过service的name在service_manage的handle链表中查找对应的handle,并且把handle返回给service_client; 这样对于service_server的binder对象,service_client和service_manage都持有它的handle了; |
service_client调用service | service_client: target handle:serive handle0。上一步获取的handle。 data:调用参数。调用参数中也可能包含handle/binder对象。(看server端的处理) | service_server: 驱动把target handle翻译成本地binder对象,调用对象提供的本地函数。 对于data中可能包含的含handle/binder对象的处理: 1、如果包含的handle是本进程binder的引用,把它翻译成本地binder,在本地可以运行; 2、如果包含的handle不是本进程binder的引用,只能给它创建一份新的引用handle。这个handle也不能在server进程中运行,只能向其他service_server请求服务; 3、不可能包含binder对象,因为client进程的binder对象在service_server进程中无法运行; |
衍生出的原则如下:
- service_server类的进程只有binder对象,没有handle(除了handle0),因为它所有操作都必须本地执行,引用远程对象毫无意义;
- service_client类的进程只有handle,没有binder对象,因为它需要远程执行service不需要本地执行;
- service_mannager进程同时有binder对象和handle,它本地binder对象的作用就是操作所有其他进程的handle;
1.2、IPC函数参数
如上一节描述,service_client可以通过名字向service_manage查询得到handle。这个handle就相当于远程的函数集指针。
但是对于一个函数调用,我们除了需要函数指针,还需要传递参数。
binder使用parcel方式来打包函数参数和返回值。parcel可以用来传递几种类型的数据:
- 普通类型的少量数据;
- binder对象/handle(struct flat_binder_object);
- fd(struct binder_fd_object);
下面详细描述每种情况的parcel包格式和承载的内容。
- 1、普通类型的少量数据:
这种普通类型(int/long/string…)的少量数据存储最为简单,存入时按照一定的顺序存入,取出时按照数据的排列格式取出即可。
- 2、binder对象/handle(struct flat_binder_object):
这一类型数据的parcel包格式如下:
可以看到这种类型的parcel包中包含了两种数据:data0/data1/…是普通类型数据;binder_obj0/binder_obj1/…是binder对象,binder_obj0 offset/binder_obj1 offset/…指出了了binder对象在parcel包中的偏移;
binder对象和handle共用结构体struct flat_binder_object。
上一节说过binder对象其实就是一组函数的指针,但是一个指针只需要一个long类型就可以标识了,为什么还需要用一个结构体struct flat_binder_object来传递。我理解下来主要的思想如下:使用binder都是面向对象语言c++/java,它们把函数组也要实例化成一个对象,一个对象只有被引用时才不会被回收,远程引用也需要让本地引用加1。
一组service函数,对本地进程来说就是binder,对其他需要使用的进程来说需要远程引用,就是handle,是一对多的关系。关系图如下:
binder object是service_server的一个“local binder object”,service_manager和service_client创建了多个远程引用“remote handle”。
这个其实就是binder的核心思想,binder花费了大量的代码在维护这个关系上面:
- service_server进程在驱动中创建了binder_node节点来保存binder对象,把本进程所有的binder_node都挂载在一颗红黑树proc->nodes上;
- service_manager和service_client每个新进程对这个binder对象引用,就创建一个新的binder_ref,它的值就是handle,并回指向binder_node。并且把本进程对其他service_server的引用都挂载到两颗红黑树proc->refs_by_node/proc->refs_by_desc上。并且远程引用会增加service_server进程关于binder对象的引用计数;
binder驱动负责建立起binder对象和handle之间的映射关系,创建上述的数据结构,并负责翻译:
- service_server把本地binder对象向service_manager注册。会在service_manager进程本地建立起binder_node,驱动会在service_manager进程中建立起对应的binder_ref引用,那么service_manager进程能看到的其实就是本进程对service_serverbinder对象的一个引用,并不能看到binder对象原始值;
- service_client根据名字向service_manager查询service。service_manager会返回本进程的handle,在内核中该handle会转换成binder对象binder_node。因为service_client不是service的本地进程,所以service_client不能得到binder对象,它只能得到引用handle。所以再针对service的binder对象创建一份service_client进程的本地引用;
[x] service_client调用远程service_server的service。内核判断handle引用是service_server的本地对象,就把handle转换成service_server的binder对象;
3、fd(struct binder_fd_object):
parcel还能传输文件句柄fd,此时的包格式如下:
传输fd的意义何在呢?当binder的两个进程间需要传输大量的数据。例如:图像声音数据、或者是一个对象。可以在匿名共享内存(Ashmem)中创建一块区域,源进程会得到一个相应的fd,再把这个fd使用binder传递给目的进程,就可以共享数据了。
需要特别说明的是对象的传递,在同一个进程内进行函数调用的话,参数对象通常是使用引用的方式传递的。但是如果是跨进程的调用,是没有办法引用的,只有把整个对象复制过去。这种操作叫做对象的序列化,java称为Serializable,android有优化的实现Parcelable。注意对象序列化的Parcelable和binder的parcel数据封装不是一回事,尽管他们原理上很相似。binder并没有提供对象Parcelable的接口,如果我们要跨进程传输对象,只能把对象序列化(Parcelable)到匿名共享内存中,再把对应fd通过binder传输给目的进程。
binder驱动在检测到传输的是fd,会在新的进程中分配一个新的fd,并指向原来的file结构,这样fd就被跨进程duplicate了。两个进程使用各自的fd对匿名共享内存区域进行mmap映射,就能访问相同的内存区域了。
1.3、IPC函数返回值
函数返回值也是使用和函数参数一样的parcel结构来封装数据的。就不再重复叙述。
上面提到的原则需要再次强调,在一次service_client和service_server之间的通讯,在传递参数和返回值时都要遵循的准则:service_client只会有handle,service_server只会有binder对象。
1.4、binder内存
前面说过binder通讯的本质就是在共享内存上加上一层api,我们来看看他是怎么管理共享内存的。
我们可以看到:
- binder驱动给每个进程分配最多4M的buffer空间,这段空间在内核通过binder_proc->alloc红黑树来管理,同时通过mmap映射到进程用户空间;
- 和所有的进程通讯机制类似,这段空间相当于进程的接收邮箱inbox,其他进程发过来的消息会从其他进程用户空间复制存放到这里;
- 因为是mmap的所有本进程的用户空间访问免除了一次拷贝;
- 另外因为进程支持多个线程,所以多个线程会共享本进程的binder buffer;
我们看一下process 0、process n进程和process 1进程进行binder通讯时的buffer使用情况:
- 首先会在process 1进程的inbox(binder buffer)空间中分配buffer;
- binder驱动把process 0、process n进程用户空间的消息拷贝到process 1进程的inbox内核buffer中;
- 因为mmap,process 1进程的用户空间也可以看见这些消息了;
2、binder驱动
驱动是整个binder通讯的核心,java和native都是对其的封装。
因为binder驱动代码比较繁杂,看代码比较不好理解。结合第一章讲的基础知识和binder通讯具体场景,我们使用图来分析每一个典型场景下binder驱动内的变化。
2.1、service_manager的初始化
通过上图我们可以看到具体过程:
- 1、binder驱动为service_manager进程创建一个新的binder_node结构,赋值:.ptr=0、.cookie=0、.proc=当前proc;
- 2、把这个binder_node新节点加入到当前进程的proc->nodes红黑树中;
- 3、把binder_device的全局handle 0指针binder_device->context.binder_context_mgr_node指向新创建的binder_node;这样其他人通过handle 0指针就能找到对应binder_node,进一步找到service_manager是哪一个进程;
service_manager代码在service_manager.c、binder.c,可以具体查看。初始化过程为:
1 | main() -> binder_open()、binder_become_context_manager() |
2.2、service_server的addService
通过上图我们可以看到,在service_server向service_manager注册service的时候,在驱动中的具体流程如下:
- 1、因为是向service_manager注册,所以target handle固定=0。通过binder_device->context找到handle 0对应的binder_node,也就找到了对应的binder_proc,找到了对应的service_manager进程;
- 2、在service_manager进程中分配binder buffer,把service_server传递过来的parcel数据全部复制进去;
- 3、翻译parcel数据中的binder对象,把binder翻译成handle;
- 4、可以看到service_manager进程的handle就是对service_server进程binder的一个引用。把handle加入到service_manager进程的handle缓存红黑树中;
- 5、把翻译后的parcel数据和其他信息打包成binder_transaction结构,并挂载到service_manager进程的proc->todo/thread->todo链表中,等待service_manager进程的读取;
service_manager的读取响应和reply动作就不去具体分析了,因为都非常的清晰。service_manager代码在service_manager.c、binder.c,可以具体查看。service_manager在svcmgr_handler()函数中响应service_server的SVC_MGR_ADD_SERVICE请求,最终调用do_add_service()把handle和对应的service name加到svclist链表中:
1 | main() -> binder_loop() -> binder_parse() -> svcmgr_handler() -> do_add_service() |
2.3、service_client的get service
如上图service_client向service_manager发送get service请求的数据比较简单:
- 1、根据handle 0找到service_manager进程;
- 2、在service_manager进程中分配binder buffer,把service_client传递过来的parcel数据全部复制进去;
- 3、parcel的内容中没有binder或者handle,不需要翻译;
- 4、把parcel数据和其他信息打包成binder_transaction结构,并挂载到proc->todo/thread->todo链表中,等待service_manager进程的读取;
上图是service_manager给service_client回复信息的过程:
- 1、service_manager根据service name在本地svclist链表中找到对应的handle,它把handle打包进parcel并reply给service_client;
- 2、根据service_manager所在线程thread->transaction_stack字段中保存的binder_transaction结构,从.from字段可以找到service_client所在的线程(binder_thread)和进程(binder_proc);
- 3、在service_client进程中分配binder buffer,把service_manager传递过来的parcel数据全部复制进去;
- 4、翻译parcel中打包的handle结构,判断handle指向的binder_node进程不是service_client进程,所以新建service_client进程中对binder_node新的引用。新创建handle并加入到service_client进程的handle缓存红黑树中;
- 5、这样service_client就从service_manager中获取到了service_server binder对应的引用handle;
- 6、把翻译后的parcel数据和其他信息打包成binder_transaction结构,并挂载到service_client进程的proc->todo/thread->todo链表中,等待service_client进程读取reply;
2.4、service_client调用service
上图是service_client调用service_server的service的过程:
- 1、service_client的target handle为上一步向service_manager查询得到的handle,根据handle能找到对应binder_node,进一步找到service_server所在进程;
- 2、在service_server进程中分配binder buffer,把service_client传递过来的parcel数据全部复制进去;
- 3、parcel中打包了函数参数,如果包含handle对象,需要进行翻译;不可能包含binder对象,因为service_client进程的binder对象在service_server进程中无法运行;
- 4、如果parcel中包含的handle指向的binder_noe和service_server是同一进程,把它翻译成本地binder,在本地可以运行;
- 5、如果parcel中包含的handle指向的binder_noe和service_server不是同一进程,那只能在service_server进程中给它创建一份新的引用handle。这个handle也不能在service_server进程中运行,只能向其他service_server请求服务;
- 6、把翻译后的parcel数据和其他信息打包成binder_transaction结构,并挂载到service_client进程的proc->todo/thread->todo链表中,等待service_client进程读取reply;
2.5、Scatter-gather模式
在Android O中binder增加了一种性能改进模式Scatter-gather,这是因为binder在传输IPC参数数据时,因为传输的量不大,binder实际上做了3次拷贝:
Scatter-gather把3次copy优化成1次:
具体的代码可以看驱动对BINDER_TYPE_PTR类型数据的处理:
1 | case BINDER_TYPE_PTR: { |
2.6、多个binder context
Android O以后创建了3个misc设备,对应3个domain(contexts),相互独立:
1 | # ls /dev/*binder |
因为在Android O以后HIDL也启用了binder通信,使用binder通信的进程越来越多,为了便于管理并且相互隔离,Android把binder划分成了3个domain(contexts):
IPC Domain | Description |
---|---|
/dev/binder | IPC between framework/app processes with AIDL interfaces |
/dev/hwbinder | IPC between framework/vendor processes with HIDL interfacesIPC between vendor processes with HIDL interfaces |
/dev/vndbinder | IPC between vendor/vendor processes with AIDL Interfaces |
2.7、调试接口
binder驱动创建了很多调试接口,可以方便的debug binder通讯的过程。
1、”/d/binder/state”
全局情况:
1 | # more /d/binder/state |
2、”/d/binder/stats”
全局统计:
1 | # more /d/binder/stats |
3、”/d/binder/proc/xxx”
具体进程的情况:
1 | # cat /d/binder/proc/1037 |
3、service manager实现
service_manager逻辑很清晰,代码也不多,主要流程在上节中已经描述就不再详细分析。service_manager.c、binder.c
4、native实现
整个native层binder的实现还是以mediaserver为例来说明。
4.1、process/thread
上图已经把native层binder通讯最重要的部分都画出来了,理解了这张图native的实现基本理解了大半:
- binder在server接收端会创建多个线程,在发送端不会创建专门的线程直接在发送者的线程中;
[x] binder在server端的通用对象是BBinder,在client端的通用引用对象是BpBinder。具体service的server端和client端的实现,只要继承这两个类就行了;
1、ProcessState类
因为binder buffer是一个进程一份的,所以不论是client还是server进程,都只会创建一个binder fd,进行一次mmap映射。binder fd、mmap公共资源在本进程内的多个线程间共享。native使用了一个ProcessState类来管理这些进程公共资源。
1 | sp<ProcessState> proc(ProcessState::self()); |
↓
frameworks/native/libs/binder/ProcessState.cpp:
1 | sp<ProcessState> ProcessState::self() |
- 2、IPCThreadState类
native binder对线程也进行了封装。
- 2.1、对于server端来说,native binder创建一个线程池,可以多个接收线程来响应和运行service服务。例如
1 | # ps -eT | grep Binder |
具体代码如下:
1 | ProcessState::self()->startThreadPool(); |
↓
PoolThread类继承了Thread类,并且实现了线程主循环函数:threadLoop()
1 | class PoolThread : public Thread |
↓
创建IPCThreadState对象
frameworks/native/libs/binder/IPCThreadState.cpp:
1 | IPCThreadState* IPCThreadState::self() |
↓
最后进入IPCThreadState类的线程主循环函数joinThreadPool()
1 | void IPCThreadState::joinThreadPool(bool isMain) |
↓
我们只需要关注其中BR_TRANSACTION命令的处理:
1 | status_t IPCThreadState::executeCommand(int32_t cmd) |
↓
BBinder是一个标准的通用binder对象,它的transact()函数会被具体的service子类重写,所以会调用到具体子类的transact()函数中
frameworks/native/libs/binder/Binder.cpp:
1 | status_t BBinder::onTransact( |
↓
BnMediaPlayerService是负责具体实现的子类,最后会调用进BnMediaPlayerService类的onTransact()函数中:
frameworks/av/media/libmedia/IMediaPlayerService.cpp:
1 | status_t BnMediaPlayerService::onTransact( |
- 2.2、对于client端来说是发送数据,native binder不会对其创建新的线程,但是IPCThreadState类也为client端的发送提供了封装。
client端通用的binder远端代理类为BpBinder,它的发送数据到binder驱动的函数为transact():
frameworks/native/libs/binder/BpBinder.cpp:
1 | status_t BpBinder::transact( |
↓
最后调用到IPCThreadState类的相关方法:
frameworks/native/libs/binder/IPCThreadState.cpp
1 | status_t IPCThreadState::transact(int32_t handle, |
4.2、manager proxy
service_client service_server和service_manager通讯时,都是处于client角色,所以只能操作service_manager的代理对象。我们看一下具体的代理对象是怎么创建起来的。
server在注册service服务时,都需要获取到默认manager代理:
1 | void MediaPlayerService::instantiate() { |
↓
frameworks/native/libs/binder/IServiceManager.cpp:
1 | sp<IServiceManager> defaultServiceManager() |
|→
frameworks/native/libs/binder/ProcessState.cpp
1 | sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/) |
|→
在创建完标准BpBinder对象以后,使用了一个模板函数interface_cast
frameworks/native/libs/binder/include/binder/IInterface.h:
1 | template<typename INTERFACE> |
interface_cast
1 | inline sp<IServiceManager> interface_cast(const sp<IBinder>& obj) |
frameworks/native/libs/binder/include/binder/IInterface.h:
1 | #define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \ |
frameworks/native/libs/binder/IServiceManager.cpp:
1 | IMPLEMENT_META_INTERFACE(ServiceManager, "android.os.IServiceManager"); |
扩展为:
1 | #define IMPLEMENT_META_INTERFACE(ServiceManager, "android.os.IServiceManager") \ |
所以defaultServiceManager()最后得到了一个BpServiceManager对象,利用它的::addService()方法来注册service。
frameworks/native/libs/binder/IServiceManager.cpp:
1 | class BpServiceManager : public BpInterface<IServiceManager> |
remote()->transact()会调用到BpBinder的transact()函数,最后IPCThreadState的transact()函数。
frameworks/native/libs/binder/BpBinder.cpp:
1 | status_t BpBinder::transact( |
借用老罗的一张图总结,service_manager类之间复杂的关系:
4.3、server
有了manager的代理对象以后,server就可以注册服务并且创建binder rx服务线程了。
frameworks/av/media/mediaserver/main_mediaserver.cpp:
1 | int main(int argc __unused, char **argv __unused) |
↓
frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp:
1 | void MediaPlayerService::instantiate() { |
所有的细节在上面几节都已经描述过了,还是借用老罗的一张图总结service_server类之间复杂的关系:
4.4、client proxy
service_client也是创建代理对象,和manager代理非常相似。我们也来具体分析一下。
frameworks/av/media/libmedia/IMediaDeathNotifier.cpp:
1 | IMediaDeathNotifier::getMediaPlayerService() |
有了BpMediaPlayerService对象,即MediaPlayerService的远端代理,就可以调用远端service服务了。
frameworks/wilhelm/src/android/android_LocAVPlayer.cpp:
1 | void LocAVPlayer::onPrepare() { |
其中通过sm->getService(String16(“media.player”))返回BpBinder的过程如下:
frameworks/native/libs/binder/IServiceManager.cpp:
1 | virtual sp<IBinder> getService(const String16& name) const{} |
↓
frameworks/native/libs/binder/Parcel.cpp
1 | sp<IBinder> Parcel::readStrongBinder() const |
↓
然后就来到了创建manager代理对象同样的位置:
frameworks/native/libs/binder/ProcessState.cpp:
1 | sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle) |
根据BpBinder对象,使用interface_cast
interface_cast
1 | inline sp<IMediaPlayerService> interface_cast(const sp<IBinder>& obj) |
IMediaPlayerService定义在:
frameworks/av/media/libmedia/IMediaPlayerService.cpp:
1 | IMPLEMENT_META_INTERFACE(MediaPlayerService, "android.media.IMediaPlayerService"); |
展开为:
1 | #define IMPLEMENT_META_INTERFACE(MediaPlayerService, "android.os.IServiceManager") \ |
BpMediaPlayerService的定义为:
frameworks/av/media/libmedia/IMediaPlayerService.cpp:
1 | class BpMediaPlayerService: public BpInterface<IMediaPlayerService> |
还是借用老罗的一张图总结service_client类之间复杂的关系:
4.5、service thread管理
binder service初始会启动2个main线程来提供服务,在等待service服务过多的情况下会动态的增加binder线程的数量,但是目前没有实现动态减少binder线程可能觉得cache着更好。
service一般默认最大考验开启15个线程,这个数值也可以通过ioctl的BINDER_SET_MAX_THREADS命令来修改。
动态增加binder线程的动作是binder驱动完成的,因为驱动可以看到service进程整个的阻塞情况。
具体驱动代码binder.c:
1 | static int binder_thread_read(struct binder_proc *proc, |
frameworks/native/libs/binder/IPCThreadState.cpp:
1 | status_t IPCThreadState::executeCommand(int32_t cmd) |
↓
frameworks/native/libs/binder/ProcessState.cpp:
1 | void ProcessState::spawnPooledThread(bool isMain) |
4.6、死亡通知(DeathRecipient)
可以使用BC_REQUEST_DEATH_NOTIFICATION注册死亡通知,在server端正常或者异常死亡的情况下都能收到通知。
在server端进程正常或者异常退出时,会关闭进程所有打开的文件句柄:
1 | do_exit() |
最终会调用到binder fd的release函数,调用到死亡通知的回调:
1 | static int binder_release(struct inode *nodp, struct file *filp) |
5、java实现
略
6、AIDL(Android Interface Definition Language)
略
参考资料:
1、Android系统进程间通信(IPC)机制 罗升阳
2、Android Binder 分析
3、Android Bander设计与实现
4、Binder实现原理分析
5、一篇文章了解相见恨晚的 Android Binder 进程间通讯机制