系统版本: Ubuntu 22.04 lts
AOSP分支: android-14.0.0_r28
相信做过Android开发的,一定对Binder这个词不陌生,我看过很多关于Binder的面试题和面试答案,总感觉是在隔靴搔痒,死记硬背一些概念,所以本文打算系统的讲解一下,到底什么是Binder。
什么是Binder
Binder,其实本质上来讲就是Android为我们提供的一种基于Binder驱动的跨进程通讯方式,如果说要问Android区别于Linux最大的特色是什么,那我可能会说Binder。
那么问题来了,为什么我们需要Binder?
为什么需要Binder
Linux本身有很多跨进程通讯手段,比如Socket,比如管道,为什么Android要多提供Binder这样一种新的跨进程通讯方式呢? 这个问题其实由Brian Swetland回答过了,他是2004年到2012年期间Android系统及内核的技术负责人,而他在邮件中是这么说的:
Android使用Binder的核心理由,其实就两条:
- avoiding copies by having the kernel copy from the writer into a
ring buffer in the reader's address space directly (allocating space
if necessary)
- managing the lifespan of proxied remoted userspace objects that can
be shared and passed between processes (upon which the userspace
binder library builds its remote reference counting model)
第一条,其实是说的Binder在驱动层使用了Linux提供的mmap内存映射机制,第二条,应该是指的Binder驱动层对"对象"的生命周期的管理,总之,Android的技术负责人认为这种新的IPC更高效和便捷。
好了,现在我们知道了Android为什么要使用Binder了,那我们应该看看,Binder到底是怎么工作的。
Binder驱动
首先,我们来看看Binder驱动,Binder驱动层的代码,并不在AOSP中,而是在Android Linux Kernel中,如何下载Android Linux Kernel源码,可以看这里:
下载好之后,查看drivers/android/,在此目录下可以看到如下文件:
打开binder.c,这里,就是驱动层的核心代码所在了:
下面,我将详细介绍整个Binder的工作流程。
Binder的工作流程
第一步 注册Binder设备
首先,我们已经知道了我们的Binder通信是基于Binder驱动来进行的,那么最开始的工作,就是Binder驱动层的初始化,打开drivers/android/binder.c,找到binder_init方法:
这里,便是我们的Binder驱动初始化方法,其它的我们暂时不关心,主要看调用的init_binder_device方法,这里面最核心的,就是调用了misc_register去注册了我们的Binder驱动:
下面是我们的binder_device结构体:
hlist: 这个变量主要是用来存储binder_device的全局链表,目前binder_device,会有三种,分别是/dev/binder,/dev/hwbinder和/dev/vndbinder,他们的区别可以看这里
miscdevice: 这是杂项设备信息,如果不了解什么是杂项设备,可以看这里:
context: 这个变量存储的即是一个特殊的binder_node,即ServiceManager,后面会讲到。
这是我们的Binder驱动设备文件操作方法定义:
如果不了解misc_register是怎么注册设备文件方法的,可以看这里:
简单来说,当我们注册好设备文件方法,当Native层调用mmap的时候,最终会调用到binder_mmap这个方法中来。
第二步 启动ServiceManager
什么是ServiceManager呢? ServiceManager你可以理解为它是一个Binder驱动层的DNS服务,当一个客户端想要与服务端进行通讯的时候,只需要通过一个"网址",就可以通过ServiceManager找到真正的服务端,它可以说是理解Binder通信的核心概念之一,下面我们来看看,它是怎么被启动的。
首先打开AOSP/system/core/rootdir/init.rc,找到如下三行:
我们可以看到,它也是被Init进程通过解析RC文件被启动的,我们再打开AOSP/frameworks/native/cmds/servicemanager/main.cpp,找到main方法,这里,便是ServiceManager的实际启动入口:
我们可以看到,整个方法并不复杂,但是我首先要简单介绍两个类的概念,以方便我们对Native层Binder通信的理解。
ProcessState
打开AOSP/frameworks/native/libs/binder/ProcessState.cpp,我们就可以看见ProcessState的具体实现。
这个类的主要作用是控制Binder驱动,比如打开,关闭。
每个进程,都会只有一个ProcessState实例,如果有兴趣的可以仔细看看它的init方法,看看它是怎么保持每个进程只有单个实例的。
IPCThreadState
打开AOSP/frameworks/native/libs/binder/IPCThreadState.cpp,我们就可以看到IPCThreadState的具体实现。
这个类的主要作用,就是实际的通过Binder驱动发送和接收数据了。
每个线程,都会只有一个IPCThreadState实例。
好,接下来我们重新回到ServiceManager的初始化部分,首先我们来看这段代码:
sp<ProcessState> ps = ProcessState::initWithDriver(driver);
最先开始,是进行了ProcessState的初始化,进入此方法,可以发现调用的是init方法:
进入init方法,可以看到最终调用了ProcessState的构造方法:
接下来再进入构造方法,我们可以看到如下内容:
这里,我们就可以看到,最开始,调用了open_driver方法,这个方法,就是用来打开Binder驱动的,我们进入此方法:
前面知道,我们已经注册了Binder驱动层设备文件方法了,所以最开始的open,会进入Binder驱动的binder_open方法,我们先看看驱动层在这个方法做了什么:
其实binder_open方法,最主要的作用就是在Binder驱动层创建并初始化了一个binder_proc,接下来我再介绍下,什么是binder_proc。
binder_proc
在AOSPLinuxKernel/drivers/android/binder_internal.h中,可以找到这样一个结构体:
'
这个结构体的主要作用,就是存储我们的进程信息,当Native层每次打开Binder驱动的时候,Binder驱动层都会创建一个binder_proc,可以说每一个Native层的ProcessState,在Binder进程中,都会有一个对应的binder_proc。
好,接下来我们再回到Native层ProcessState的open_driver,可以看到调用open之后,获取了Binder驱动的fd,下面就是执行这样一条:
status_t result = ioctl(fd, BINDER_VERSION, &vers);
这里,又会再次回到Binder驱动层的binder_ioctl:
这个方法,主要是执行我们的一些控制命令,从上面我们可以知道,我们传进去的命令是BINDER_VERSION,那么就会执行到下面这个部分:
这里主要是获取内核Binder版本,然后与用户层的Binder版本进行对比,防止错误。
接下来我们再回到open_driver,可以看到接下来执行到了这一步:
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
同样的,我们再回到驱动层,可以看到执行的是这里:
他的主要作用,是设置了驱动层当前进程的对应binder_proc的线程上限。
再回到open_driver,接下来到了这一步:
result = ioctl(fd, BINDER_ENABLE_ONEWAY_SPAM_DETECTION, &enable);
然后驱动层就会执行这里:
这里就是设置是否开启OneWay Spam的检测,那么什么是OneWay Spam呢?众所周知,我们的方法是可以设置为one way的,也就是客户端不等待服务端的返回,直接继续执行自己的逻辑,那么这个时候,客户端如果疯狂的调用oneway方法,就会挤爆Binder的缓冲区,这个行为就叫OneWay Spam,当开启此项,如果进程有OneWay Spam的嫌疑,那么驱动层就会返回一个BR_ONEWAY_SPAM_SUSPECT,在IPCThreadSate.cpp里,也可以看到对此返回的处理:
到这里,open_driver方法已经执行完毕了,在最后,返回了驱动的fd,接下来我们再回到ProcessState的构造方法。
接下来,会对open_driver的返回进行一个判断,如果打开正常,则会执行如下:
mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,
opened.value(), 0);
首先,我们来看BINDER_VM_SIZE的定义:
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
这就是我们Binder缓冲区的大小,默认情况下是1024kb - 8kb,也就是1016kb。
这里的mmap,最终会调用驱动层的binder_mmap:
可以看到,最后实际执行的binder_alloc_mmap_handler去进行内存的分配,这里就不展开讲了,下面说明一下,Binder驱动层所谓的一次拷贝到底是怎么实现的。
Binder单次拷贝
Binder的单次拷贝,其原理就是Binder驱动会申请一块实际的物理内存,并且通过mmap内存映射机制,将服务端进程的用户空间的虚拟内存地址和内核空间的虚拟内存地址都同时映射在这一块物理内存上,这样当客户端进程向Binder驱动层传输数据的时候,Binder驱动层会把数据写入服务端进程内核空间的对应虚拟内存地址,那么对于服务端的用户空间来说,就可以直接读取到客户端传输过来的数据,因为虚拟内存地址所指向的是同一块物理内存。
到这一步,其实Binder驱动层只是分配了内核的连续内存和实际的物理内存,当驱动层调用binder_transaction并且在里面调用binder_alloc_new_buf,才会进行实际的内存映射。
至此ProcessState的构造方法便执行完了,接下来,我们再回到AOSP/frameworks/native/cmds/servicemanager/main.cpp,可以看到ProcessState初始化完成以后,会调用:
ps->setThreadPoolMaxThreadCount(0);
这里,最终调用的也是刚才驱动层的方法,这个方法主要是设置的Native层ProcessState的最大线程上限为0,并且让驱动层对应的binder_proc的最大线程上限也为0。
好,我们再回到刚才,接着往下看这段代码:
sp<ServiceManager> manager = sp<ServiceManager>::make(std::make_unique<Access>());
if (!manager->addService("manager", manager, false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()) {
LOG(ERROR) << "Could not self register servicemanager";
}
可以看到,这里初始化了ServiceManager,并且调用了addService,并且把自己的实例传了进去,这里就是开始注册服务端了,而最先开始被注册成服务端的,第一个服务,就是我们的ServiceManager,也可以理解为Binder通讯的"DNS服务"。
我们进入此方法,直接找到这一段:
我们可以看到,其实ServiceManager是通过mNameToService来保存我们的Binder服务端实例的,我们再看下它的类型:
struct Service {
sp<IBinder> binder; // not null
bool allowIsolated;
int32_t dumpPriority;
bool hasClients = false; // notifications sent on true -> false.
bool guaranteeClient = false; // forces the client check to true
Access::CallingContext ctx; // process that originally registers this
// the number of clients of the service, including servicemanager itself
ssize_t getNodeStrongRefCount();
~Service();
};
using ServiceMap = std::map<std::string, Service>;
ServiceMap mNameToService;
可以看到它就是一个HashMap,key为服务的名字,而value则是Service实例,而在Service中,则保存了binder实例,但是这并不是一个实际上其它进程的服务实例,而是一个基于其句柄构造BPBinder实例。
我们再回到前面的main方法,看这两条语句:
IPCThreadState::self()->setTheContextObject(manager);
ps->becomeContextManager();
第一条,是获取或创建当前线程的IPCThreadState实例,并且调用它的setTheContextObject方法并且将ServiceManager实例赋值给它,可以看到里面的实现很简单:
第二条,则是调用了刚才创建的ProcessState的becomeContextManager,我们进入此方法:
可以看到这里调用了驱动层,并且命令为BINDER_SET_CONTEXT_MGR_EXT,我们进入驱动层的此方法:
可以看到最终调用了驱动层的binder_ioctl_set_ctx_mgr,这里,其实就是根据当前binder_proc创建一个特殊的binder_node,并且将它作为特殊的binder_node保存下来,这里再说下binder_node。
binder_node
binder_node可以理解为一个实体的节点,这个节点就是指一个Binder服务端,也就是说,每一个Binder服务端,在驱动层都会有对应的binder_node,下面是binder_node的结构体:
总之becomeContextManager这个方法的作用,你可以理解为ServiceManager作为一个特殊的Binder服务端,不光是在Native层特殊,在驱动层,Binder也为ServiceManager留下了一个特殊的位置,即一个特殊的binder_node,可以让驱动层随时能联系上它。
好,我们再回到main方法,可以看到接下来,创建了一个Native层的Looper,并且进入等待状态:
sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/);
BinderCallback::setupTo(looper);
ClientCallbackCallback::setupTo(looper, manager);
#ifndef VENDORSERVICEMANAGER
if (!SetProperty("servicemanager.ready", "true")) {
LOG(ERROR) << "Failed to set servicemanager ready property";
}
#endif
while(true) {
looper->pollAll(-1);
}
// should not be reached
return EXIT_FAILURE;
至此,ServiceManager,也就是我们的"DNS服务"已经准备完成了。
第三步 注册成为服务端
那么现在,我们的Binder驱动也准备好了,"DNS服务"也注册好了,现在要做的是什么呢? 就是开启一个又一个的Binder服务了。
我们来找个Binder服务注册的例子,打开AOSP/frameworks/av/media/audioserver/main_audioserver.cpp,找到main方法,划到这部分代码:
sp<IServiceManager> sm = defaultServiceManager();
sm->addService(String16(IAudioFlinger::DEFAULT_SERVICE_NAME), afAdapter,
false /* allowIsolated */, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT);
可以看到,首先调用defaultServiceManager,获取到了IServiceManager,那我们先来看看这个方法的实现:
我们可以看到这里实际调用了ProcessState的getContextObject,再进入此方法:
可以看到这里调用了getStrongProxyForHandle,并且传入了0,下面我不再展开,而是大致说一下这里的实现逻辑。
首先,handle是一个驱动层使用的概念,你可以理解为它是一个句柄,驱动层通过这个句柄,可以找到对应的binder_node和binder_proc,而我们的ServiceManager因为其特殊的身份,所以它的句柄是0。
因为我们目前与ServiceManager不在同一个进程,所以我们的defaultServiceManager获取的并不是ServiceManager实例,而是一个可以与ServiceManager进行Binder通信的BpServiceManager,而这个BpServiceManager,是由AOSP/frameworks/native/libs/binder/aidl/android/os/IServiceManager.aidl生成出来的,而当我们调用IServiceManager的方法的时候,你可以理解为最终是通过BpServiceManager通过Binder通讯的方式,最终调用到了我们在单独进程的那个ServiceManager,这中间的逻辑根我们在Java层使用aidl文件的逻辑,其实没有什么差别,具体中间是怎样实现的,有兴趣的可以详细看一下相关类的具体实现,这里不再赘述。
接下来,我们再回到刚才的main方法,这里的addService,最终会调用我们ServiceManager的addService,这里的逻辑其实和ServiceManager初始化后调用自己的addService一样,就是把当前的服务名和Binder对象添加进了mNameToService里。
到这里,服务可以说是已经注册完成了,接下来就是开始启动我们的Binder服务。
第四步 启动Binder服务
启动Binder服务端,只需要调用这两条语句就可以:
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
首先,我们先分析startThreadPool,ProcessState的初始化,上面已经讲过,这里不再赘述:
可以看到,这里调用了spawnPooledThread,再看看这个方法:
可以看到,这里面最核心的其实是创建了一个PoolThread,并且运行它的run方法,那我们再看看PoolThread的定义:
可以看到,里面很简单,就是调用了IPCThreadState::self()->joinThreadPool()。
那么问题来了,可以看到上面还会执行一次IPCThreadState::self()->joinThreadPool(),为什么同样的方法要执行两次?
这个问题,只要进入joinThreadPool就可以知道了:
这个方法,其实就是我们最终与Binder驱动层交互的主要逻辑了,可以看到这里的主要执行逻辑,就是不断地等待客户端的请求,并且处理命令,主要result不出问题,那么就会一直循环执行。
我们接下来再进入getAndExecuteCommand:
可以看到先调用了talkWithDriver,进入此方法:
可以看到前面的代码,主要是为了构造一个binder_write_read,在Native层,它的结构体如下:
然后我们再进入驱动层,也可以找到一个binder_write_read,结构如下:
可以看到Native层和驱动层的结构基本是一致的,这个binder_write_read,代表的就是驱动层的单次读写。
我们再回到talkWithDriver,找到这部分代码:
这里就是将我们构造好的binder_write_read传输给驱动层了,我们再进入驱动层的处理:
再进入binder_ioctl_write_read:
可以看到里面调用了binder_thread_write和binder_thread_read去处理写和读:
因为我们给mOut复制了值,这个值可能是BC_ENTER_LOOPER或BC_REGISTER_LOOPER,可以看到对应的处理,主要是给thread的looper进行赋值:
要注意,当调用ProcessState::self()->startThreadPool()的时候,会创建一个新的线程,并设置那个新的线程为主线程,当调用IPCThreadState::self()->joinThreadPool()的时候,指的是当前线程,所以ProcessState::self()->startThreadPool()最终创建出来的线程同样会去调用IPCThreadState::self()->joinThreadPool(),但是那是主线程的joinThreadPool(),你可以理解当执行完ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool();的时候,目前已经有两条,即一条主线程,一条子线程可以与Binder通信的线程了
回到刚才的binder_ioctl_write_read,我们执行完binder_thread_write之后,就要执行binder_thread_read了,因为在IPCThreadState的talkWithDriver中,我们的doReceive和needRead都为true,所以read_size的值是大于0的:
我们进入binder_thread_read,看到这里调用了binder_wait_for_work:
可以看到从这个方法开始,线程进入等待状态,并且将当前的thread加入对应binder_proc的waiting_threads里面:
到这里,我们的Binder服务就启动起来了,线程会进入等待状态,当有客户端想要进行Binder通讯时,对应的线程才会被唤醒,接下来,我们就来看看当有客户端进行Binder通信时,整个的处理流程是怎么样的。
第五步 Binder客户端通信
比如当我们看到如下代码的时候:
sp<IBinder> binder =
defaultServiceManager()->getService(String16("media.player"));
sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);
CHECK(service.get() != NULL);
service->addBatteryData(params);
前三句,我们都已经知道具体流程了,那么当我们调用service->addBatteryData(params),实际调用的是什么呢? 这里实际的调用,是在AOSP/frameworks/av/media/libmedia/IMediaPlayerService.cpp中,可以看到BpMediaPlayerService的实际实现:
可以看到,这里面实际的传输,是通过remote()->transact()来进行传输的,remote()返回的,其实就是一个BpBinder实例,transact方法的具体实现,可以在AOSP/frameworks/native/libs/binder/BpBinder.cpp看到:
可以看到,最终实际的传输还是调用IPCThreadState::self()->transact来进行的,我们再进入此方法,可以看到,这里调用了writeTransactionData,并且传入的cmd参数为BC_TRANSACTION,再进入此方法:
可以看到,这个方法主要是组装了一个binder_transaction_data,并将其写入mOut中。
我们再次回到IPCThreadState::self()->transact中,可以看到下面这段代码:
这段代码的主要含义,就是判断当前的调用是否是ONE_WAY,如果不是的话,就向waitForResponse传入reply,如果是的话,就传入nullptr。
我们再进入waitForResponse方法:
可以看到,从这里开始,我们又进入了talkWithDriver方法了,也就意味着,我们开始要往驱动层写入数据了,我们直接进入驱动层:
可以看到,无论是BC_TRANSACTION还是BC_REPLY,驱动层的逻辑都是相同的,就是先把传入的binder_transaction_data从用户空间拷贝到内核空间,然后调用binder_transaction方法,我们再进入此方法:
可以看到,这里面的方法很长,一共900多行,所以我大致讲一下这里面做的事情,首先通过传入的binder_transaction_data的handle找到对应的服务端binder_node和binder_proc,然后将传入的数据写入到服务端进程的缓冲区内,然后从目标进程的waiting_thread中找一条线程来进行唤醒。
至于里面是如何通过binder_thread的todo列表,binder_transaction和binder_work来实现的,有兴趣可以自己看看。
接下来,就是服务端进程的工作了,我们再回到第三步所提到过的binder_thread_read,直接看这部分:
这里,就是服务端的线程开始被唤醒,并且开始处理客户端的请求了,接下来主要是一系列的数据的处理和赋值,我们先不关心,主需要知道,从这里开始,服务端线程会把回传给Native层的数据的cmd设置为BR_TRANSACTION,就可以了,然后也需要知道执行完这里,我们的服务端就可以获取到客户端传过来的数据了,以上面的代码举例也就是params这个参数。
接下来我们再看服务端Native层的处理:
这个时候talkWithDriver已经不会再阻塞,服务端下面要执行的就是executeCommand,进入此方法,找到这部分代码:
可以看到,这里调用的就是根据tr.cookie转换成的BBinder指针,通过MediaPlayerService的初始化方法我们可以知道,这其实指向的就是一个MediaPlayerService实例:
我们再进入BBinder的transact方法,可以看到最终调用的是自身的onTransact,那么我们看下MediaPlayerService的头文件,可以看到他继承了BnMediaPlayerService,然后再看BnMediaPlayerService,可以看到它重写了onTransact方法:
可以看到它的实现,最终是调用了MediaPlayerService的addBatteryData方法:
然后我们再回到executeCommand的BR_TRANSACTION部分的处理,可以看到接下来下做了判断,如果不是OneWay调用的话,就会调用sendReply方法:
进入此方法,可以看到又调用了writeTransactionData和waitForResponse,将服务端的返回写入到驱动层:
这里就不再赘述了,这里其实还是一样的逻辑,将回复的数据写入客户端的内存缓冲区中,然后将cmd设置成BR_REPLY,然后再让客户端对应的请求去处理服务端的返回。
客户端的Native层,会将驱动层的数据先读取到binder_transaction_data中,然后再将数据赋值给reply:
最后,上层再从reply中读取到服务端的返回值,至此,就完成了一次Binder通信。