深入理解Binder

深入理解Binder

Binder如何精确制导,找到Binder实体,并唤醒进程或者线程?

Binder实体服务分两种:

  1. 一是通过addService注册到ServiceManager中的服务,比如ActivityManagerService、PackageManagerService、PowerManagerService等,这些一般都是系统服务。
  2. 另一种是通过bindService拉起的一些服务,一般是开发者自己实现的服务。

理解Binder定向制导关键是理解Binder四棵红黑树,要使用Service,就需要通过getService向ServiceManager请求服务。

Client如何才能访问Service?

addService流程:

  1. Service通过addService将binder实体注册到ServiceManager,getService向ServiceManager请求服务。
  2. 请求服务时,ServiceManager会将服务相关的信息存储到自己的进程Service列表中区。
  3. 同时在ServiceManager进程的binder_ref红黑树中为Service添加binder_ref节点,ServiceManager就能获取Service的Binder实体信息。

getService流程:

  1. 当Client通过getService向ServiceManager请求该服务时,ServiceManager会在注册的Service列表中查找该服务,如果找到就将该服务返回给Client。
  2. 在这个过程中,ServiceManager会在Client进程的binder_ref红黑树中添加binder_ref节点。

总结:本进程中的binder_ref红黑树节点都不是本进程自己创建的,要么是Service进程将binder_ref插入到ServiceManager中去,要么是ServiceManager进程将binder_ref插入到Client中去。之后,Client就能通过Handle句柄获取binder_ref,进而访问Service服务。

Binder为什么只需要一次拷贝?

主要涉及两点:

  • Binder的map函数,会将内核空间直接和用户空间对应,用户空间可以直接访问内核空间的数据。
  • A进程的数据会被直接拷贝到B进程的内核空间(一次拷贝)。

当APP启动时,就开启地址映射,但并未涉及到数据的拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply){
...
在通过进行binder事务的传递时,如果一个binder事务(用struct binder_transaction结构体表示)需要使用到内存,
就会调用binder_alloc_buf函数分配此次binder事务需要的内存空间。
需要注意的是:这里是从目标进程的binder内存空间分配所需的内存
//关键点:从target进程的binder内存空间分配所需的内存大小,这也是一次拷贝,完成通信的关键,直接拷贝到目标进程的内核空间
//由于用户空间跟内核空间仅仅存在一个偏移地址,所以也算拷贝到用户空间
t->buffer = binder_alloc_buf(target_proc, tr->data_size,
tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
t->buffer->allow_user_free = 0;
t->buffer->debug_id = t->debug_id;
//该binder_buffer对应的事务
t->buffer->transaction = t;
//该事务对应的目标binder实体 ,因为目标进程中可能不仅仅有一个Binder实体
t->buffer->target_node = target_node;
trace_binder_transaction_alloc_buf(t->buffer);
if (target_node)
binder_inc_node(target_node, 1, 0, NULL);
// 计算出存放flat_binder_object结构体偏移数组的起始地址,4字节对齐。
offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));
// struct flat_binder_object是binder在进程之间传输的表示方式 //
// 这里就是完成binder通讯单边时候在用户进程同内核buffer之间的一次拷贝动作 //
// 这里的数据拷贝,其实是拷贝到目标进程中去,因为t本身就是在目标进程的内核空间中分配的,
if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
binder_user_error("binder: %d:%d got transaction with invalid "
"data ptr\n", proc->pid, thread->pid);
return_error = BR_FAILED_REPLY;
goto err_copy_data_failed;
}

当数据从用户空间拷贝的内核空间的时候,是直接从当前进程的用户空间拷贝到目标进程的内核空间,这个过程在请求端线程中处理的,操作对象是目标进程的内核空间。

一次拷贝

一次拷贝的关键点:

  1. 进程A直接写入进程B涉及到的内核空间。
  2. 进程B涉及到的内核空间和本身的用户空间做了内存映射,当内核空间改变时,用户空间也同步进行改变。

Binder传输数据的大小限制

比如在Activity之间传输BitMap的时候,如果Bitmap过大,就会引起问题,这都是和Binder传输数据大小的限制有关系。上一个问题已经说过了,mmap函数会为Binder数据传递映射一块虚拟地址,而这块地址是有大小限制的,不同进程可能还不大一样。

Binder本身就是为了进程间频繁而灵活的通信所设计的,并不是为了拷贝大数据而使用的。

  1. 普通由Zygote孵化而来的用户进程(比如App进程),所映射的Binder内存大小是不到1M的。

    1
    #define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))
  2. 内核中限制是4M

  3. 有个特殊的ServiceManager进程,只申请了128k,因为只提供addService,getService等功能,不涉及多大的数据传输,因此无需申请多大的内存。

系统服务和bindService等启动的服务区别

服务可分为系统服务和普通服务:

  • 系统服务:在系统启动时,由SystemServer进程创建并注册到ServiceManager中的。而普通服务一般是通过ActivityManagerService启动的服务。
  • 普通服务:通过ActivityManagerService启动的服务,或者通过四大组件的Service组件启动的服务。

这两种服务在使用上是有所不同的:

  • 服务的启动方式
  • 服务的注册与管理
  • 服务的请求使用方式

启动方式

  • 系统服务:一般由SystemServer进程负责启动,比如AMS,WMS,PKMS等,这个启动过程是通过ServiceManager管理
  • 普通服务:这类服务一般是通过Activity的startService或者其他context的startService启动的,这里的Service组件只是个封装,主要的是里面Binder服务实体类,这个启动过程是通过ActivityManagerService进行管理的,同Activity管理类似。

注册与管理

  • 系统服务:通过ServiceManager的addService进行注册,这些服务一般需要拥有特定的权限才能注册到ServiceManager,bindService启动的服务可以注册到ActivityManagerService。
  • 普通服务:bindService启动的服务可以算是注册到ActivityManagerService,只不过是通过ActivityManagerService进行管理,同Activity管理类似。

使用方式

  • 系统服务:通过ServiceManager的getService得到服务的句柄,这个过程是去ServiceManager中查询注册系统服务。
  • 普通服务:去AMS中查找相应的Service组件,最终会将Service内部Binder的句柄传给Client。

Binder线程,Binder主线程,Client请求线程的概念和区别

Binder线程是执行Binder服务的载体,只对于服务端才有意义。Binder线程就是执行Binder实体。

普通线程如何才能成为Binder线程?
只要开启一个监听Binder字符设备的Loop线程即可。在Android中有很多种方法,总之都是监听Binder,换成代码是通过ioctl来进行监听。

1
2
3
4
5
6
7
8
9
10
11
void binder_loop(struct binder_state *bs, binder_handler func)
{
...
for (;;) {
<!--关键点1-->
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
<!--关键点2-->
res = binder_parse(bs, 0, readbuf, bwr.read_consumed, func);
。。
}
}

关键点:

  1. 阻塞监听客户端请求
  2. 处理请求,一个死循环,不退出

实现原理:在SystemServer主线程执行到最后的时候,Loop监听Binder设备,变身死循环线程。

1
2
3
4
5
6
7
8
9
extern "C" status_t system_init()
{
...
ALOGI("System server: entering thread pool.\n");
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
ALOGI("System server: exiting thread pool.\n");
return NO_ERROR;
}

Binder请求的同步与异步

Binder对Client端同步,Service端异步

Client在请求Server端服务的过程中,是需要返回结果的,即时看不到返回数据,还是会有成功和失败的处理结果返回给Client,这就是说Client端是同步的。

在服务端被唤醒后,就去处理请求,处理结束后,服务端将结果返回给正在等待的Client线程,将结果写入到Client内核空间后,服务端就会直接返回了,不会再等待Client端确认,这就是所说的服务端是异步的。

请求同步的例子是,Android6.0之前,国产ROM权限的申请是同步的,在申请权限的时候线程会阻塞,ROM防止ANR,会给权限申请设置一个倒计时,到了时间,就给个默认操作。(这个逻辑并没有在正式版生效,所以我们看到在6.0之前并没有动态申请的逻辑)

Android APP进程天生支持Binder通信的原理是什么?

  1. ProcessState::self()函数会调用open()打开/dev/binder设备,Client能通过Binder进行远程通信
  2. 其次proc->startThreadPool()负责新建一个binder线程,监听Binder设备。

Android APP有多少Binder线程?

Binder线程的数目不是固定的

驱动会根据目标进程中是否存在足够多的Binder线程来告诉进程是不是要新建Binder线程。

也就是说,在不够用的时候,会新建Binder线程。

但是默认的Binder线程数量是有上限的,默认为15个。

具体来看,新建的条件如下:

1
2
3
4
if(proc->requested_threads + proc->ready_threads == 0 &&
proc->requested_threads_started < proc->max_threads &&
(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED)))

关键点:

  • proc->requested_threads + proc->ready_threads == 0 :如果目前还没申请新建Binder线程,并且proc->ready_threads空闲Binder线程也是0,就需要新建一个Binder线程,其实就是为了保证有至少有一个空闲的线程。
  • proc->requested_threads_started < proc->max_threads:目前启动的普通Binder线程数requested_threads_started还没达到上限(默认APP进程是15)
  1. 如果没有Client请求服务,就保持线程数不变,减少资源浪费,需要的时候再分配新线程。
  2. 有请求的情况下,保证至少有一个空闲线程时给Client端,以提高Server端的响应速度。

Client端线程睡眠在哪个队列上,唤醒Server端哪个等待队列上的线程

发送端线程睡眠在哪个队列上?

发送端线程一定睡眠在自己binder_thread的等待队列上,并且,该队列上有且只有自己一个睡眠线程

在Binder驱动去唤醒线程的时候,唤醒的是哪个等待队列上的线程?

栈顶的一定先于栈内执行。

Binder协议中BC与BR的区别

BC与BR主要是标志数据及Transaction流向,其中

  • BC是从用户空间流向内核,比如Client向Sever发送请求的时候,用的是BC_TRANSACTION
  • BR是从内核流向用户空间,当数据被写入到目标进程之后,target_proc所在的进程被唤醒,在内核空间中,会将BC转换为BR,并将数据与操作传递该用户空间。

Binder的封装与解封装流程

Binder封装

Binder驱动传递数据的释放(释放时机)

在Binder通信的过程中,数据是从发起通信进程的用户空间直接写到目标进程内核空间,而这部分数据是直接映射到用户空间,必须等用户空间使用完数据才能释放,Binder通信中内核数据的释放时机应该是用户空间控制的。

执行这个函数的命令是BC_FREE_BUFFER。
上层用户空间的入口是IPCThreadState::freeBuffer。

简单的Binder通信C/S模型

很多文章将Binder框架定义了四个角色:Server,Client,ServiceManager、以及Binder驱动,但这容易将人引导到歧途:好像所有的Binder服务都需要去ServiceManager去注册才能使用,其实不是这样。例如,平时APP开发通过bindService启动的服务,以及有些自己定义的AIDL远程调用,都不一定都ServiceManager注册这条路,个人理解:ServiceManager主要功能是:管理系统服务,比如AMS、WMS、PKMS服务等,而APP通过的bindService启动的Binder服务其实是由SystemServer的ActivityManagerService负责管理。这篇主要关注Android APP Java层Binder通信一些奇葩点:

  • ServiceManager addService的限制(并非服务都能使用ServiceManager的addService)
  • bindService启动Service与Binder服务实体的流程
  • Java层Binder实体与与BinderProxy是如何实例化及使用的,与Native层的关系是怎样的
  • Parcel readStrongBinder与writeStrongBinder的原理(首先两端知晓)
  • 数据传递参数AIDL单向与双向(inout)

ServiceManager addService的限制–并非服务都能通过addService添加到ServiceManager

大部分系统服务都是由SystemServer进程添加到ServiceManager中去的,在这个过程中,是需要校验权限的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int svc_can_register(unsigned uid, uint16_t *name)
{
unsigned n;
// 谁有权限add_service 0进程,或者 AID_SYSTEM进程
if ((uid == 0) || (uid == AID_SYSTEM))
return 1;
for (n = 0; n < sizeof(allowed) / sizeof(allowed[0]); n++)
if ((uid == allowed[n].uid) && str16eq(name, allowed[n].name))
return 1;
return 0;
} 可以看到 (uid == 0) 或者 (uid == AID_SYSTEM)的进程都是可以添加服务的,uid=0,代表root用户,而uid=AID_SYSTEM,代表系统用户 。或者是一些特殊的配置进程。SystemServer进程在被Zygote创建的时候,就被分配了UID 是AID_SYSTEM(1000),

private static boolean startSystemServer()
throws MethodAndArgsCaller, RuntimeException {
/* Hardcoded command line to start the system server */
String args[] = {
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003,3006,3007",
"--capabilities=130104352,130104352",
"--runtime-init",
"--nice-name=system_server",
"com.android.server.SystemServer",
};

可以看到,普通的进程是没有权限注册到ServiceManager中的。那么APP平时通过bindService启动的服务怎么注册于查询的呢?接管这个任务的就是SystemServer的ActivityManagerService。

bindService启动Service与Binder服务实体的流程(ActivityManagerService)

  • bindService的框架
  • binder服务实例化和转化
  • 业务逻辑的唤醒
  • 请求代理的转化和唤醒

按步骤如下:

  1. Activity调用bindService:通过Binder通知ActivityManagerService,要启动哪个Service
  2. ActivityManagerService创建ServiceRecord,并利用ApplicationThreadProxy回调,通知APP新建并启动Service启动起来
  3. ActivityManagerService把Service启动起来后,继续通过ApplicationThreadProxy,通知APP,bindService,其实就是让Service返回一个Binder对象给ActivityManagerService,以便AMS传递给Client
  4. ActivityManagerService把从Service处得到这个Binder对象传给Activity,这里是通过IServiceConnection binder实现。
  5. Activity被唤醒后通过Binder Stub的asInterface函数将Binder转换为代理Proxy,完成业务代理的转换,之后就能利用Proxy进行通信了。

bindService和startService的区别

  • 生命周期上的区别
  • bindService比startService多了一套Binder通信,多了一个C/S通信的建立流程。