binder_buffer管理
binder_buffer 不是“申请一段内存然后 free”的薄包装。它同时连接三种状态:allocator 的虚拟地址区间、驱动中的 binder_transaction、以及 Parcel offsets 中已经翻译过的 Binder 对象。接收方写回 BC_FREE_BUFFER(data_ptr) 后,驱动必须先确认这个地址确实属于该进程且已经允许用户释放,再解除 transaction 关联、释放 node/ref/fd 对象,推进同一 node 的下一笔 oneway work,最后才把区间交回 binder_alloc 合并。
本文面向已经读过 binder_alloc内存分配器、Binder事务发送 和 BR事务返回 的读者。BD024 解释 free tree、页和 LRU,BD017 解释 buffer 如何被写入和挂上 transaction,BD022 解释用户态何时通过 BC_FREE_BUFFER 发起回收;本篇把这些入口接成一个单 buffer 的生命周期。
本文不重新讲 best-fit 的地址选择和页 shrinker 算法,也不把 binder_transaction_buffer_release() 当作完整对象翻译教程。重点是回答:谁拥有 buffer、何时允许用户释放、失败事务和正常事务释放有什么不同、offsets 中的对象如何反向减引用,以及 oneway 队列为什么要在 buffer 真正回收后推进。
1. 三层责任
1.1 Buffer对象
源码文件:kernel/common/drivers/android/binder_alloc.h
相关类型:binder_buffer
struct binder_buffer {
struct list_head entry;
struct rb_node rb_node;
unsigned free:1;
unsigned clear_on_free:1;
unsigned allow_user_free:1;
unsigned async_transaction:1;
unsigned oneway_spam_suspect:1;
unsigned debug_id:27;
struct binder_transaction *transaction;
struct binder_node *target_node;
size_t data_size;
size_t offsets_size;
size_t extra_buffers_size;
unsigned long user_data;
int pid;
};字段属于三个消费者:user_data、free、allow_user_free 由 allocator 负责地址和释放权,transaction、target_node 由驱动事务路径负责关联和 oneway 调度,data_size、offsets_size、extra_buffers_size 由对象释放路径确定需要扫描的范围。一个字段被清零或移动,不能推断另外两层已经完成。
1.2 生命周期图
正常用户释放、事务翻译失败和 FD fixup 失败会进入对象释放并最终交回 allocator。descriptor 写入用户 read buffer 的 -EFAULT 是例外:当前分支只清理 transaction,buffer 未获得 allow_user_free,也没有立即进入 allocator free,最终由目标 proc 的 deferred release 收束。
1.3 Owner矩阵
| 阶段 | 主要 owner | 主要消费者 | 生效时机 |
|---|---|---|---|
| 地址区间 | binder_alloc | free/allocated tree | 分配或合并时 |
| 事务关联 | binder_transaction 与 buffer 互指 | binder_free_buf() | 交付、失败或用户释放时 |
| 对象引用 | offsets 中的对象 | binder_transaction_buffer_release() | buffer 结束时 |
| 用户访问权 | allow_user_free | BC_FREE_BUFFER 校验 | binder_thread_read() 写完 BR 后 |
| oneway 顺序 | target_node->async_todo | binder_free_buf() | 当前 async buffer 释放后 |
2. 分配挂接
2.1 外层入口
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_alloc_new_buf()
if (!binder_alloc_is_mapped(alloc))
return ERR_PTR(-ESRCH);
size = sanitized_size(data_size,
offsets_size, extra_buffers_size);
if (unlikely(!size))
return ERR_PTR(-EINVAL);
next = kzalloc(sizeof(*next), GFP_KERNEL);
if (!next)
return ERR_PTR(-ENOMEM);
mutex_lock(&alloc->mutex);
buffer = binder_alloc_new_buf_locked(
alloc, next, size, is_async);allocator 先检查 VMA 状态和长度,再预分配一个可能用于拆分剩余区间的 binder_buffer 元数据。这里的 next 不是事务 buffer;它只有在大 free 区间被拆分时才成为剩余 free 节点,否则在锁内尾部被释放。
2.2 元数据写入
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_alloc_new_buf()
buffer->data_size = data_size;
buffer->offsets_size = offsets_size;
buffer->extra_buffers_size = extra_buffers_size;
buffer->pid = current->tgid;
mutex_unlock(&alloc->mutex);
ret = binder_install_buffer_pages(
alloc, buffer, size);
if (ret) {
binder_alloc_free_buf(alloc, buffer);
buffer = ERR_PTR(ret);
}树状态先变成 allocated,页安装在 mutex 解锁后进行。如果页安装失败,调用 allocator free 回滚区间;因此调用者看到失败时不能假设“已经返回的 buffer 仍在 allocated tree”。
2.3 事务互指
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_transaction()
t->buffer = binder_alloc_new_buf(
&target_proc->alloc,
tr->data_size,
tr->offsets_size,
extra_buffers_size,
!reply && (tr->flags & TF_ONE_WAY));
if (IS_ERR(t->buffer)) {
t->buffer = NULL;
return_error = BR_FAILED_REPLY;
goto err_binder_alloc_buf_failed;
}
t->buffer->transaction = t;
t->buffer->target_node = target_node;buffer->transaction = t 建立的是驱动内部反向索引:释放 buffer 时可以把仍指向它的 transaction 置空;t->buffer = buffer 则让失败标签和交付路径找到 allocator 对象。 target_node 不只是调试字段,异步 buffer 释放时要用它推进该 node 的 async_todo。
3. 交付门槛
3.1 未交付状态
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
if (put_user(cmd,
(uint32_t __user *)ptr)) {
binder_cleanup_transaction(
t, "put_user failed",
BR_FAILED_REPLY);
return -EFAULT;
}
ptr += sizeof(uint32_t);
if (copy_to_user(ptr, &tr, trsize)) {
binder_cleanup_transaction(
t, "copy_to_user failed",
BR_FAILED_REPLY);
return -EFAULT;
}descriptor 写入失败时,work 已从队列取出但用户尚未获得 buffer 地址。驱动调用 binder_cleanup_transaction(),最终由 binder_free_transaction() 清空 buffer->transaction;这个分支没有调用 binder_free_buf(),也不会设置 allow_user_free。因此用户态无法回收该地址,buffer 最终由目标 proc 的 deferred release 清理,而不是在 -EFAULT 返回点立即回到 free tree。
3.2 交付后状态
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
t->buffer->allow_user_free = 1;
if (cmd != BR_REPLY &&
!(t->flags & TF_ONE_WAY)) {
binder_inner_proc_lock(thread->proc);
t->to_parent = thread->transaction_stack;
t->to_thread = thread;
thread->transaction_stack = t;
binder_inner_proc_unlock(thread->proc);
} else {
binder_free_transaction(t);
}BR 写入成功后,buffer 才允许用户用地址回收。同步请求的 transaction 对象继续留在接收线程 stack,等待服务端发送 BC_REPLY;BR_REPLY 和 oneway 的 transaction 对象可以先释放,但 buffer 仍由用户 Parcel 持有,直到 BC_FREE_BUFFER。
4. 释放校验
4.1 BC入口
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_write() 中 BC_FREE_BUFFER
case BC_FREE_BUFFER: {
binder_uintptr_t data_ptr;
struct binder_buffer *buffer;
if (get_user(data_ptr,
(binder_uintptr_t __user *)ptr))
return -EFAULT;
ptr += sizeof(binder_uintptr_t);
buffer = binder_alloc_prepare_to_free(
&proc->alloc, data_ptr);
if (IS_ERR_OR_NULL(buffer)) {
if (PTR_ERR(buffer) == -EPERM)
binder_user_error(
"BC_FREE_BUFFER matched unreturned buffer");
else
binder_user_error(
"BC_FREE_BUFFER no match");
break;
}
binder_free_buf(proc, thread, buffer, false);
break;
}BC 命令只携带一个用户指针。驱动不信任这个指针对应的内容,而是把它交给 allocator 的 allocated tree 做精确地址查找;找不到和“找到了但尚未允许用户释放”是两个不同错误。
4.2 一次性权限
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_alloc_prepare_to_free_locked()
if (user_ptr < buffer->user_data)
n = n->rb_left;
else if (user_ptr > buffer->user_data)
n = n->rb_right;
else {
if (!buffer->allow_user_free)
return ERR_PTR(-EPERM);
buffer->allow_user_free = 0;
return buffer;
}成功匹配会立即把 allow_user_free 清零,再返回 buffer。这个顺序防止同一进程的两个线程同时提交相同地址时都通过校验:第一个线程拿到释放权,第二个线程看到 -EPERM。真正的区间回收稍后才发生,但权限已经被消费。
4.3 错误不变量
BC_FREE_BUFFER 的地址必须是 buffer->user_data 的起始地址,而不是区间中间任意地址;必须来自当前接收进程的 Binder VMA,而不是发送方地址;必须已经由 binder_thread_read() 交付。错误地址不会触发对象扫描和 allocator 合并。
5. 驱动回收
5.1 断开事务
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_free_buf()
binder_inner_proc_lock(proc);
if (buffer->transaction) {
buffer->transaction->buffer = NULL;
buffer->transaction = NULL;
has_transaction = true;
}
binder_inner_proc_unlock(proc);释放入口先断开 buffer 与 transaction 的互指。 has_transaction 会传给 trace 和后续释放逻辑,说明这次回收是否还发现一个活动 transaction;这一步不等于释放 transaction 对象本身,transaction 可能已经由其他路径处理。
5.2 Oneway推进
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_free_buf()
if (buffer->async_transaction &&
buffer->target_node) {
struct binder_node *buf_node =
buffer->target_node;
struct binder_work *w;
binder_node_inner_lock(buf_node);
BUG_ON(!buf_node->has_async_transaction);
BUG_ON(buf_node->proc != proc);
w = binder_dequeue_work_head_ilocked(
&buf_node->async_todo);
if (!w) {
buf_node->has_async_transaction = false;
} else {
binder_enqueue_work_ilocked(
w, &proc->todo);
binder_wakeup_proc_ilocked(proc);
}
binder_node_inner_unlock(buf_node);
}同一 node 的 async buffer 释放后,驱动才从 async_todo 取下一笔 work。没有下一笔时清除 has_async_transaction;有下一笔时把它放入目标 proc 的 todo 并唤醒消费者。这个推进发生在 allocator 合并前,保证“当前 buffer 释放”是 oneway 串行化的生效点。
5.3 释放总入口
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_free_buf()
trace_android_vh_binder_buffer_release(
proc, thread, buffer, has_transaction);
trace_binder_transaction_buffer_release(buffer);
binder_release_entire_buffer(
proc, thread, buffer, is_failure);
binder_alloc_free_buf(&proc->alloc, buffer);在这段尾部之前,函数已经断开 transaction 并推进 async work;随后固定为 tracing hook → 扫描 offsets 中的 Binder 对象 → allocator 回收区间。若把 allocator free 提前,后续 binder_alloc_copy_from_buffer() 将可能读取已经被重新分配或回收的地址。
6. 对象释放
6.1 扫描范围
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_release_entire_buffer()、binder_transaction_buffer_release()
off_end_offset = ALIGN(
buffer->data_size, sizeof(void *));
off_end_offset += buffer->offsets_size;
binder_transaction_buffer_release(
proc, thread, buffer,
off_end_offset, is_failure);offsets 数组位于 data 对齐后的尾部,off_end_offset 只覆盖对象偏移数组,不扫描 extra_buffers_size。额外空间可能保存 SG 或安全上下文等数据,它们由各自的释放路径处理;不能把整个 allocator 区间长度当作对象偏移表长度。
6.2 目标节点
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_transaction_buffer_release()
if (buffer->target_node)
binder_dec_node(
buffer->target_node, 1, 0);
for (buffer_offset = off_start_offset;
buffer_offset < off_end_offset;
buffer_offset += sizeof(binder_size_t)) {
binder_size_t object_offset;
struct binder_object object;
if (!binder_alloc_copy_from_buffer(
&proc->alloc, &object_offset,
buffer, buffer_offset,
sizeof(object_offset)))
object_size = binder_get_object(
proc, NULL, buffer,
object_offset, &object);target_node 是 buffer 目标 node 的临时强引用,先于 offsets 对象扫描递减。随后驱动从接收进程自己的 Binder buffer 读取每个 offset,再验证对象头;对象偏移损坏时记录错误并跳过该对象,不会盲目按用户提供的长度解引用。
6.3 Binder对象
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_transaction_buffer_release()
switch (hdr->type) {
case BINDER_TYPE_BINDER:
case BINDER_TYPE_WEAK_BINDER: {
struct flat_binder_object *fp =
to_flat_binder_object(hdr);
struct binder_node *node =
binder_get_node(proc, fp->binder);
if (node) {
binder_dec_node(node,
hdr->type == BINDER_TYPE_BINDER,
0);
binder_put_node(node);
}
} break;
case BINDER_TYPE_HANDLE:
case BINDER_TYPE_WEAK_HANDLE: {
struct flat_binder_object *fp =
to_flat_binder_object(hdr);
binder_dec_ref_for_handle(
proc, fp->handle,
hdr->type == BINDER_TYPE_HANDLE,
&rdata);
} break;
}本地 Binder 对象通过 node 指针减引用,远程 handle 对象通过当前 proc 的 descriptor 减引用。释放 buffer 不会“释放一个全局 Binder 对象”:它只撤销这次 Parcel 在目标 proc 上增加的 node/ref 边沿,最终 node/ref 是否销毁还取决于其它引用和附件。
6.4 FD数组
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_transaction_buffer_release()
case BINDER_TYPE_FD:
/* 成功交付的 fd 由用户态关闭。 */
break;
case BINDER_TYPE_FDA:
if (is_failure)
continue;
/* 校验 parent 和 fd 数量后读取目标 buffer 中的 fd。 */
for (fd_index = 0;
fd_index < fda->num_fds;
fd_index++) {
binder_alloc_copy_from_buffer(
&proc->alloc, &fd, buffer,
offset, sizeof(fd));
binder_deferred_fd_close(fd);
}
break;成功交付后,用户态 Parcel 获得目标进程的 fd,驱动释放 buffer 时不替用户关闭这些 fd。未交付失败路径则可能没有安装目标 fd;is_failure 为真时跳过 FDA 关闭扫描,避免把尚未真正交给用户的编号当作已安装 fd。
7. 地址合并
7.1 进入allocator
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_alloc_free_buf()、binder_free_buf_locked()
if (buffer->clear_on_free) {
binder_alloc_clear_buf(alloc, buffer);
buffer->clear_on_free = false;
}
mutex_lock(&alloc->mutex);
binder_free_buf_locked(alloc, buffer);
mutex_unlock(&alloc->mutex);清零在 allocator mutex 外进行,随后锁内才改变树和链表。 binder_free_buf_locked() 要求 buffer 已不再关联 transaction,并把它从 allocated tree 移入 free tree;如果上层漏掉 transaction 清理,allocator 会通过 BUG_ON(buffer->transaction != NULL) 暴露不变量破坏。
7.2 相邻判断
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_free_buf_locked()、binder_buffer_next()、binder_buffer_prev()
if (!list_is_last(&buffer->entry,
&alloc->buffers)) {
struct binder_buffer *next =
binder_buffer_next(buffer);
if (next->free) {
rb_erase(&next->rb_node,
&alloc->free_buffers);
binder_delete_free_buffer(
alloc, next);
}
}
if (alloc->buffers.next != &buffer->entry) {
struct binder_buffer *prev =
binder_buffer_prev(buffer);
if (prev->free) {
binder_delete_free_buffer(
alloc, buffer);
rb_erase(&prev->rb_node,
&alloc->free_buffers);
buffer = prev;
}
}free tree 按大小排序,不能用它判断虚拟地址相邻。驱动必须通过 buffers 链表找前后邻居,先吸收 next,再吸收 prev,最后把合并后的一个节点重新插回 free tree。合并的是元数据和连续虚拟区间,不是立即释放其中所有 page。
7.3 页状态
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_lru_freelist_add()、binder_delete_free_buffer()
binder_lru_freelist_add(
alloc, PAGE_ALIGN(buffer->user_data),
(buffer->user_data + buffer_size)
& PAGE_MASK);只有不再被活动邻居使用的页才加入 LRU。一个未对齐 buffer 的首尾页可能与相邻活动 buffer 共享,所以区间 free、free tree 合并和 page LRU 入队不能简单按同一整段地址处理。
8. 失败与取消
8.1 页安装失败
相关源码:
kernel/common/drivers/android/binder.ckernel/common/drivers/android/binder_alloc.c
相关函数:binder_transaction()、binder_alloc_new_buf()
ret = binder_install_buffer_pages(
alloc, buffer, size);
if (ret) {
binder_alloc_free_buf(alloc, buffer);
buffer = ERR_PTR(ret);
}页安装失败发生在 buffer 已进入 allocated tree 之后,因此回滚必须走完整 allocator free;不能只 kfree(buffer),否则 free tree、buffers 链表、async 配额和已进入 LRU 的页都会失真。
8.2 事务翻译失败
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_transaction() 的 err_translate_failed 路径
binder_cleanup_deferred_txn_lists(
&sgc_head, &pf_head);
binder_free_txn_fixups(t);
binder_transaction_buffer_release(
target_proc, NULL, t->buffer,
buffer_offset, true);
t->buffer->transaction = NULL;
binder_alloc_free_buf(
&target_proc->alloc, t->buffer);对象、父子 pointer、FD 或安全上下文翻译失败时,驱动只释放已经处理到 buffer_offset 的对象范围,并把 is_failure 传给 buffer release。这样未完成的 FD fixup 不会被当成已安装 fd 关闭,已经增加的 node/ref 则会被反向减回。
8.3 写入失败
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_cleanup_transaction()、binder_free_transaction()、binder_alloc_deferred_release()
if (t->buffer)
t->buffer->transaction = NULL;
binder_free_txn_fixups(t);
kfree(t);descriptor put_user()/copy_to_user() 失败会走这段 transaction 清理,但 buffer 区间仍在 allocated_buffers。目标进程释放 Binder fd 后,allocator deferred release 遍历所有残留 allocated buffer,断言 transaction 已清空,再执行对象和区间清理。这个边界说明 ioctl -EFAULT 不是一个可以重试同一 BR descriptor 的正常协议结果。
8.4 用户取消
Binder buffer 没有一个独立的“用户取消 buffer”命令。同步调用被取消、目标进程死亡或 descriptor 写失败,最终都通过驱动错误路径找到 buffer 并释放;已经交付给用户态的 buffer 则必须由 Parcel 的 freeBuffer() 写 BC_FREE_BUFFER。因此“取消 transact”不等于“任意时刻都能直接释放目标 buffer”。
9. 测试边界
9.1 地址和页
源码文件:kernel/common/drivers/android/tests/binder_alloc_kunit.c
相关测试:binder_alloc_exhaustive_test()
failures = binder_alloc_test_alloc_buf(
test, alloc, buffers,
tc->buffer_sizes,
tc->free_sequence);
KUNIT_EXPECT_EQ_MSG(test, failures, 0,
"Initial allocation failed");
failures = binder_alloc_test_free_buf(
test, alloc, buffers,
tc->buffer_sizes,
tc->free_sequence, end);
KUNIT_EXPECT_EQ_MSG(test, failures, 0,
"buffers not freed correctly");
failures = binder_alloc_test_free_page(
test, alloc);
KUNIT_EXPECT_EQ_MSG(test, failures, 0,
"pages still installed");测试改变 buffer 的对齐布局和释放排列,验证 allocator 层的区间、邻居、页 LRU 和最终 page free;它不验证 binder_transaction_buffer_release() 中的 Binder object、FDA 或 transaction stack 语义。
9.2 用户释放
源码文件:frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp
相关测试:BinderDriverInterfaceTest.Transaction
EXPECT_EQ(BR_REPLY, br.cmd2);
bc2.cmd1 = BC_FREE_BUFFER;
bc2.arg1 = br.arg2.data.ptr.buffer;
bwr.write_buffer = (uintptr_t)&bc2;
bwr.write_size = sizeof(bc2);
binderTestIoctl(BINDER_WRITE_READ, &bwr);
EXPECT_EQ(sizeof(bc2), bwr.write_consumed);测试先取到 reply descriptor 中的 data.ptr.buffer,再把同一个地址作为 BC_FREE_BUFFER 输入。关键断言是命令被完整消费并且后续 read 为空;它验证用户地址回收入口,不证明非法地址、重复释放和包含 Binder 对象的 buffer 都会以同样日志表现。
9.3 Oneway顺序
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
相关测试:BinderLibTest.OnewayQueueing
测试让第一笔 oneway callback 延迟,再发送第二笔 oneway,使第二笔进入单线程 server 的 async 队列;两个 callback 都必须按预期完成。它间接覆盖 target_node->async_todo 和释放后推进的可观察结果,但不把测试结果扩大成所有线程数、所有 node 或所有 vendor hook 下的公平性保证。
10. 源码导航
排查一个 buffer “无法释放”或“释放后空间没有恢复”时,可以按下面顺序阅读:
- 从
binder_thread_write()确认收到的地址是否是BC_FREE_BUFFER,以及binder_alloc_prepare_to_free()返回 NULL 还是-EPERM; - 看
buffer->allow_user_free和buffer->transaction,区分尚未交付、已交付未回收、正在释放三种状态; - 进入
binder_free_buf(),确认 transaction 互指是否断开、async node 是否推进; - 进入
binder_release_entire_buffer(),检查 offsets 范围和is_failure,再检查 node/ref/FDA 反向清理; - 最后看
binder_alloc_free_buf()的 free tree 合并和页 LRU,而不是只看kfree(buffer); - 若是重复释放,回到一次性
allow_user_free校验;若是空间不连续,检查buffers链表邻居是否已经合并。
源码搜索:
rg -n "BC_FREE_BUFFER|binder_alloc_prepare_to_free|binder_free_buf\\(" \
kernel/common/drivers/android/binder.c \
kernel/common/drivers/android/binder_alloc.c
rg -n "binder_transaction_buffer_release|binder_release_entire_buffer|BINDER_TYPE_FDA" \
kernel/common/drivers/android/binder.c
rg -n "binder_free_buf_locked|binder_delete_free_buffer|allow_user_free" \
kernel/common/drivers/android/binder_alloc.c \
kernel/common/drivers/android/binder_alloc.h
rg -n "Transaction|OnewayQueueing|BC_FREE_BUFFER" \
frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp \
frameworks/native/libs/binder/tests/binderLibTest.cpp如果能解释“为什么 BC_FREE_BUFFER 先做一次性权限消费”“为什么对象释放必须发生在 allocator 合并之前”“为什么 oneway 下一笔 work 要等当前 buffer 释放”“为什么失败翻译只扫描到 buffer_offset”,就已经掌握了 binder_buffer 作为地址、事务和对象引用交汇点的真实生命周期。
