如果把 Android 系统比作一个 "城市",进程就是 "公司",进程间通信(IPC)就是 "公司间寄快递"。但 Android 里的 "快递" 有个规矩 ------公司不能直接串门送件 ,必须通过一个 "官方快递站" 中转,这个快递站就是 Binder 驱动。
而 "公开 Binder" 和 "匿名 Binder",就是快递站里两种不同的 "快递柜":
- 公开 Binder:类似 "社区公共快递柜",有固定编号(比如 0 号柜),全城市都知道它是 "服务管理柜",谁都能查、能用;
- 匿名 Binder:类似 "临时专属柜",没有公开编号,只有寄件方和收件方知道密码(handle),用完就可能回收。
下面用 "快递站运营日记" 结合源码,带你吃透这两种柜子的 "开户 + 使用" 过程,最后附时序图。
一、先搞懂快递站的 "基础规则"(Binder 核心结构)
在讲流程前,先认识快递站里的 3 个核心 "道具",后面全靠它们:
结构名(源码里的定义) | 类比成快递站的东西 | 作用 |
---|---|---|
binder_proc |
公司在快递站的 "账户" | 每个进程(公司)打开快递站(open /dev/binder )时,驱动会创建一个 binder_proc ,记录这个进程的信息(比如持有的快递柜列表、待处理的快递等) |
binder_node |
物理快递柜(实体) | 真正存 "快递" 的地方,每个 Binder 实体对应一个 binder_node ,不管是公开还是匿名 Binder,本质都是这个结构 |
binder_handle |
快递柜的 "取件码" | 进程操作 binder_node 时,不能直接用 node 地址(驱动不暴露内核地址),而是用 handle (一个整数,比如 0、123),每个进程的 handle 只对自己有效 |
二、公开 Binder:全城市都知道的 "0 号公共柜"(以 ServiceManager 为例)
公开 Binder 的核心是 "有固定身份、全进程可查",最典型的就是 ServiceManager(SM) ------ 它是 Android 里的 "服务大管家",所有系统服务(比如 ActivityManager、WindowManager)都要向它注册,其他进程要调用服务,必须先找 SM 要 "服务地址"。
而 SM 能当 "大管家",关键是它占用了 Binder 驱动的 "0 号公开柜"。下面看这个柜子是怎么 "上架" 和 "被使用" 的。
故事线:快递站开业,SM 抢注 "0 号柜"
1. 第一步:快递站开门(驱动初始化)
系统启动时,内核会加载 Binder 驱动(类似 "快递站装修开业"),创建一个设备文件 /dev/binder
------ 这是所有进程和快递站打交道的 "唯一入口"(不管是公开还是匿名 Binder,都走这个入口)。
2. 第二步:SM 来开 "账户"(open /dev/binder
)
SM 是 Android 早期启动的进程(由 init
进程启动),它启动后第一件事就是 "去快递站开账户",对应代码就是调用 open("/dev/binder", O_RDWR)
。
这时 Binder 驱动会执行 binder_open
函数(源码路径:kernel/drivers/android/binder.c
),核心逻辑是:
c
static int binder_open(struct inode *inode, struct file *filp) {
// 1. 为当前进程(SM)创建一个"账户":binder_proc
struct binder_proc *proc = kzalloc(sizeof(*proc), GFP_KERNEL);
if (!proc) return -ENOMEM;
// 2. 初始化账户信息:绑定进程 PID、初始化待处理队列等
proc->pid = current->tgid; // current 是当前进程的内核结构体
INIT_LIST_HEAD(&proc->todo); // 待处理的"快递"队列
init_waitqueue_head(&proc->wait); // 等待队列(没快递时进程休眠)
// 3. 把"账户"挂到文件描述符上(后续操作都通过这个 fd 找 proc)
filp->private_data = proc;
return 0;
}
解读 :SM 调用 open
后,就在快递站有了专属 "账户"(binder_proc
),拿到了一个文件描述符(fd),后续所有操作(比如抢 0 号柜)都靠这个 fd 跟快递站沟通。
3. 第三步:SM 抢注 "0 号公开柜"(ioctl(BINDER_SET_CONTEXT_MGR)
)
有了账户后,SM 要告诉快递站:"我要当大管家,把 0 号柜分给我!",对应代码是调用 ioctl(fd, BINDER_SET_CONTEXT_MGR, 0)
。
驱动会执行 binder_ioctl
函数(核心处理逻辑),针对 BINDER_SET_CONTEXT_MGR
的分支:
c
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
struct binder_proc *proc = filp->private_data; // 拿到 SM 的"账户"
void __user *ubuf = (void __user *)arg;
switch (cmd) {
case BINDER_SET_CONTEXT_MGR: {
// 1. 检查:只有没被抢注过,才能设置(保证 0 号柜唯一)
if (binder_context_mgr_node != NULL) {
return -EBUSY; // 已经有人占了,返回"忙"
}
// 2. 为 SM 创建"物理快递柜":binder_node
struct binder_node *node = binder_node_alloc(proc);
node->flags = BINDER_NODE_FLAG_CONTEXT_MGR; // 标记为"大管家柜"
// 3. 全局登记:把这个 node 设为"公开大管家柜"
binder_context_mgr_node = node;
// 4. 给 SM 分配"取件码":0(固定!)
struct binder_handle *handle = binder_handle_alloc(proc, node);
handle->desc = 0; // desc 就是用户进程看到的 handle
// 5. 把取件码 0 返回给 SM(用户进程)
if (copy_to_user(ubuf, &handle->desc, sizeof(handle->desc))) {
return -EFAULT;
}
break;
}
// 其他分支...
}
return 0;
}
关键结论:
- 0 号 handle 是 公开、唯一 的,只有 SM 能拿到;
- 全系统的进程都知道:"要找 SM,就用 handle 0"。
4. 第四步:普通 APP 找 SM(获取 0 号 handle)
比如 "小程 APP" 要调用 "电话服务",第一步得先找 SM 问 "电话服务的 handle 是多少"。它不需要抢注,直接拿 0 号 handle 即可:
- 小程 APP 调用
open("/dev/binder", O_RDWR)
,驱动为它创建一个binder_proc
(自己的账户); - 小程调用
ioctl(fd, BINDER_GET_CONTEXT_MGR, &handle)
(实际是通过BINDER_IOCTL_TRANSACT
发送查询请求); - 驱动看到 "要找大管家",直接返回
handle = 0
(因为binder_context_mgr_node
已经存在); - 小程拿到 0 号 handle 后,就可以用它跟 SM 通信了(比如发送 "查询电话服务" 的请求)。
公开 Binder 核心特点
- 有固定标识:以 SM 为例,用固定 handle 0 作为公开入口;
- 全进程可见 :任何进程只要打开
/dev/binder
,都能获取到这个公开 handle; - 用途:用于 "全局服务注册 / 查询"(比如系统服务、第三方 SDK 服务)。
三、匿名 Binder:只有两人知道的 "临时柜"
如果 "小程 APP" 要给 "小李 APP" 发私消息(比如跨进程传递一个 Service
实例),不想用公开的 0 号柜(没必要,也不安全),就需要 "匿名 Binder"------ 类似 "临时专属柜",只有小程和小李知道取件码。
故事线:小程给小李寄 "私货",临时开柜
1. 前提:小程和小李都有快递站账户
小程和小李都已经调用过 open("/dev/binder", O_RDWR)
,各自有自己的 binder_proc
(账户)。
2. 第二步:小程申请 "临时柜"(驱动自动创建匿名 node)
小程要给小李发一个 Binder
对象(比如 MyService
),它不需要主动调用 "创建匿名 Binder" 的接口,而是在发送数据时,驱动会自动创建匿名柜:
- 小程构造一个 "快递包裹"(
flat_binder_object
,Binder 通信的核心数据结构),里面指定type = BINDER_TYPE_BINDER
(表示 "这是一个要传递的 Binder 实体"); - 小程调用
ioctl(fd, BINDER_IOCTL_TRANSACT, &transact_data)
,把包裹发给小李(目标进程是小李的 PID); - 驱动在
binder_transaction
函数(处理传递逻辑)中,发现包裹是BINDER_TYPE_BINDER
,开始创建匿名柜:
c
static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread,
struct binder_transaction_data *tr, int reply) {
// ... 其他逻辑 ...
// 检查包裹类型:如果是要传递 Binder 实体,创建匿名 node
if (tr->data_type == BINDER_TYPE_BINDER) {
struct flat_binder_object *obj = (struct flat_binder_object *)tr->data.ptr.buffer;
// 1. 为小程(发送方)创建"匿名物理柜":binder_node
struct binder_node *node = binder_node_alloc(proc);
node->flags = 0; // 不标记为公开,就是匿名
// 2. 给小程分配"取件码"(比如 123,只在小程的账户里有效)
struct binder_handle *sender_handle = binder_handle_alloc(proc, node);
sender_handle->desc = 123; // 小程的 handle 是 123
// 3. 给小李(接收方)分配"对应的取件码"(比如 456,只在小李的账户里有效)
struct binder_handle *receiver_handle = binder_handle_alloc(target_proc, node);
receiver_handle->desc = 456; // 小李的 handle 是 456
// 4. 把小李的取件码 456 放进包裹,发给小李
obj->handle = receiver_handle->desc;
obj->type = BINDER_TYPE_HANDLE; // 接收方看到的是"handle",不是实体
}
// ... 把包裹发给小李的"待处理队列" ...
}
解读:
- 匿名 Binder 没有 "公开名字",是驱动在 "传递实体" 时自动创建的;
- 小程的 handle(123)和小李的 handle(456)不一样,但指向同一个物理柜(binder_node) ------ 就像同一把锁,小程有钥匙 A,小李有钥匙 B,都能开同一个柜子。
3. 第三步:小李用 "临时柜" 回复小程
小李收到包裹后,里面有 handle 456,它就知道:"这是小程给我开的临时柜,用 456 就能跟小程通信"。
之后小李要给小程发回复时,只需要在 ioctl
中指定 handle = 456
,驱动会根据小李的 binder_proc
找到对应的 binder_node
,再找到小程的 binder_proc
,最终把消息发给小程。
4. 第四步:临时柜的回收
当小程和小李都不再使用这个匿名 Binder(比如进程退出、释放 handle),驱动会检测到 binder_node
没有任何进程引用了,就会自动回收这个 "物理柜"(释放 binder_node
内存)------ 类似 "快递柜超时没人用,自动清空回收"。
匿名 Binder 核心特点
- 无公开标识:没有固定名字和 handle,靠 "进程间传递" 共享;
- 隐私性:只有参与通信的进程知道 handle(其他进程拿不到);
- 用途 :用于 "进程间临时通信"(比如跨进程传递
Service
、Activity
间传递Binder
对象)。
四、时序图:直观看流程
用 Mermaid 时序图,把上面的故事转化为 "技术步骤",小白能直接对照看。
1. 公开 Binder 时序图(SM 注册 + APP 获取 0 号 handle)

init进程Binder驱动普通APP进程SM进程init进程Binder驱动普通APP进程SM进程启动SM进程open("/dev/binder") // 开账户创建SM的binder_proc返回fd(账户凭证)ioctl(fd, BINDER_SET_CONTEXT_MGR)1. 创建binder_node(公开柜)2. 分配handle=03. 全局登记binder_context_mgr_node返回handle=0open("/dev/binder") // 开自己的账户创建APP的binder_proc返回fdioctl(fd, 查SM的handle)读取binder_context_mgr_node返回handle=0
2. 匿名 Binder 时序图(小程→小李传递匿名 Binder)

小李APP小程APP小李APP(接收方)Binder驱动小程APP(发送方)小李APP小程APP小李APP(接收方)Binder驱动小程APP(发送方)open("/dev/binder")创建小程的binder_proc返回fd1open("/dev/binder")创建小李的binder_proc返回fd2ioctl(fd1, BINDER_IOCTL_TRANSACT, 包裹(type=BINDER_TYPE_BINDER))1. 创建匿名binder_node2. 给小程分配handle=1233. 给小李分配handle=4564. 包裹改type=BINDER_TYPE_HANDLE,填handle=456把包裹放进小李的todo队列返回"发送成功"ioctl(fd2, 读取todo队列)返回包裹(含handle=456)ioctl(fd2, BINDER_IOCTL_TRANSACT, 回复包裹(handle=456))通过handle=456找到binder_node,再找到小程的handle=123把回复放进小程的todo队列返回"回复成功"
五、一句话总结:公开 vs 匿名 Binder
对比维度 | 公开 Binder(如 SM) | 匿名 Binder(如跨进程传递 Service) |
---|---|---|
标识方式 | 固定 handle(如 0)+ 公开名字 | 临时 handle(进程内唯一)+ 传递共享 |
创建时机 | 主动调用 BINDER_SET_CONTEXT_MGR |
被动:驱动在传递 Binder 实体时自动创建 |
可见范围 | 全系统进程可见 | 仅参与通信的进程可见 |
核心用途 | 全局服务注册 / 查询 | 进程间临时通信 |
关键代码分支 | binder_ioctl → BINDER_SET_CONTEXT_MGR |
binder_transaction → 处理 BINDER_TYPE_BINDER |
记住:不管是公开还是匿名 Binder,都是通过 /dev/binder
这个 "唯一入口" 跟驱动交互,核心区别在于 "是否有公开标识" 和 "创建方式"------ 就像快递站的两种柜子,一个是全社区共用,一个是临时专属。