1. binder是什么?
binder是安卓系统的进程间通信方式。
2. 为什么安卓要用binder?
Android内核是基于Linux系统,linux本身就有很多种进程间通信方式: 内存共享,消息队列、信号量等,为什么安卓还要用binder呢?
传统ipc(进程间通信):
共享内存,不需要内存拷贝,但是控制繁琐。
管道通信,需要两次内存拷贝。
binder通信:
Binder只需要一次拷贝是因为安卓的内存映射方法,也就是mmap。a进程发数据给b进程,a进程把数据拷贝给mmap开辟的内核空间,b进程通过mmap就可以取出来这个数据而不用重新拷贝。
3. binder的实现:
binder的实现是基于内存映射的,也就是上面说的mmap。
这里我们首先要理解用户空间和内核空间。内核空间可以访问所有的进程页表。也就是内核空间可以访问所有的用户空间。也是基于这个前提,进程间通信才可以实现。
要理解mmap必须先理解物理地址、虚拟地址和页表的概念。
物理地址:
物理地址空间是实在的存在于计算机中的一个实体,在每一台计算机中保持唯一独立性。我们可以称它为物理内存;如在32位的机器上,物理空间的大小理论上可以达到2^32字节(4GB)。
虚拟地址:
虚拟地址并不真实存在于计算机中。每个进程都有4G的虚拟地址空间,其中3G用户空间,1G内核空间(linux),每个进程共享内核空间,独立的用户空间。每个进程都有自己独立的虚拟地址空间。这样每个进程都能访问自己的地址空间,这样做到了有效的隔离。
虚拟地址里面存放的什么内容?
mmap的作用是映射文件描述符和指定文件的(off_t off)区域至调用进程的(addr,addr *len)的内存区域,如下图所示:
binder的整个流程:
4. binder的通信流程
整个安卓的进程间通信都是基于c/s架构的,就是client / server 架构。
系统启动时servicemanager作为0号进程先启动,然后会读取rc文件,把系统中其他的进程依次启动。
server启动的时候就把自己注册到servicemanager里面,servicemanager里面会维护一个services的list。
client端想要和service通信就会先获取servicemanager服务,然后从servicemanager里面通过service name获取对应的service的binder,拿到了这个binder client端就可以通过这个binder和service通信了。
具体过程如下:
4.1 server注册进程:
此处以mediaplayer为例:
int main(int argc __unused, char **argv __unused)
{
signal(SIGPIPE, SIG_IGN);
// 获取service manager的代理供IPC使用。
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm(defaultServiceManager());
ALOGI("ServiceManager: %p", sm.get());
InitializeIcuOrDie();
MediaPlayerService::instantiate(); // 注册service,这里是注册了一个binder
ResourceManagerService::instantiate(); // 这里和上面一样
// 这块是调用binder driver,底层实现是一个thread pool就是一个死循环
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
所有的::instantiate()函数都是static型函数,且分别是创建各个子模块的对象实例。由此可看出mediaserver共分2个子模块: MediaPlayerService & ResourceManagerService。
void MediaPlayerService::instantiate() {
defaultServiceManager()->addService( // 注册server,server name为media.player
String16("media.player"), new MediaPlayerService());
}
server注册到servermanager的时候会调用frameworks/native/cmds/servicemanager/service_manager.c中的do_add_service。这个函数中首先检查是否有权限注册service,没权限就会报错返回;然后检查是否已经注册过,注册过的service将不能再次注册。然后构造一个svcinfo对象,并加入一个全局链表中svclist中。最后通知binder设备:有一个service注册进来。
Tips: server一定是一个死循环,一直在等待client的调用,我们自己写的server一般是while死循环之类的。安卓server的死循环在binder driver里面,我们在server代码里面看不到,但是只要server调用了startThreadPool就会有一个死循环在底层跑着。
4.2 service启动流程
首先,每个service都要有对应的rc文件,里面说明了这个service启动需要加载的文件流程:
以mediaserver.rc为例:
service media /system/bin/mediaserver
class main
user media
group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc mediadrm
ioprio rt 4
task_profiles ProcessCapacityHigh HighPerformance
writepid /dev/memcg/camera/media/service/cgroup.procs
有了这个rc文件,系统在启动的时候就会调用system/bin/mediaserver这个可执行文件。这个文件里面的实现就是上面的main函数,然后就会把MediaPlayerService和ResourceManagerService注册进去。
4.3 client调用流程
下面以MediaCodecList为例:
// frameworks/av/media/libstagefright/MediaCodecList.cpp
sp<IMediaCodecList> MediaCodecList::getInstance() {
Mutex::Autolock _l(sRemoteInitMutex);
if (sRemoteList == nullptr) {
// 此处就是通过servicemanager获取name为"media.player"的server的binder
sp<IBinder> binder =
defaultServiceManager()->getService(String16("media.player"));
// 拿到了binder就可以访问service的接口了
sp<IMediaPlayerService> service =
interface_cast<IMediaPlayerService>(binder);
if (service.get() != nullptr) {
sRemoteList = service->getCodecList();
if (sRemoteList != nullptr) {
sBinderDeathObserver = new BinderDeathObserver();
binder->linkToDeath(sBinderDeathObserver.get());
}
}
if (sRemoteList == nullptr) {
// if failed to get remote list, create local list
sRemoteList = getLocalInstance();
}
}
return sRemoteList;
}
client端调用的就是IMediaPlayerService中的函数,这个IMediaPlayerService就是接口类,bn和bp都会继承这个类。client端调用的是bp的代码,然后bp通过binder调用到bn。
class BpMediaPlayerService: public BpInterface<IMediaPlayerService> {
virtual sp<IMediaCodecList> getCodecList() const {
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
remote()->transact(GET_CODEC_LIST, data, &reply);
// remote这里就是通过binder调用从client端调用server端
return interface_cast<IMediaCodecList>(reply.readStrongBinder());
}
}
status_t BnMediaPlayerService::onTransact(...) {
case GET_CODEC_LIST: {
sp<IMediaCodecList> mcl = getCodecList();
reply->writeStrongBinder(IInterface::asBinder(mcl));
// 这里就会通过binder调用把server端的调用结果返回给client端
}
}
sp<IMediaCodecList> MediaPlayerService::getCodecList() const {
return MediaCodecList::getLocalInstance();
}
4.4 一个问题: MediaPlayerService和ResourceManagerService属于一个进程吗?
这里首先理解一个概念: 代码中的service究竟是什么?
class MediaPlayerService : public BnMediaPlayerService
class BnMediaPlayerService: public BnInterface<IMediaPlayerService>
class IMediaPlayerService: public IInterface
service是一个继承了interface的具体实现,用于和client通信。server端和client端同时继承interface类,一个是bp一个是bn。
所以呢?service和binder有啥关系? service和进程有啥关系?
service调用了instantiate函数就是把自己注册到了servicemanager里面,这个时候service就是一个binder接口。
注意:直到这里都没有进程的概念。service 不等于 进程。
那么进程是怎么来的?
看看service的启动流程, 在servicemanager加载rc文件的时候,这个rc文件里面调用的是/system/bin/mediaserver,一个可执行文件,可执行文件在系统中是什么?是一个单独的进程。到这里就破案了。
MediaPlayerService和ResourceManagerService都在/system/bin/mediaserver中。所以他们肯定属于同一个进程,但是他们分别注册到了service manager中,所以他们是两个binder。
5. 安卓的treble机制
在Android 8.0开始,Android引入了Treble的机制,为了方便Android系统的快速移植、升级,提升系统稳定性,Binder机制被拓展成了"/dev/binder", "/dev/hwbinder","/dev/vndbinder"。
三种binder的具体使用如下:
此处需要注意VNDK的两种概念:
在代码中,当一个so既需要在system分区使用又需要在vendr分区使用则声明该so为VNDK,然后so生成的时候就会产生两个, 一个在system目录,一个在vendor目录。
此处的vndk是两个vendor分区的进程通信用的。
6. 总结
本文讲述了安卓binder通信。首先从linux已有进程通信存在的问题讲起,讲述了binder存在的必要,然后讲述了binder的实现流程,以及通过media player service为例讲述了在c/s架构中binder的具体使用过程。最后讲述了安卓中的treble机制,就是安卓对已有binder的几种扩展。下篇文章将通过audio finger和audio hal的通信再次讲述hwbinder的实现过程。敬请期待~