Binder机制学习总结

一、为什么 Android 非要自己搞个 Binder

Linux 原生 IPC(管道、消息队列、Socket、共享内存)在手机场景都不太合适:

IPC 方式 问题
Socket 两次拷贝,开销大,身份认证弱
共享内存 性能最高但得自己管同步,编程复杂
消息队列/管道 效率低,不适合高频调用

Android 是个"服务化"系统,AMS/WMS/IMS 全是跨进程调,要求高频低延迟 + 身份可信 + 编程友好,传统 IPC 凑不出这套,所以 Binder 自己上了。

Binder 的几个设计卖点:

  • 一次拷贝(mmap 共享,而不是传统的两次)
  • 驱动级身份校验,UID/PID 由驱动塞进事务,Client 没法伪造
  • 面向对象 ,跨进程像本地调用(IInterfacetransact()/onTransact()
  • 死亡通知 + 引用计数,Server 崩了 Client 能感知

二、四层架构(从上到下)

scss 复制代码
应用层(AIDL)    Proxy(Client) ──── Stub(Server)
                android.os.*              ↑
Java框架层      Binder / ServiceManager    │ JNI
                ──────────────────────────┼──
Native层        BpBinder ────────── BBinder│ libbinder
                IPCThreadState / ProcessState
                ──────────────────────────┼──
驱动层          binder.ko (mmap / 线程池 / 红黑树)
  • 应用层 :AIDL 编译器生成 Proxy(Client 侧)和 Stub(Server 侧),开发者只碰这一层
  • Java 框架层android.os.BinderServiceManager,Proxy 把方法 id + 参数打成 Parcel,Stub 在 onTransact()里拆包分发
  • Native 层libbinderBpBinder(transact)/ BBinder(onTransact),每个进程的 ProcessState单例持有一个 Binder 设备 fd,IPCThreadState管单线程的读写循环
  • 驱动层/dev/binder,真正的"裁判",管内存映射、线程调度、binder_node/binder_ref 红黑树、UID/PID 校验

💡 ServiceManager 本身也是个 Binder Server,只是它的 handle 固定为 0------所有进程打开 binder 驱动后,先用 handle 0 跟它对话(注册/查服务),所以它相当于 Binder 世界的"DNS"。


三、驱动层两个核心机制

1. mmap:一次拷贝是怎么做到的

传统 IPC(Socket/管道):Client 用户态 → 内核态 → Server 用户态两次 copy_from_user/copy_to_user

Binder 的做法(以 Client → Server 单向为例):

  1. Server 进程在 open("/dev/binder")后调用 mmap在内核和 Server 用户空间之间映射同一块物理页(接收缓冲区)
  2. Client 调 ioctl(BINDER_WRITE_READ)发包,驱动用 copy_from_user()把 Client 用户态的 Parcel 拷进内核缓冲区 ------ 这是第 1 次拷贝
  3. 因为那块内核缓冲区跟 Server 用户空间是 mmap 共享的,Server 用户态直接就能读到 ,不需要再 copy_to_user ------ 这一段是零拷贝
  4. Server 回包时反过来:Server 往共享区写结果 → 驱动 → Client 侧 copy_to_user()取回

所以严格说:单向 1 次拷贝(Client→驱动),往返 2 次,比传统 IPC 省 1 次

⚠️ 注意 Binder 不适合传超大块数据(一般单事务 ≤ 1MB,有些厂商还更小),因为 mmap 那块缓冲区是有限的。真要传大文件走 ashmem 或 fd 传递。

为什么存在多次 copy_from_user ?

传输控制协议部分存在2次拷贝,仅核心 Parcel data 是一次拷贝。

一次拷贝指的是单程------发送方用户→内核必拷一次,接收方 mmap 直读省一次。

真正建立内存映射的只有一次,其余的主要是地址引用的copy。

2. binder_node / binder_ref:怎么表示"远程对象"

Binder 是面向对象的,Client 拿到的不是真的 Server 对象,而是引用

  • binder_node:驱动里代表 Server 侧的 Binder 实体,挂在 Server 进程的节点树里
  • binder_ref :Client 侧拿到的是"引用",驱动维护 ref → node的红黑树映射,Client 手里的 handle其实就是 ref 的编号
  • 跨进程传 Binder 对象时(比如 Service 的 onBind()返回 Binder),驱动会自动做 node/ref 转换------这才实现了"Binder 对象可以像参数一样传来传去"

这也是为什么 Binder 能当"虚拟总线"用:引用像指针,node 像对象,跨进程传引用就等价于跨进程传"指针"。

匿名binder

Android Binder框架实现之何为匿名/实名Binder_匿名binder-CSDN博客

注册到ServiceManager 的是实名binder;没有注册到ServiceManager 的是匿名binder。

匿名binder 必须借用实名 binder 来实现。

bindService 就是典型的匿名 binder .


四、一次完整 AIDL 调用的数据路径

把上一轮 AIDL 的例子对到这条链上:

arduino 复制代码
Client App                         Server App
─────────                         ─────────
proxy.someMethod(a, b)            stub.onTransact(code, data, reply, flags)
  → Parcel 打包                     ← 拆包 dispatch
  → JNI → BpBinder.transact()       → 执行业务
    → IPCThreadState::talkWithDriver  → Parcel 写回
      → ioctl(BINDER_WRITE_READ)     → 驱动通知 Client
        └─→ 进入 binder.ko ◀─────────────┐
              copy_from_user (1次)       │
              找到 target binder_node     │
              唤醒 Server 的 Binder 线程  │
                                      │
Client 阻塞等 reply ◀─────────────────── 驱动把共享区数据交给 Server
  copy_to_user (回包1次)

关键节点:

  • Client 的 transact()默认是同步的------调出去之后当前线程就挂起等 reply(所以主线程调 Binder 有可能 ANR)
  • Server 的 onTransact()跑在 Binder 线程池里,不是主线程。
  • oneway 的区别 :Client 调完 transact()不等 reply 直接返回,驱动侧事务标记为异步,Server 端走串行队列分发(这就是上轮说的"oneway 反而让 Server 串行"的根因)

五、线程模型

  • 每个开了 Binder 的进程,驱动里都有一个 Binder 线程池
  • DEFAULT_MAX_BINDER_THREADS = 15,加上 Binder 主线程 → 实际 16 个可并发处理事务
  • 线程是 spawn-on-demand 的:初始几个,事务多了驱动通知 IPCThreadState自己 pthread_create新线程,触顶 15 就停
  • system_server 自己显式调到 31,因为扛的系统服务调用多
  • 事务在驱动里排队的条件是:16 个 Binder 线程全忙 + 新事务到来 → Client 侧 transact()阻塞,直到有线程腾出来

所以上轮的结论在这里闭环了------AIDL 多进程并发不会自动阻塞,堵的永远是 Binder 线程池被打满 / stub 里自己加锁 / 等主线程


六、安全与死亡通知

  • UID/PID 由驱动在事务结构体 binder_transaction_data里塞好 ,Server 调 getCallingUid()拿到的是驱动给的,Client 改不了------这是 Binder 权限校验的根
  • 死亡通知(DeathRecipient) :Server 进程挂了,驱动会扫它所有的 binder_node,给每个还活着的引用方发 BR_DEAD_BINDER,Client 注册 linkToDeath()就能感知,常用来做 Service 重连

七、一句话串起来

AIDL 的 Proxy.transact()→ JNI → BpBinderioctlbinder.ko→ 驱动靠 mmap 共享区 + 一次 copy_from_user ​ 把 Parcel 递到 Server 的 Binder 线程 → Stub.onTransact()分发 → 回包走反向 copy_to_user。整条链路里驱动是裁判(身份/引用/线程/死亡都归它管),mmap 是性能根,线程池 16 并发是上轮阻塞问题的答案。

PS:Binder相关类介绍:

IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。

IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)。

Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。

Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。


RefBase:指针基类 IBinder 继承RefBase,IBinder.h: 申明binder的必要方法; BBinder 继承IBinder;在Server进程中,BBinder为Binder本地对象提供了抽象的进程间接口. BpBinder 继承IBinder;Binder proxy ,在Client进程中; BBinder和BpBinder的功能并不是对称的,改成BnBinder可能更形象: Binder Native。 BnInterface 继承BBinder; 在Server进程中。 BpInterface 继承BpRefBase;BpRefBase继承RefBase,为Binder代理对象提供了进程间通信的接口

asInterface 完成的是Binder到Interface的转换,具体就是:BBinder->BnInterface;BpBinder->BpInterface。 而asBinder功能则相反,具体是:BnInterface->BBinder;BpInterface->BpBinder

Binder代理对象 类型为BpBinder,在用户空间创建,且执行在Client进程中.会被Client进程中的其他对象引用,另外会引用Binder驱动程序中的Binder引用对象. Binder引用对象 类型为binder_ref,在Binder驱动程序中创建,被Binder代理对象引用. Binder实体对象 类型为binder_node,在Binder驱动程序中创建,被Binder引用对象所引用 Binder本地对象 类型为BBinder,在用户空间中创建,且执行在Server进程中.会被Server进程中其他对象引用,还会被Binder实体对象引用.

ProcessState是负责打开Binder节点并做mmap映射,IPCThreadState是负责与Binder驱动进行具体的命令交互。 getService@ServiceManagerProxy-->transact@BinderProxy-->transact@BpBinder-->transact@IPCThreadState

Binder传输最大数据:普通的由Zygote孵化而来的用户进程,所映射的Binder内存大小是不到1M的,准确说是 (1024*1024) - (4096 *2) :这个限制定义在ProcessState类中。

相关推荐
AI2中文网2 小时前
App Inventor 2 音频播放器 vs 音效 vs 视频播放器:三个媒体组件,选错了App直接卡死
android·音视频·媒体
李剑一2 小时前
uni-app x 实现本地 JSON 文件的导入导出(APP端可用)
android·前端·uni-app
喜欢打篮球的普通人2 小时前
LLVM后端指令选择
android·java·javascript
hunterandroid2 小时前
Room 数据库迁移:让本地数据升级更稳
android·前端
阿巴斯甜2 小时前
Compose NavHost的使用:
android
雁鸣零落2 小时前
浏览器扩展 CaptionGo,在网页视频上显示双语字幕,支持 PC 和手机端使用
android·chrome·edge·firefox·安卓
2501_915106323 小时前
iOS 软件测试工具性能监控、日志分析 KeyMob、Instruments等
android·ios·小程序·https·uni-app·iphone·webview
__Witheart__3 小时前
lunch选择的版型与版型对应的配置文件
android·linux
stevenzqzq4 小时前
快递盒模型
android