Binder内存映射
Binder 的 transaction buffer 不是在每次发送时临时 malloc 一块内核内存,再复制到接收进程的 另一块用户内存。接收进程先通过 mmap() 给驱动一段 VMA,驱动把这段地址记录在 binder_alloc 中;后续事务申请 buffer 时,驱动在这段地址对应的位置安装物理页,并把发送方 的数据复制到这些页。接收线程最终读取的就是自己地址空间里的那段 Binder buffer。
本文面向已经读过 Binder设备打开、事务数据生命周期 和 Binder数据转换链 的读者,专门回答 “一次 mmap 到底发布了什么状态,物理页何时出现,关闭映射后谁负责收尾”。
文章不展开完整的 binder_transaction() 对象翻译,也不把“one-copy”理解成零拷贝。这里的 “一次”只描述发送方用户 buffer 到接收方 Binder buffer 的数据复制次数;VMA 建立、页表安装、 页回收和对象修复仍然是独立的内核工作。
1. 三层状态
映射阶段至少要区分三种状态:
| 状态 | 关键字段 | 谁建立 | 是否已有物理页 |
|---|---|---|---|
| 打开元数据 | alloc->mm、mutex、buffers、freelist | binder_alloc_init() | 否 |
| 映射发布 | vm_start、buffer_size、初始 free buffer、mapped | binder_alloc_mmap_handler() | 可能仍为否 |
| buffer 使用 | pages[index]、allocated buffer、LRU 状态 | binder_alloc_new_buf() | 覆盖 buffer 的页会有 |
mapped == true 只表示 allocator 已经拥有一段可以管理的 Binder 地址空间,不表示所有页面都 已经分配。当前实现把“可申请 buffer”和“页已经安装”拆开,才能让大块地址空间在没有事务时不 占用等量物理页。
2. 用户入口
2.1 映射请求
源码文件:frameworks/native/libs/binder/ProcessState.cpp
相关函数:ProcessState::ProcessState()
#define BINDER_VM_SIZE \
((1 * 1024 * 1024) - \
sysconf(_SC_PAGE_SIZE) * 2)
ProcessState::ProcessState(const char* driver)
: mDriverName(String8(driver)),
mDriverFD(-1),
mVMStart(MAP_FAILED) {
String8 error;
unique_fd opened = open_driver(
driver, &error);
if (opened.ok()) {
mVMStart = mmap(
nullptr, BINDER_VM_SIZE,
PROT_READ,
MAP_PRIVATE | MAP_NORESERVE,
opened.get(), 0);
if (mVMStart == MAP_FAILED) {
opened.reset();
mDriverName.clear();
}
}
if (opened.ok())
mDriverFD = opened.release();
}这段用户态代码说明了三个事实:
mmap()使用PROT_READ,用户态不会通过这个映射主动写 Binder transaction buffer;MAP_PRIVATE | MAP_NORESERVE只请求一段地址空间,不等于立即提交同等大小的物理内存;- mmap 失败时,ProcessState 关闭刚打开的 fd,随后驱动侧的
binder_release()负责 proc 收束。
BINDER_VM_SIZE 是 libbinder 的请求大小,不应被写成所有设备、所有进程永恒不变的可用容量。 驱动还会在 allocator 中以 min_t(..., SZ_4M) 限制实际管理大小;应用的请求、页大小和驱动上限 共同决定最终的 buffer_size。
2.2 驱动分派
源码文件:kernel/common/drivers/android/binder.c
相关符号:binder_fops
const struct file_operations binder_fops = {
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = compat_ptr_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};VFS 根据 fd 找到 file_operations,.mmap 把 filp 和用户传入的 vm_area_struct 交给 binder_mmap()。此时 filp->private_data 已经由 BD013 的 binder_open() 设置为 binder_proc;映射过程不重新按 PID 查找 proc。
3. VMA校验
3.1 进程归属
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_mmap()
static int binder_mmap(
struct file *filp,
struct vm_area_struct *vma) {
struct binder_proc *proc =
filp->private_data;
if (proc->tsk != current->group_leader)
return -EINVAL;
binder_debug(BINDER_DEBUG_OPEN_CLOSE,
"%s: %d %lx-%lx (%ld K)\n",
__func__, proc->pid,
vma->vm_start, vma->vm_end,
(vma->vm_end - vma->vm_start) /
SZ_1K);binder_proc->tsk 在打开时保存的是 group leader。映射请求必须属于同一 group leader;fork 出的子进程即使继承了 fd,也不能用自己的 group leader 为父进程的 proc 建立 VMA。这个判断保护 的是 Binder proc 与 alloc->mm 的 owner 关系,不能简单解释成“Binder 不支持多线程”。同一 进程的普通线程共享 group leader,仍可由该进程完成映射并使用同一 Binder 地址空间。
3.2 VMA标志
源码文件:kernel/common/drivers/android/binder.c
相关宏与函数:FORBIDDEN_MMAP_FLAGS、binder_mmap()
#define FORBIDDEN_MMAP_FLAGS (VM_WRITE)
if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
pr_err("%s: %d %lx-%lx %s failed %d\n",
__func__, proc->pid,
vma->vm_start, vma->vm_end,
"bad vm_flags", -EPERM);
return -EPERM;
}
vm_flags_mod(vma,
VM_DONTCOPY | VM_MIXEDMAP,
VM_MAYWRITE);当前版本禁止 VM_WRITE,并清除 VM_MAYWRITE。这两个动作分别处理“本次 VMA 已经以可写方式 请求”和“未来能否通过权限修改变成可写”两个层面。VM_DONTCOPY 防止 fork 时复制这段 Binder VMA;VM_MIXEDMAP 允许驱动通过 vm_insert_page() 管理特殊的页安装方式。
因此不能把 Binder 映射解释成普通的可写共享内存。接收进程读取自己 VMA 中的内容,但页的安装、 buffer 的生命周期和数据写入由 Binder 驱动控制。
3.3 VMA回调
源码文件:kernel/common/drivers/android/binder.c
相关符号:binder_vm_ops、binder_vma_open()、binder_vma_close()、binder_vm_fault()
static void binder_vma_open(
struct vm_area_struct *vma) {
struct binder_proc *proc =
vma->vm_private_data;
binder_debug(BINDER_DEBUG_OPEN_CLOSE,
"%d open vm area %lx-%lx\n",
proc->pid,
vma->vm_start, vma->vm_end);
}
static void binder_vma_close(
struct vm_area_struct *vma) {
struct binder_proc *proc =
vma->vm_private_data;
binder_alloc_vma_close(&proc->alloc);
}
VISIBLE_IF_KUNIT vm_fault_t binder_vm_fault(
struct vm_fault *vmf) {
return VM_FAULT_SIGBUS;
}
static const struct vm_operations_struct binder_vm_ops = {
.open = binder_vma_open,
.close = binder_vma_close,
.fault = binder_vm_fault,
};binder_mmap() 把 proc 放入 vma->vm_private_data 后,VMA close 才能找到同一个 allocator。 binder_vma_open() 只记录调试信息;binder_vm_fault() 返回 VM_FAULT_SIGBUS,说明 Binder 不是等用户缺页时才通过通用 fault 回调分配页。页由 allocator 在 buffer 即将使用时显式安装。
4. 地址初始化
4.1 mm一致性
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_alloc_mmap_handler()
int binder_alloc_mmap_handler(
struct binder_alloc *alloc,
struct vm_area_struct *vma) {
struct binder_buffer *buffer;
const char *failure_string;
int ret;
if (unlikely(vma->vm_mm != alloc->mm)) {
ret = -EINVAL;
failure_string = "invalid vma->vm_mm";
goto err_invalid_mm;
}BD013 的 binder_alloc_init() 保存了 current->mm 并增加引用。这里再次比较 VMA 的 vm_mm, 把“打开时的地址空间 owner”和“这次 mmap 的 VMA”接起来。检查失败不修改 buffer_size、pages 或 mapped,因此可以直接返回 -EINVAL。
4.2 单次映射
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_alloc_mmap_handler()、binder_alloc_mmap_lock
mutex_lock(&binder_alloc_mmap_lock);
if (alloc->buffer_size) {
ret = -EBUSY;
failure_string = "already mapped";
goto err_already_mapped;
}
alloc->buffer_size = min_t(
unsigned long,
vma->vm_end - vma->vm_start,
SZ_4M);
mutex_unlock(&binder_alloc_mmap_lock);
alloc->vm_start = vma->vm_start;当前实现用 buffer_size != 0 作为“已经成功占用映射空间”的标志,并在全局 binder_alloc_mmap_lock 下检查和写入。注释和 KUnit fixture 都把一个 Binder instance 描述为 生命周期内单次映射;第二次 mmap 返回 -EBUSY,不会替换旧 VMA。
buffer_size 取 VMA 长度与 SZ_4M 的较小值。它描述 allocator 允许管理的地址范围,而不是 “VMA 被截断后用户还可以安全访问更大的尾部”。用户态应把驱动实际接受的映射作为唯一 buffer 范围,不能越过 vm_start + buffer_size 推测可用空间。
4.3 元数据建立
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_alloc_mmap_handler()
alloc->pages = kvcalloc(
alloc->buffer_size / PAGE_SIZE,
sizeof(alloc->pages[0]),
GFP_KERNEL);
if (!alloc->pages) {
ret = -ENOMEM;
failure_string = "alloc page array";
goto err_alloc_pages_failed;
}
buffer = kzalloc(
sizeof(*buffer), GFP_KERNEL);
if (!buffer) {
ret = -ENOMEM;
failure_string = "alloc buffer struct";
goto err_alloc_buf_struct_failed;
}
buffer->user_data = alloc->vm_start;
list_add(&buffer->entry, &alloc->buffers);
buffer->free = 1;
binder_insert_free_buffer(alloc, buffer);
alloc->free_async_space = alloc->buffer_size / 2;这里分配的是 pages[] 指针数组,不是 buffer_size / PAGE_SIZE 个物理页。初始 binder_buffer 也只是 allocator 的区间描述:它覆盖从 vm_start 开始的整段空间,标记为 free, 并插入按大小索引的 free_buffers 红黑树。此时 allocated_buffers 仍为空。
这一步让后续 binder_alloc_new_buf() 能够先从 free tree 选择地址,再根据选中的 buffer 范围 安装所需页。没有这条“先有区间描述,再有物理页”的顺序,allocator 就无法判断相邻 buffer 是否 共享同一页,也无法维护 LRU 回收。
4.4 发布状态
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_alloc_set_mapped()、binder_alloc_is_mapped()
static inline void binder_alloc_set_mapped(
struct binder_alloc *alloc, bool state) {
smp_store_release(&alloc->mapped, state);
}
static inline bool binder_alloc_is_mapped(
struct binder_alloc *alloc) {
return smp_load_acquire(&alloc->mapped);
}
/* binder_alloc_mmap_handler() 的成功尾部 */
binder_alloc_set_mapped(alloc, true);
return 0;mapped 的 release/acquire 配对不是装饰。它把前面写入的 vm_start、buffer 链表、free tree 和 async space 发布给后续 allocator 使用者。binder_alloc_new_buf() 进入时先调用 binder_alloc_is_mapped();若看到 false,直接返回 -ESRCH,不会在半初始化地址空间上申请 buffer。
5. 按需装页
5.1 Buffer申请
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_alloc_new_buf()
struct binder_buffer *binder_alloc_new_buf(
struct binder_alloc *alloc,
size_t data_size,
size_t offsets_size,
size_t extra_buffers_size,
int is_async) {
struct binder_buffer *buffer, *next;
size_t size;
int ret;
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);
if (!IS_ERR(buffer)) {
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);
if (!IS_ERR(buffer)) {
ret = binder_install_buffer_pages(
alloc, buffer, size);
if (ret) {
binder_alloc_free_buf(alloc, buffer);
buffer = ERR_PTR(ret);
}
}
return buffer;
}申请顺序很重要:先确认 mapped,再对齐并校验总大小,再从 free tree 拿到一个逻辑 buffer, 最后才为这个 buffer 覆盖到的页调用 binder_install_buffer_pages()。因此“申请一个 buffer”是 allocator 发布物理页的触发点,而不是 mmap 本身。
5.2 页范围
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_install_buffer_pages()
static int binder_install_buffer_pages(
struct binder_alloc *alloc,
struct binder_buffer *buffer,
size_t size) {
unsigned long start, final;
unsigned long page_addr;
start = buffer->user_data & PAGE_MASK;
final = PAGE_ALIGN(
buffer->user_data + size);
for (page_addr = start;
page_addr < final;
page_addr += PAGE_SIZE) {
unsigned long index =
(page_addr - alloc->vm_start) /
PAGE_SIZE;
int ret;
if (binder_get_installed_page(
alloc, index))
continue;
ret = binder_install_single_page(
alloc, index, page_addr);
if (ret)
return ret;
}
return 0;
}范围按页对齐,首尾 buffer 可能只使用页的一部分。若某页已经由相邻 buffer 安装,循环跳过它; 这就是为什么页 owner 不能简单等同于单个 buffer。释放 buffer 时,allocator 还要依据相邻区间 判断这页是否可以放回 LRU。
5.3 页安装
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_install_single_page()、binder_page_alloc()、binder_page_insert()
static int binder_install_single_page(
struct binder_alloc *alloc,
unsigned long index,
unsigned long addr) {
struct page *page;
int ret;
if (!mmget_not_zero(alloc->mm))
return -ESRCH;
page = binder_page_alloc(alloc, index);
if (!page) {
ret = -ENOMEM;
goto out;
}
ret = binder_page_insert(alloc, addr, page);
switch (ret) {
case -EBUSY:
ret = 0;
binder_free_page(page);
page = binder_page_lookup(alloc, addr);
if (!page) {
ret = -ESRCH;
break;
}
fallthrough;
case 0:
binder_set_installed_page(
alloc, index, page);
break;
default:
binder_free_page(page);
break;
}
out:
mmput_async(alloc->mm);
return ret;
}这里有两个 owner 转换:binder_page_alloc() 创建并清零物理页及 shrinker 元数据; binder_page_insert() 把页插入已有 VMA 的页表;成功后 pages[index] 才用 release store 发布。 如果并发路径先安装了同一页,vm_insert_page() 返回 -EBUSY 被视为可接受,当前调用释放自己 刚分配的页,再查找已经存在的页,避免重复拥有同一页。
mmget_not_zero() 和 mmput_async() 把这次页安装与 mm 生命周期绑定。进程正在退出、mm 引用已 经归零时,页安装返回 -ESRCH;这不是普通“没有空间”,而是地址空间 owner 已经消失。
6. 数据复制
6.1 发送方到目标页
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_alloc_copy_user_to_buffer()
unsigned long binder_alloc_copy_user_to_buffer(
struct binder_alloc *alloc,
struct binder_buffer *buffer,
binder_size_t buffer_offset,
const void __user *from,
size_t bytes) {
if (!check_buffer(
alloc, buffer,
buffer_offset, bytes))
return bytes;
while (bytes) {
struct page *page;
pgoff_t pgoff;
unsigned long size;
unsigned long ret;
void *kptr;
page = binder_alloc_get_page(
alloc, buffer,
buffer_offset, &pgoff);
size = min_t(size_t,
bytes, PAGE_SIZE - pgoff);
kptr = kmap_local_page(page) + pgoff;
ret = copy_from_user(kptr, from, size);
kunmap_local(kptr);
if (ret)
return bytes - size + ret;
bytes -= size;
from += size;
buffer_offset += size;
}
return 0;
}发送方的用户指针经过 copy_from_user() 写入目标 proc 的 Binder buffer 页。接收方随后从自己 VMA 中读取同一批页;没有第二次把完整 payload 从内核 buffer 复制到另一个接收用户 buffer 的 步骤。这就是当前 Binder one-copy 叙述能够成立的具体源码依据。
但“one-copy”不代表所有数据都走这一条路径。offsets、Binder object、fd fixup 和 scatter-gather 元数据还需要额外校验、翻译和修复;binder_alloc_copy_to_buffer() 也会把内核生成的修复数据写 回目标页。
6.2 Buffer安全
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:check_buffer()
static inline bool check_buffer(
struct binder_alloc *alloc,
struct binder_buffer *buffer,
binder_size_t offset,
size_t bytes) {
size_t buffer_size =
binder_alloc_buffer_size(
alloc, buffer);
return buffer_size >= bytes &&
offset <= buffer_size - bytes &&
IS_ALIGNED(offset, sizeof(u32)) &&
!buffer->free &&
(!buffer->allow_user_free ||
!buffer->transaction);
}复制前同时检查长度、偏移对齐、buffer 是否仍 allocated,以及用户 free 权限与 transaction 状态。这个检查把“VMA 地址存在”与“某个 Binder buffer 当前仍可访问”区分开来;一个已经归还给 free tree 的地址,即使仍落在 VMA 范围内,也不能继续被复制函数使用。
7. 释放回收
7.1 Buffer归还
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_free_buf_locked()、binder_lru_freelist_add()
static void binder_free_buf_locked(
struct binder_alloc *alloc,
struct binder_buffer *buffer) {
size_t buffer_size =
binder_alloc_buffer_size(
alloc, buffer);
BUG_ON(buffer->free);
BUG_ON(buffer->transaction != NULL);
BUG_ON(buffer->user_data < alloc->vm_start);
BUG_ON(buffer->user_data >
alloc->vm_start + alloc->buffer_size);
binder_lru_freelist_add(
alloc,
PAGE_ALIGN(buffer->user_data),
(buffer->user_data + buffer_size) &
PAGE_MASK);
rb_erase(&buffer->rb_node,
&alloc->allocated_buffers);
buffer->free = 1;
binder_insert_free_buffer(alloc, buffer);
}buffer 归还先要求 transaction 已经脱离,再把覆盖范围内不再被其他活动 buffer 使用的页放进 LRU。这里没有立刻释放物理页,因为同一页可能仍被相邻 buffer 使用,或者短时间内很快被重新分配。 free buffer 重新进入按大小索引的红黑树,下一次申请可以复用其地址。
7.2 Shrinker回收
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_alloc_free_page()
enum lru_status binder_alloc_free_page(
struct list_head *item,
struct list_lru_one *lru,
void *cb_arg) {
struct binder_shrinker_mdata *mdata =
container_of(item,
typeof(*mdata), lru);
struct binder_alloc *alloc = mdata->alloc;
struct mm_struct *mm = alloc->mm;
struct vm_area_struct *vma;
struct page *page_to_free;
size_t index = mdata->page_index;
if (!mmget_not_zero(mm))
return LRU_SKIP;
/* 获取 VMA、alloc mutex 后确认映射仍有效 */
page_to_free = alloc->pages[index];
binder_set_installed_page(
alloc, index, NULL);
list_lru_isolate(lru, item);
binder_free_page(page_to_free);
mmput_async(mm);
return LRU_REMOVED_RETRY;
}系统内存压力触发 shrinker 时,只有已经进入 LRU 的空闲页才是候选。回收者重新确认 mm、VMA 和 allocator 状态,先从 pages[index] 摘除,再解除用户页映射并释放物理页。活动 buffer 的页不会 因为 shrinker 被直接回收。
7.3 VMA关闭
源码文件:kernel/common/drivers/android/binder.c、kernel/common/drivers/android/binder_alloc.c
相关函数:binder_vma_close()、binder_alloc_vma_close()
static void binder_vma_close(
struct vm_area_struct *vma) {
struct binder_proc *proc =
vma->vm_private_data;
binder_alloc_vma_close(&proc->alloc);
}
void binder_alloc_vma_close(
struct binder_alloc *alloc) {
binder_alloc_set_mapped(alloc, false);
}VMA 关闭只发布 mapped = false,不在回调中遍历并释放所有页。之后新的 binder_alloc_new_buf() 会因为 binder_alloc_is_mapped() 为 false 返回 -ESRCH;已经存在的 事务则要先由 transaction release 路径解除,再由 deferred allocator release 释放 buffer 和页。
7.4 最终释放
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_alloc_deferred_release()
void binder_alloc_deferred_release(
struct binder_alloc *alloc) {
struct rb_node *n;
struct binder_buffer *buffer;
mutex_lock(&alloc->mutex);
BUG_ON(alloc->mapped);
while ((n = rb_first(
&alloc->allocated_buffers))) {
buffer = rb_entry(
n, struct binder_buffer, rb_node);
BUG_ON(buffer->transaction);
binder_free_buf_locked(alloc, buffer);
}
/* 释放 buffer 描述、安装页和 LRU */
mutex_unlock(&alloc->mutex);
kvfree(alloc->pages);
if (alloc->mm)
mmdrop(alloc->mm);
}BUG_ON(alloc->mapped) 把 VMA 生命周期放在 allocator 最终清理之前;BUG_ON(buffer->transaction) 把事务生命周期放在 buffer 描述释放之前。这个顺序解释了 BD013 中“close fd 不等于立即 free proc” 的原因:文件、VMA、事务和临时引用必须共同收束。
8. 失败路径
8.1 入口失败
| 分支 | 返回 | 已改变状态 | 后续处理 |
|---|---|---|---|
| group leader 不匹配 | -EINVAL | 无 allocator 映射状态变化 | 不建立 VMA |
VM_WRITE 已设置 | -EPERM | 无 allocator 映射状态变化 | 用户态修正保护属性后重试 |
vm_mm 不同 | -EINVAL | buffer_size 仍为 0 | 不进入已映射状态 |
已有 buffer_size | -EBUSY | 原映射保持不变 | 不能用第二次 mmap 替换旧映射 |
8.2 初始化失败
kvcalloc(pages) 失败时,handler 清零 vm_start 和 buffer_size;初始 buffer 分配失败时还会 先 kvfree(alloc->pages),再执行同样的回滚。错误标签不是形式上的 goto 排版,它保证失败后 binder_alloc_deferred_release() 不会误以为已经发布了完整映射。
err_alloc_buf_struct_failed:
kvfree(alloc->pages);
alloc->pages = NULL;
err_alloc_pages_failed:
alloc->vm_start = 0;
mutex_lock(&binder_alloc_mmap_lock);
alloc->buffer_size = 0;
err_already_mapped:
mutex_unlock(&binder_alloc_mmap_lock);
err_invalid_mm:
binder_alloc_debug(
BINDER_DEBUG_USER_ERROR,
"%s: %d %lx-%lx %s failed %d\n",
__func__, alloc->pid,
vma->vm_start, vma->vm_end,
failure_string, ret);
return ret;8.3 装页失败
buffer 已经从 free tree 移入 allocated tree 后,页安装仍可能返回 -ESRCH 或 -ENOMEM。此时 binder_alloc_new_buf() 调用 binder_alloc_free_buf() 归还逻辑 buffer,并把已经进入 LRU 的页 交回 allocator;调用者看到的是 buffer 申请失败,而不是一个半可用的 transaction buffer。
9. 测试输入
9.1 用户态请求
源码文件:frameworks/native/libs/binder/ProcessState.cpp
相关测试入口:frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp 中的 BinderDriverInterfaceTest.OpenNoMmap
OpenNoMmap 专门证明打开 fd 不依赖 mmap;它不能证明映射成功。真正的正常用户态映射由 ProcessState 构造函数执行,输入是 PROT_READ、MAP_PRIVATE | MAP_NORESERVE 和 BINDER_VM_SIZE,失败断言是 mVMStart == MAP_FAILED 后关闭 fd。
9.2 KUnit映射
源码文件:kernel/common/drivers/android/tests/binder_alloc_kunit.c
相关测试:binder_alloc_test_mmap()、binder_alloc_exhaustive_test()、binder_alloc_test_exit()
static void binder_alloc_test_mmap(
struct kunit *test) {
struct binder_alloc_test *priv =
test->priv;
struct binder_alloc *alloc =
&priv->alloc;
struct binder_buffer *buf;
struct rb_node *n;
KUNIT_EXPECT_EQ(test,
alloc->mapped, true);
KUNIT_EXPECT_EQ(test,
alloc->buffer_size,
BINDER_MMAP_SIZE);
n = rb_first(
&alloc->allocated_buffers);
KUNIT_EXPECT_PTR_EQ(test, n, NULL);
n = rb_first(&alloc->free_buffers);
buf = rb_entry(n,
struct binder_buffer, rb_node);
KUNIT_EXPECT_EQ(test,
binder_alloc_buffer_size(
alloc, buf),
BINDER_MMAP_SIZE);
}fixture 使用 BINDER_MMAP_SIZE = SZ_128K,先调用 __binder_alloc_init(),再通过测试 file 的 .mmap 建立映射。这个测试的输入和断言说明:映射成功后 allocator 有一个覆盖整段空间的 free buffer,但 allocated_buffers 为空;它没有暗示 128 KiB 是生产 libbinder 的固定大小。
binder_alloc_exhaustive_test() 进一步覆盖 5 种 buffer 尾部对齐、前后两种页共享布局和所有释放 顺序,检查页在 allocated、LRU 和最终 free 之间转换。fixture teardown 先关闭 backing file 触发 VMA close,再调用 deferred release,并断言测试 freelist 为空。
这些测试覆盖 allocator 的区间、页安装、复用和回收边界,但没有覆盖真实 Binder 事务的对象翻译、 跨进程 copy_from_user()、设备权限或不同架构的页表行为。
10. 阅读边界
映射状态
Mapped 表示 allocator 已发布一次合法映射,不代表所有 buffer 页都已驻留;Reclaimable 到 PageInstall 的回边是按需装页,而不是重新建立整个 VMA。
从本文可以得到的主线是:
ProcessState::mmap
→ binder_fops.mmap
→ binder_mmap
→ binder_alloc_mmap_handler
→ vm_start/buffer_size/pages/free buffer/mapped
→ binder_alloc_new_buf
→ binder_install_single_page
→ vm_insert_page
→ binder_alloc_copy_user_to_buffer
→ VMA close 与 deferred release这条线没有回答三个相邻问题:
binder_alloc_new_buf_locked()如何按大小选择和拆分 free buffer,属于后续 allocator/buffer 管理文章;binder_transaction()如何把目标 buffer、offsets、Binder object 和 fd fixup 串起来,属于 事务文章;BINDER_WRITE_READ如何让接收线程从 buffer 读取并发送BC_FREE_BUFFER,属于 ioctl/协议文章。
可以在源码 checkout 中执行以下搜索,把这三个边界接回真实入口:
rg -n "ProcessState::ProcessState|BINDER_VM_SIZE|mVMStart = mmap" \
frameworks/native/libs/binder/ProcessState.cpp
rg -n "binder_mmap|binder_vm_ops|binder_vma_close" \
kernel/common/drivers/android/binder.c
rg -n "binder_alloc_mmap_handler|binder_install_buffer_pages|binder_install_single_page" \
kernel/common/drivers/android/binder_alloc.c
rg -n "binder_alloc_test_mmap|binder_alloc_exhaustive_test|binder_alloc_test_exit" \
kernel/common/drivers/android/tests/binder_alloc_kunit.c如果读者能指出 mapped 为 false 时为什么不能申请 buffer、-EBUSY 为什么不是“旧映射被覆盖”、 以及某个空闲 buffer 的页何时进入 LRU,就已经能继续阅读下一篇驱动入口,而不需要把 Binder 映射 误当成普通共享内存。
