1. Binder 整体的架构
binder包含4部分: 我将Binder机制分为了Java Binder、Native Binder、Kernel Binder
-
四层架构:
- 应用层:AIDL接口
- 框架层:IBinder/BinderProxy
- JNI层:android_util_Binder.cpp
- 驱动层:/dev/binder
-
核心角色:
BpBinder:客户端代理(Proxy)BBinder:服务端基类(Base)ServiceManager:服务注册中心
-
注册服务(addService) :Server进程要先注册Service到ServiceManager。该过程:Server是客户端,ServiceManager是服务端。
3.2 . 获取服务(getService) :Client进程使用某个Service前,须先向ServiceManager中获取相应的Service。该过程:Client是客户端,ServiceManager是服务端。
3.3 使用服务:Client根据得到的Service信息建立与Service所在的Server进程通信的通路,然后就可以直接与Service交互。该过程:client是客户端,server是服务端。
2. Binder 机制整个过程
1).Binder 驱动的全局链表 binder_procs 中插入服务端的信息(binder_proc 结构体,每个 binder_proc 结构体中都有 todo 任务队列),然后向 ServiceManager 的 svcinfo 列表中缓存一下注册的服务。
2). 有了服务端,客户端就可以跟服务端通讯了,通讯之前需要先获取到服务,拿到服务的代理,也可以理解为引用
获取服务端的方式就是通过 ServiceManager 向 svcinfo 列表中查询一下返回服务端的代理,svcinfo 列表就是所有已注册服务的通讯录,保存了所有注册的服务信息。
3).有了服务端的引用我们就可以向服务端发送请求了,通过 BinderProxy 将我们的请求参数发送给 ServiceManager,通过共享内存的方式使用内核方法 copy_from_user() 将我们的参数先拷贝到内核空间,这时我们的客户端进入等待状态,然后 Binder 驱动向服务端的 todo 队列里面插入一条事务,执行完之后把执行结果通过 copy_to_user() 将内核的结果拷贝到用户空间(这里只是执行了拷贝命令,并没有拷贝数据,binder只进行一次拷贝),唤醒等待的客户端并把结果响应回来,这样就完成了一次通讯。
3.Binder驱动
重要的几个方法:open ,mmap ,iocontro。特别是mmap内存映射 ##3.1 open - 打开Binder驱动
作用:建立进程与Binder驱动的连接
// kernel/drivers/android/binder.c
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc = kzalloc(sizeof(*proc), GFP_KERNEL);
// 初始化关键数据结构
proc->tsk = current->group_leader;
INIT_LIST_HEAD(&proc->todo);
init_waitqueue_head(&proc->wait);
proc->default_priority = task_nice(current);
// 插入全局进程列表
hlist_add_head(&proc->proc_node, &binder_procs);
filp->private_data = proc;
}
核心操作:
- 创建
binder_proc结构体 - 初始化任务队列和等待队列
- 设置默认优先级(继承当前进程nice值)
- 加入全局进程列表
binder_procs
典型调用示例:
int fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
3.2、mmap - 内存映射(最核心机制)
作用:建立用户空间与内核的共享内存
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct binder_proc *proc = filp->private_data;
// 1. 检查映射大小(通常1M-8M)
if ((vma->vm_end - vma->vm_start) > SZ_4M)
return -ENOMEM;
// 2. 记录映射区域
proc->vma = vma;
proc->buffer = vmalloc_user(vma->vm_end - vma->vm_start);
// 3. 建立页表映射
binder_insert_free_buffer(proc, proc->buffer);
// 4. 设置内存操作回调
vma->vm_ops = &binder_vm_ops;
}
-
内存共享:
- 内核缓冲区同时映射到通信双方
- 物理页面仅一份,通过页表实现共享
void* addr = mmap(NULL, 128*1024, PROT_READ, MAP_PRIVATE, binder_fd, 0);
// 参数说明:
// - PROT_READ:只读映射(Binder特殊处理写权限)
// - MAP_PRIVATE:私有映射(但实际共享)
3.3、ioctl - 控制命令交互
作用:进程与驱动间的主要通信接口
命令分类:
| 命令类型 | 功能 | 使用频率 |
|---|---|---|
| BINDER_WRITE_READ | 读写数据(90%调用) | ⭐⭐⭐⭐⭐ |
| BINDER_SET_MAX_THREADS | 设置最大线程数 | ⭐⭐ |
| BINDER_SET_CONTEXT_MGR | 设置ServiceManager | ⭐ |
BINDER_WRITE_READ 数据结构:
struct binder_write_read {
binder_size_t write_size; // 发送数据大小
binder_size_t write_consumed;// 已消费数据
binder_uintptr_t write_buffer;// 发送缓冲区
binder_size_t read_size; // 接收缓冲区大小
binder_size_t read_consumed; // 已读取数据
binder_uintptr_t read_buffer; // 接收缓冲区
};
典型调用流程:
// 用户空间调用示例
struct binder_write_read bwr = {
.write_size = sizeof(cmd),
.write_buffer = (uintptr_t)&cmd,
.read_size = sizeof(reply),
.read_buffer = (uintptr_t)&reply
};
ioctl(fd, BINDER_WRITE_READ, &bwr);
// 驱动处理逻辑
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case BINDER_WRITE_READ:
ret = binder_ioctl_write_read(filp, cmd, arg);
break;
// 其他命令处理...
}
}
4. Binder 进程与线程
-
进程管理:
binder_proc:每个进程唯一,管理所有资源- 全局链表维护所有 Binder 进程
-
线程池:
-
默认启动 16 个 Binder 线程
-
线程状态:
BINDER_LOOPER_STATE_REGISTEREDBINDER_LOOPER_STATE_ENTERED
-
驱动通过
BR_SPAWN_LOOPER动态创建线程
-
对于底层Binder驱动,通过 binder_procs 链表记录所有创建的 binder_proc 结构体,binder 驱动层的每一个 binder_proc 结构体都与用户空间的一个用于 binder 通信的进程一一对应,且每个进程有且只有一个 ProcessState 对象,这是通过单例模式来保证的。在每个进程中可以有很多个线程,每个线程对应一个 IPCThreadState 对象,IPCThreadState 对象也是单例模式,即一个线程对应一个 IPCThreadState 对象,在 Binder 驱动层也有与之相对应的结构,那就是 Binder_thread 结构体。在 binder_proc 结构体中通过成员变量 rb_root threads,来记录当前进程内所有的 binder_thread。
Binder 线程池:每个 Server 进程在启动时创建一个 binder 线程池,并向其中注册一个 Binder 线程;之后 Server 进程也可以向 binder 线程池注册新的线程,或者 Binder 驱动在探测到没有空闲 binder 线程时主动向 Server 进程注册新的的 binder 线程。对于一个 Server 进程有一个最大 Binder 线程数限制,默认为16个 binder 线程,例如 Android 的 system_server 进程就存在16个线程。对于所有 Client 端进程的 binder 请求都是交由 Server 端进程的 binder 线程来处理的。
5.Service的注册过程
一句话总结:把service服务通过binder驱动添加到serverManager的过程
自己的总结添加服务注册的过程: 重要的是找到 servermanager, 然后调用添加service到sm
- 找到ser verManager, 通过binder驱动 (在binder驱动中),通过handler值=0找到serverManager
- 在biner驱动中添加服务-----》通过bp binder, 打开驱动, 得到了servermanager就可以添加 , 同
- 时把handler, name, 写入binder驱动
流程图:
5.1 总结:
1.addService函数将数据打包发送给BpBinder来进行处理。
2.BpBinder新建一个IPCThreadState对象,并将通信的任务交给IPCThreadState。
3.IPCThreadState的writeTransactionData函数用于将命令协议和数据写入到mOut中。
4.IPCThreadState的waitForResponse函数主要做了两件事,一件事是通过ioctl函数操作mOut和mIn来与Binder驱动进行数据交互,另一件事是处理各种命令协议。
5.2 问题1:
请问MediaPlayerService所在的进程,如何和binder驱动通信的?(重点)
MediaPlayerService是如何注册的。通过了解MediaPlayerService是如何注册的,可以得知系统服务的注册过程
1).打开binder驱动
2). 把对应的参数,封装成bpbinder序列化的对象,交给binder驱动
打开/dev/binder设备,这样的话就相当于和内核binder机制有了交互的通道
映射fd到内存,设备的fd传进去后,估计这块内存是和binder设备共享的
binder_ioctl()
5.3 问题2:MediaPlayerService和Servermanager是不同进程,如何通信的?
通过binder驱动。内核,内存映射关系。
5.4 问题2:bpbinder有什么作用?
BpBinder中的成员变量mHandler表示Binder引用对象的句柄值.封装传handle值
Bn 是Binder Native的含义,是和Bp相对的,Bp的p是proxy代理的意思,那么另一端一定有一个和代理打交道的东西,这个就是Bn。
讲到这里会有点乱喔。先分析下,到目前为止都构造出来了什么。
BpServiceManager
BnMediaPlayerService
数据流向:
具体的数据有 interfaceToken(远程服务的名称)、handle(远程服务的句柄)、cookie(本地服务的地址),
有两个结构体 flat_binder_object 和 binder_write_read。
5.5 问题4:Media 服务是如何添加到servermanager中的?
1).Media 进程是由 init 进程通过解析 init.rc 文件而创建的。
2). 最后是交给了binder 驱动的 ioctl 方法
6.ServiceManager和binder通信过程
6.1 ServiceManager 进程启动的三个阶段:
ServiceManager 进程启动的三个阶段:ServiceManager: 也可以说是守护进程
- 打开 binder 驱动:binder_open;
- 调用binder_become_context_manager函数,将servicemanager注册成为Binder机制的上下文管理者。(告诉驱动自己是serviceManager)
- 进入无限循环,binder_loop函数,循环等待和处理client端发来的请求。(循环,不停的解析和处理客户端的数据和请求)
最终会通过 binder 驱动跨进程执行 ServiceMananger 的 do_add_service 方法。
6.2 binder驱动是如何和serviceManager进行通信的?数据是如何传递的?
打开驱动,然后进行读取文件!内存映射
6.3 ServiceManager 是如何管理service信息的?
6.4 ServiceManager存在的意义
原来,Android系统中Service信息都是先add到ServiceManager中,由ServiceManager来集中管理,
这样就可以查询当前系统有哪些服务。而且,Android系统中某个服务例如MediaPlayerService的客户端想要和MediaPlayerService通讯的话,必须先向ServiceManager查询MediaPlayerService的信息,然后通过ServiceManager返回的东西再来和MediaPlayerService交互。
毕竟,要是MediaPlayerService身体不好,老是挂掉的话,客户的代码就麻烦了,就不知道后续新生的MediaPlayerService的信息了,所以只能这样:
l MediaPlayerService向SM注册
l MediaPlayerClient查询当前注册在SM中的MediaPlayerService的信息
l 根据这个信息,MediaPlayerClient和MediaPlayerService交互
另外,ServiceManager的handle标示是0,所以只要往handle是0的服务发送消息了,最终都会被传递到ServiceManager中去。
6.5.binder驱动是怎么帮我们找到servermanager进程的
handle = 0 , 驱动层会创建一个静态的变量 binder_context_mgr_node
里面有4给红黑树,存放了很多信息!
6.6.servermanager进程是怎进入等待,怎么被唤醒的?
6.5.1 我自己的理解:开始servermanager在等待,然后因为客户端往binder写入了数据,然后binder驱动往serverManager发送了
东西,之后就把它唤醒了!
等待是在 wait 队列上等,条件是 todo 队列是否为空【重点:队列】
5.5.2 客户端找到 tagert_prco 了之后把数据拷贝到目标进程,什么时候去找?这个代码在哪里?
找到了tagert_prco之后就能找到等待队列!****
同时把数据拷贝到目标进程往目标进程写入处理的命令,
然后往自己进程写入一个接受请求的命令(让自己进入等待),
最后唤醒服务进程的wait队列,这个时候服务进程被唤醒就会继续往下处理todo队列请求。
6.5.3. 客户端是怎么进入等待的
客户端是因为在请求服务端的时候驱动层会写入一个 BINDER_WORK_TRANSACTION_COMPLETE ,
然后会清空 writeData , 从新进入驱动层,进入读方法,因为todo队列里面没东西进入等待,等待服务端执行完毕唤醒客户端
6.5.4. 客户端等待响应过程分析
6.7 和serviceManger结合,整个流程
重要的是找到 servermanager, 然后调用添加service到sm 1.打开binder驱动 2.serverManager成为 Binder 驱动管理者
- 找到serverManager, 通过binder驱动 (在binder驱动中),通过handler值=0找到serverManager
- serverManager一直在等待, 等待服务注册 serverr要注册, 添加服务,binder驱动 把serverManager唤醒
- 在biner驱动中通过sm添加服务-----》通过bp binder, 打开驱动, 得到了servermanager就可以添加 , 同时把handler, name, 写入binder驱动,
7.唤醒流程图,Binder协议中BC与BR的区别
内核层内核层的通信都是通过ioctl来进行的,client打开一个ioctl,进入到轮询队列,一直阻塞直到时间到或者有消息
流程:
1.开始servermanager处于等待状态
2.然后media进程发送BP_transation
3.binder收到后发送br_transation。把servermanager唤醒,同时br_transtation_complete发送给客户端
4.客户端收到指令后,客户端处于等待的状态!
5.同上serverManager收到指令后。开始执行执行完后发送br_replay给binder驱动。同时还会发送
br_transtation_complete给servermanager。binder驱动发送br_rep给客户端唤醒
7.2 bpBinder和bBbinder
7.2.1 、BpBinder(客户端代理)
核心作用:跨进程调用的代理对象
-
远程句柄管理:
- 持有服务端的
handle(驱动分配的标识) - 示例:
mHandle=5代表ActivityManager服务
- 持有服务端的
-
请求转发机制:
// frameworks/native/libs/binder/BpBinder.cpp status_t BpBinder::transact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { IPCThreadState* ipc = IPCThreadState::self(); return ipc->transact(mHandle, code, data, reply, flags); }// Java层等价体现 public class BinderProxy implements IBinder { private final int mNativeData; // 对应native的handle }
7.2.2 、BBinder(服务端基类)
核心作用:服务实现的基类对象
```
// frameworks/native/libs/binder/Binder.cpp
status_t BBinder::transact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch(code) {
case INTERFACE_TRANSACTION:
reply->writeString16(getInterfaceDescriptor());
return NO_ERROR;
default:
return onTransact(code, data, reply, flags);
}
}
```
关键差异对比
| 特性 | BpBinder | BBinder |
|---|---|---|
| 定位 | 客户端代理 | 服务端基类 |
| 内核关联 | 无直接关联 | 对应binder_node |
| 核心方法 | 转发transact到驱动 | 实现onTransact处理请求 |
| 内存管理 | 不涉及 | 通过binder_node管理生命周期 |
| 典型子类 | BinderProxy(Java层) | AIDL生成的Stub类 |
8.读写数据
binder_thread_write 和 binder_thread_read 和 copy_from_user 和copy_to_user
8.1 、binder_thread_write - 写操作(用户→内核)
作用:处理用户空间发送的BC_*协议命令
用户空间ioctl(BINDER_WRITE_READ)
→ binder_ioctl()
→ binder_thread_write()
关键代码:
static int binder_thread_write(
struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer,
size_t size)
{
while (ptr < end) {
// 1. 从用户空间拷贝命令头
copy_from_user(&cmd, user_ptr, sizeof(uint32_t));
switch (cmd) {
case BC_TRANSACTION:
// 2. 拷贝完整事务数据
copy_from_user(&tr, user_ptr, sizeof(tr));
// 3. 处理事务
binder_transaction(proc, thread, &tr);
break;
case BC_REPLY:
// 类似处理回复...
break;
}
ptr += sizeof(cmd) + data_size;
}
}
典型处理流程:
- 从用户缓冲区读取命令码(BC_*类型)
- 根据命令类型拷贝对应数据结构
- 执行具体命令(如发起事务、回复等)
8.2 、binder_thread_read - 读操作(内核→用户)
作用:向用户空间返回BR_*协议响应
用户空间ioctl(BINDER_WRITE_READ)
→ binder_ioctl()
→ binder_thread_read()
static int binder_thread_read(
struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer,
size_t size)
{
wait_for_proc_work = thread->transaction_stack == NULL;
// 1. 从待处理队列获取工作项
binder_get_thread_work(thread, wait_for_proc_work);
while (1) {
// 2. 准备返回数据
struct binder_work *w = thread->todo.next;
switch (w->type) {
case BINDER_WORK_TRANSACTION:
// 3. 拷贝事务到用户空间
copy_to_user(user_ptr, &tr, sizeof(tr));
break;
case BINDER_WORK_RETURN_ERROR:
// 错误处理...
break;
}
}
}
典型处理流程:
- 检查线程的待处理队列(todo list)
- 根据工作项类型准备响应数据
- 通过copy_to_user返回给用户空间
8.3 、copy_from_user - 数据拷贝(用户→内核)
作用:安全地从用户空间拷贝数据
调用场景:
-
在binder_thread_write中拷贝:
- BC_*命令头
- binder_transaction_data结构体
- 附加数据(如Parcel内容)
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (access_ok(from, n)) {
// 执行实际拷贝
__copy_from_user(to, from, n);
}
return n;
}
安全机制:
- 检查用户指针有效性(access_ok)
- 处理页错误异常
- 返回未拷贝的字节数(0表示成功)
8.4 、copy_to_user - 数据拷贝(内核→用户)
作用:安全地向用户空间写入数据
-
在binder_thread_read中返回:
- BR_*响应头
- 事务处理结果
- 错误码等
9. handle回调函数详解
数据是如何传递的,handle和type是怎么被计算和管理的
binder 驱动会判断 type 是什么,然后呢会往自己的进程挂上一个 binder_node ,
会往目标进程挂上两个 binder_node 一个挂在以 handle 值为 key 的红黑树上,一个挂在以 cookie 为 key 的红黑树上,handle 值是根据红黑树的数据了累加的。
主要在: 驱动层有两个核心复杂方法 binder_thread_write 和 binder_thread_read
if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) || (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
goto fail_open;
}
// ServiceManager 进程:让 ServiceManager 进程成为管理者
int binder_become_context_manager(struct binder_state *bs)
{
return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
// ServiceManager 进程:binder 线程进入循环等待
readbuf[0] = BC_ENTER_LOOPER;
binder_write(bs, readbuf, sizeof(uint32_t));
// ServiceManager 进程:进入循环不断的读写 binder 内容
for (;;) {
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (uintptr_t) readbuf;
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
...
}
// media 进程:添加 MediaPlayerService 服务
do {
// 通过 ioctl 不停的读写操作,跟 Binder Driver 进行通信,转发给 ServiceManager 进程
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
...
} while (err == -EINTR); //当被中断,则继续执行
10. 数据结构binder_node,binder_ref,binder_proc,binder_thread
binder_proc 就是用来存放 binder 相关数据的结构体,每个进程独有一份。
10.1 、binder_proc(进程描述符)
核心作用:描述一个使用Binder的进程
struct binder_proc {
struct hlist_node proc_node; // 全局进程链表节点
struct rb_root threads; // 线程红黑树(key: pid)
struct rb_root nodes; // binder_node红黑树(key: ptr)
struct rb_root refs_by_desc; // binder_ref红黑树(key: desc)
struct rb_root refs_by_node; // binder_ref红黑树(key: node)
struct list_head todo; // 待处理事务队列
wait_queue_head_t wait; // 等待队列
struct vm_area_struct *vma; // 内存映射区域
struct mm_struct *vma_vm_mm; // 内存管理结构
pid_t pid; // 进程ID
struct task_struct *tsk; // 进程控制块
int max_threads; // 最大Binder线程数
};
典型生命周期:
-
创建:
open("/dev/binder")时初始化 -
管理:
nodes树管理本进程提供的Binder实体refs_by_desc树管理引用的远程服务
-
销毁:进程退出时清理所有资源
10.2 、binder_thread(线程描述符)
核心作用:描述一个Binder线程
struct binder_thread {
struct binder_proc *proc; // 所属进程
struct rb_node rb_node; // 进程线程树的节点
struct list_head waiting_thread_node; // 等待线程链表
int pid; // 线程ID
int looper; // 线程状态标志
struct binder_transaction *transaction_stack; // 事务栈
struct list_head todo; // 线程专属任务队列
wait_queue_head_t wait; // 等待队列
struct binder_stats stats; // 统计信息
};
关键行为:
-
事务处理:从
todo队列获取任务 -
线程休眠:无任务时在
wait队列阻塞// 驱动通知创建新线程 if (need_new_thread) { thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN; write32_cmd(BR_SPAWN_LOOPER); }
10.3 、binder_node(Binder实体)
核心作用:描述一个Binder服务实体
struct binder_node {
int debug_id; // 调试标识
struct binder_proc *proc; // 所属进程
struct hlist_node dead_node; // 死亡节点链表
struct hlist_head refs; // 所有引用的链表
int strong_refs; // 强引用计数
int weak_refs; // 弱引用计数
void __user *ptr; // 用户空间BBinder指针
void __user *cookie; // 用户空间Service对象
struct list_head async_todo; // 异步任务队列
};
引用计数规则:
- 强引用+1:
Binder.attachInterface() - 弱引用+1:
linkToDeath() - 当
strong_refs=0 && weak_refs=0时销毁节点
10.4 、binder_ref(Binder引用)
核心作用:描述对远程Binder实体的引用
struct binder_ref {
int debug_id; // 调试标识
uint32_t desc; // 引用句柄(用户可见)
struct rb_node rb_node_desc; // 按desc排序的红黑树节点
struct rb_node rb_node_node; // 按node排序的红黑树节点
struct binder_node *node; // 关联的binder_node
struct binder_proc *proc; // 引用方进程
struct hlist_node node_entry; // 节点引用链表
};
句柄分配机制:
- 动态分配:
binder_get_ref_for_node() - 范围限制:1~
INT_MAX - 特殊句柄:0固定为ServiceManager
11. Binder驱动对于binder机制的总结:
11.1 我们在使用 Binder 时基本都是调用 framework 层封装好的方法,AIDL 就是 framework 层提供的傻瓜式是使用方式。假设服务已经注册完,我们来看看客户端怎么执行服务端的方法。
11.2 首先我们通过 ServiceManager 获取到服务端的 BinderProxy 代理对象,通过调用 BinderProxy 将参数,方法标识(例如:TRANSACTION_test,AIDL中自动生成)传给 ServiceManager,同时客户端线程进入等待状态。
11.3 ServiceManager 将用户空间的参数等请求数据复制到内核空间,并向服务端插入一条执行执行方法的事务。事务执行完通知 ServiceManager 将执行结果从内核空间复制到用户空间,并唤醒等待的线程,响应结果,通讯结束。