前言
Binder可以说是整个Android框架最重要的一个基础。如果不能吃透Binder,就谈不上对Android有多么深刻的理解。这个道理相信大部分Android开发者都清楚。但是Binder整个框架又看起来好像深不可测,上至Java,下至kernel。又横跨众多进程和服务。想看看源代码来学习。看着看着就看不明白了,不由得发出“我是谁?我在哪里?”这样的疑问。这篇文章就想从作者个人的经验来聊聊如何学习Binder。
Binder的学习
Binder的学习和学习其他源代码一样并没有什么特殊的诀窍。那就是硬着头皮看源码,看文章。反复的看,不停的想,至少把妨碍自己理解的堵塞点都搞懂了,整个链路从上到下,从下到上都无障碍的跑通了那才算真正掌握了Binder。当然这也是学习所有其他框架源码的要求。关于学习源码的更多方法,可以参考我的另一篇文章《我是如何学习Flutter源码的》。
源码浩如烟海,看着看着就不知道跑哪里去了,没有关系。可以参考前人的经验来为自己带路。然而互联网上说Binder的文章可谓多如牛毛。如何选择呢?这里我推荐一位大牛的Binder系列文章。一遍看不懂那就多看几遍,而且要结合Android源码去看。文章里的每一个代码片段最好都自己去源码里对照着研究。这个一是为了加深记忆,二是为了促进消化。
如果全搞明白了,那么恭喜你,这篇文章剩下的内容不用再看了。如果是那种刚看完觉得自己都会了,过一段时间别人问你你又说不出来个子丑寅卯。这种情况是因为你只是死记硬背记住了关于Binder的一些东西而没有完全理解Binder,有一些关于Binder通信的关键点没有搞清楚,知其然而不知其所以然。处于“半懂不懂”的状态。如果此时放弃学习Binder那就太可惜了。
写这篇文章呢,就是希望能用尽量少的搬运源码的方式来捋一下Binder。希望能帮你疏通堵点,彻底理解Binder。
正如我在前言里说的,Binder深不可测,上至Java,下至kernel。而程序员似乎会有个习惯,不太喜欢去挖自己领域之外的东西,喜欢Java的就不想去看C/C++的东西,搞APP的,搞Framework的不想去瞅一眼kernel里面长啥样。“我只要知道这东西API是啥,会用就行了。。。”。某些情况下这可能是没什么问题的。但不适用于Binder。
要彻底理解Binder,必须要理解Binder驱动(Driver)。
很多对Binder一知半解的同学通常都是卡在这个地方了,不是说硬记住那几个“BC_TRANSACTION”,“BR_REPLY”命令,然后交互流程是啥样就可以了。但如果此时你把这些东西也忘了,这篇文章也就不用往下看了,建议你回头把上面说的Binder系列文章撸几遍再来。
如果你现在处于“半懂不懂”的状态,那么接着看下面的章节能不能帮到你来理解Binder。
Binder的理解
本章节假设读者对Binder架构已经有了初步的了解。所以并不会很详细的搬源码。大部分东西都会直接拿来用。涉及驱动的部分也不会做详尽说明,只会把一些关键点串起来。文中也不会涉及Java Binder。Java Binder在这里完全是干扰。
概述
这里首先列一下Binder中的一些内容:
- 除了
service_manager
,其他应用级进程/线程,和Binder驱动打交道的都是IPCThreadState
。 - 我们只关注进程间通信的核心流程:
transaction
。 - 传输发起者需要告诉Binder三个东西:
handle
(你找谁?),cmd
(你要干啥?)和data
(东西呢?)。 - 在客户端
handle
由BpBinder
持有。不管什么样的服务在Binder眼里就是一串数字。记住这一点。 - 在server端真正的服务是
BBinder
来完成的。
error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,
&reply, tr.flags);
- 显然Binder驱动帮我们完成了从本地的一串数字
handle
到远端实体服务BBinder
的转换。
这里就引出了几个问题:
- 本地
handle
是如何转换为远端BBinder
的? - 这个
handle
是从哪里来的呢?
第二个问题可能有些同学会说是从service_manager
那里查来的,然而并没有这么简单。这个问题我们稍后再解释。这里先假设我们已经有了这个handle
来说明第一个问题。
Binder驱动传输流程
这个流程其实就只有三步:第一,客户端把数据写进来。第二,把数据从客户端传到服务端。第三,通知服务端把数据读走。
这个流程我希望大家能紧盯着handle
,因为它是我们理解问题1的唯一线索。
客户端把数据写进来
在用户空间,函数IPCThreadState::writeTransactionData()
负责写入驱动。
这个函数会构造一个binder_transaction_data
的结构体tr
。我们的handle
会被写进去。
status_t IPCThreadState::writeTransactionData(...
int32_t handle, ...)
{
binder_transaction_data tr;
...
// handle在这里
tr.target.handle = handle;
tr.code = code;
...
}
然后就到了驱动那里,也就是进入内核空间:
static int binder_thread_write(...) {
...
//我们只关注BC_TRANSACTION
case BC_TRANSACTION:
case BC_REPLY: {
struct binder_transaction_data tr;
//tr到了这里
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
//这个函数做传输工作
binder_transaction(proc, thread, &tr,
cmd == BC_REPLY, 0);
break;
}
...
}
写进来的可能是各种命令,我们只关注transaction
。
把数据从客户端传到服务端
这事是由函数binder_transaction()
来干的。注意看一下那几个入参,proc
代表客户端进程。thread
代表客户端线程,tr
呢就是最开始的那个,记住里面放着handle
。
传输的前提是要找到目标的进程\线程。而我们唯一的线索就是handle
。怎么通过handle
来获得目标进程\线程呢?
static void binder_transaction(...) {
...
if (tr->target.handle) {
//通过handle查找目标引用
target_ref = binder_get_ref(proc, tr->target.handle,
true);
//通过目标引用得到目标节点。
target_node = target_ref->node;
...
}
//从node里拿到目标进程的信息
target_proc = target_node->proc;
...
//然后搞了个binder_transaction的结构体
t = kzalloc(sizeof(*t), GFP_KERNEL);
...
//设置目标进程/线程
t->to_proc = target_proc;
t->to_thread = target_thread;
...
//设置目标节点
t->buffer->target_node = target_node;
...
//要干的活是传输
t->work.type = BINDER_WORK_TRANSACTION;
...
//把传输任务插入到目标进程/线程的todo队列。
binder_proc_transaction(t, target_proc, target_thread);
...
把数据从客户端传到服务端阶段就完成了。从上述关键代码我们可以看到,要完成传输就需要拿到目标进程的信息。而我们只有handle
。驱动会先拿这个handle
在自身进程信息proc
内查找节点的引用ref
,然后由这个引用就能得到节点node
,得到节点就能拿到目标进程的信息target_proc
了。那么ref
是啥?node
又是啥呢?为什么他们能通过handle
找的到呢?
node
可以说是Binder驱动的核心数据结构。node
会以红黑树的形式保存在服务端进程结构体内,客户端则保存的是它的引用ref
。而handle
来自ref->desc
。
node
和ref
是什么时候保存到Binder驱动中的呢?这个我们在后面解决handle
从哪里来的问题再解释。
所以在传输阶段我们看到handle
换成了node
。在插入到目标进程todo
队列的数据里只有node
而没有handle
。handle
已经完成了它的使命,接下来让我们关注node
。
唤起服务端把数据读走
static int binder_thread_read(...) {
...
//有活干了
w = list_first_entry(...);
...
//看看是什么活,我们只关心transaction
switch (w->type) {
case BINDER_WORK_TRANSACTION: {
t = container_of(w, struct binder_transaction, work);
} break;
}
...
if (t->buffer->target_node) {
tr.target.ptr = target_node->ptr;
tr.cookie = target_node->cookie;
...
cmd = BR_TRANSACTION;
}
...
tr.code = t->code;
}
cmd
设置成了BR_TRANSACTION
;binder_thread_read()
会拼一个binder_transaction_data
。也就是tr
出来。它是要被送往用户空间的,就像之前从用户空间往binder驱动里写的也是一个tr
一样。这里注意驱动把node
里的两个东西设置给了tr
。ptr
和cookie
。记住它们。
接下来就离开内核空间,来到服务端用户空间了:
status_t IPCThreadState::executeCommand(int32_t cmd) {
//我们只关心传输
case BR_TRANSACTION:
{
if (tr.target.ptr) {
if (reinterpret_cast<RefBase::weakref_type*>(
tr.target.ptr)->attemptIncStrong(this)) {
error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,
&reply, tr.flags);
reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);
}
}
break;
}
啊哈,这里我们就能看出来原来cookie
的真身就是BBinder
。也就是我们服务的真身了啊。
所以捋一下Binder驱动的传输过程就是从客户端的handle
在自身进程信息proc
里查找ref
。再通过ref
就能得到node
。而node
则是存储在服务端进程信息里的。node
就很关键了,从它身上能找到目标进程的信息和目标服务的真身BBinder
实例。然后回到用户区直接调用就好了。
传输过程就介绍完了,接下来我们还有解决另外一个系列问题,handle
从从哪里来?node
又是什么时候给保存到内核里去的呢?
对了,这就涉及到services_manager
了。
服务注册及获取
我们知道services_manager
是个特殊的服务。有点像个DNS服务器,其他什么AMS,PMS等等都需要先到它那里注册以后,客户们才能在services_manager
找到他们。既然services_manager
这么特殊,那它在驱动中肯定会有个独一无二的node
吧。
所以services_manager
启动的时候会给Binder驱动发这么个命令BINDER_SET_CONTEXT_MGR
。
Binder驱动照办:
static int binder_ioctl_set_ctx_mgr(struct file *filp)
{
...
temp = binder_new_node(proc, 0, 0);
...
context->binder_context_mgr_node = temp;
binder_put_node(temp);
...
}
函数binder_new_node()
是用来新建node
的
static struct binder_node *binder_new_node(struct binder_proc *proc,
binder_uintptr_t ptr,
binder_uintptr_t cookie)
三个入参,proc
,ptr
和cookie
。回想一下上面介绍传输过程的时候这三个参数是不是都用到过?当然,对于services_manager
来说只需要进程信息,其他两个入参都是0。这个特殊的node
被设置给context->binder_context_mgr_node
。
接下来让我们看一下服务注册过程:
注册服务
注册服务的要点是盯住服务实体Binder是如何转化为node
的。
我们都知道service_manager
特殊,特殊就特殊在它的handle
就是0。所以无论是注册服务还是获取服务,我们不需要去别处问,自己就可以搞出来个代表service_manager
的BpBinder
。
注册的时候是这样的:
virtual status_t addService(const String16& name, const sp<IBinder>& service,
bool allowIsolated, int dumpsysPriority) {
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
data.writeInt32(allowIsolated ? 1 : 0);
data.writeInt32(dumpsysPriority);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}
注意这句data.writeStrongBinder(service);
也就是说把服务自身给写到要传输的数据里面去了。
写成啥样了呢?
status_t flatten_binder(){
...
flat_binder_object obj;
...
IBinder *local = binder->localBinder();
if (!local) {
BpBinder *proxy = binder->remoteBinder();
const int32_t handle = proxy ? proxy->handle() : 0;
obj.hdr.type = BINDER_TYPE_HANDLE;
obj.binder = 0;
obj.handle = handle;
obj.cookie = 0;
} else {
obj.hdr.type = BINDER_TYPE_BINDER;
obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
obj.cookie = reinterpret_cast<uintptr_t>(local);
}
...
}
因为这里是注册服务,所以给的Binder是实体Binder,走的是else
分支。类型为BINDER_TYPE_BINDER
,cookie
设置为service
实体。
注册服务是一次handle
为0的Binder传输。回想我们之前讨论的传输过程,handle
如果是0的话会在传输过程走不一样的分支:
static void binder_transaction(...) {
...
if (tr->target.handle) {
...
} else {
target_node = context->binder_context_mgr_node;
}
//从node里拿到目标进程的信息
target_proc = target_node->proc;
...
switch (hdr->type) {
case BINDER_TYPE_BINDER:
case BINDER_TYPE_WEAK_BINDER: {
struct flat_binder_object *fp;
fp = to_flat_binder_object(hdr);
ret = binder_translate_binder(fp, t, thread);
} break;
case BINDER_TYPE_HANDLE:
case BINDER_TYPE_WEAK_HANDLE: {
struct flat_binder_object *fp;
fp = to_flat_binder_object(hdr);
ret = binder_translate_handle(fp, t, thread);
} break;
...
//把传输任务插入到目标进程/线程的todo队列。
binder_proc_transaction(t, target_proc, target_thread);
...
对于注册服务的流程来说在传输的时候有两个地方需要注意,
- 由于
handle
是0,所以直接会找到binder_context_mgr_node
。这个node
也就是我们之前看到的service_manager
初始化的时候创建的,也就是说我们直接就扎到了service_manager
。 - 还记得我们之前把服务实体放在传输数据里面了吗?这里驱动会检查传输的数据,如果发现有
BINDER_TYPE_XXX
类型的都要做转换,为啥要转呢?因为我的实体到你那里就得是个handle
啊, 否则都不在同一个进程,不转换的话你拿我的实体也没用啊。所以,这里我们的服务实体要做一次转换了:
static int binder_translate_binder(struct flat_binder_object *fp,
struct binder_transaction *t,
struct binder_thread *thread)
{
struct binder_node *node;
struct binder_ref *ref;
struct binder_proc *proc = thread->proc;
struct binder_proc *target_proc = t->to_proc;
node = binder_get_node(proc, fp->binder);
if (!node) {
node = binder_new_node(proc, fp->binder, fp->cookie);
}
ref = binder_get_ref_for_node(target_proc, node, &thread->todo);
if (fp->hdr.type == BINDER_TYPE_BINDER)
fp->hdr.type = BINDER_TYPE_HANDLE;
else
fp->hdr.type = BINDER_TYPE_WEAK_HANDLE;
fp->binder = 0;
fp->handle = ref->desc;
fp->cookie = 0;
...
}
注意看这里的转换,首先是在自己的node
树里面找有没有实体服务Binder对应的node
。这里因为我们是要注册服务,所以node
不存在,需要新建一个。你看至此我们的新服务已经有了自己的node
了。然后呢会在目标进程信息,也就是service_manager
中找这个node
的引用,没有的话会新建一个引用的,这个过程会产生一个数放在ref->desc
。对了,这个数就是我们的新服务在service_manager
那边的handle
了。最后就是对原始数据做变身了。类型从BINDER
变为HANDLE
。清空binder
和cookie
。给handle
字段赋值,我们的实体Binder就这样华丽的变成自己的分身了。
千万不要搞混了,这里做转换的是我们要注册的服务,完全是处在事务数据中。而传输这些事务数据的是handle为0,对应node是binder_context_mgr_node的事务。
然后就是service_manager
那边读取传输的数据。拿到那个转换以后的fp->handle
和服务的名字一起保存起来。
总体而言注册服务:
- 是一次对
service_manager
的传输,只不过需要在开始的时候把服务实体Binder放在要传输的数据里。 - Binder驱动会在传输过程中做一次从实体服务Binder到对应
handle
的变换。 - 在这个变换的过程中会创建实体服务Binder对应的
node
。
接下来我们看一下获取服务的过程。貌似应该已经一目了然了。客户端拿着服务名字去service_manager
那里去请求handle
。拿到以后就可以实例化BpBinder
去调用服务了。
获取服务
获取服务的过程分两段,一个是把服务名字传过去,另一个是把handle
传回来。去程没啥好说的,和上面的注册服务过程差不多,还少了中间做转换的步骤。回程就是不一样的操作了。service_manager
把查到的handle
通过BC_REPLY
给送回来的。别看handle
只是个数,在传输的时候必须把它包装一下,变成这样的:
obj->hdr.type = BINDER_TYPE_HANDLE;
obj->handle = handle;
obj->cookie = 0;
类型是BINDER_TYPE_HANDLE
。
这个数据结构会写入Binder驱动,命令BC_REPLY
会走到和BC_TRANSACTION
一样的函数,也就是binder_transaction()
,但是某些分支有有所不同。
binder_transaction(...){
if (reply) {
in_reply_to = thread->transaction_stack;
target_thread = in_reply_to->from;
target_proc = target_thread->proc;
}
...
case BINDER_TYPE_HANDLE:
case BINDER_TYPE_WEAK_HANDLE: {
struct flat_binder_object *fp;
fp = to_flat_binder_object(hdr);
ret = binder_translate_handle(fp, t, thread);
} break;
...
}
可见返回过程不需要再查找node
因为调用端是谁是清楚的。直接把from
变成target
就行了。
因为返回数据块里面放着获取到的服务handle
。同样的这里会给它做个变换。
看一下binder_translate_handle()
如何做变换:
static int binder_translate_handle(struct flat_binder_object *fp,
struct binder_transaction *t,
struct binder_thread *thread)
{
struct binder_ref *ref;
struct binder_proc *proc = thread->proc;
struct binder_proc *target_proc = t->to_proc;
ref = binder_get_ref(proc, fp->handle,
fp->hdr.type == BINDER_TYPE_HANDLE);
if (ref->node->proc == target_proc) {
if (fp->hdr.type == BINDER_TYPE_HANDLE)
fp->hdr.type = BINDER_TYPE_BINDER;
else
fp->hdr.type = BINDER_TYPE_WEAK_BINDER;
fp->binder = ref->node->ptr;
fp->cookie = ref->node->cookie;
} else {
struct binder_ref *new_ref;
new_ref = binder_get_ref_for_node(target_proc, ref->node, NULL);
fp->binder = 0;
fp->handle = new_ref->desc;
fp->cookie = 0;
}
}
这里的转换有两个分支,如果此服务进程和目标进程相同,则将handle
转换成实体Binder。也就是说你去service_manager
那里获取到了和你同一个进程的服务,自然可以转换为实体Binder直接拿来用了。
如果不是同一个进程呢?则会到目标进程信息那里去获取一个新的handle
。替换掉原来的handle
返回给调用者。
为什么会这样呢?这就需要深入了解node
和ref
的关系。
node
实体只保存在创建者进程所对应的proc
中。ref
是保存在当前进程中对外部node
实体的引用,不同的进程对同一个node
的ref
是不同的。- 所以可以认为
handle
是进程私有的,不要想当然的认为从service_manager
拿到的handle
就是和自己进程拿到的一样。
总结
Binder虽然结构复杂,难以全面掌握。但有时又是Android开发者在实际工作中不得不面对的问题。掌握Binder需要投入大量精力和努力。通过一些文章的指引,是可以在学习Binder的路途上减轻一些压力,避免一些误区的。本文就希望能帮助开发者打通一些在学习Binder时可能会遇到的阻塞点,特别是这些阻塞点基本上会存在于Binder驱动层。毕竟楼再高,盖起来还是要先从地基打起,Binder驱动就是Binder这座大厦的基础。打好这个基础,往上的就都是包裹和通道了,理解起来也就会比较轻松。希望能对大家有所帮助。