Skip to content

Binder内存映射

追踪 Binder VMA 校验、地址空间初始化、按需页安装、buffer 分配和映射关闭的源码边界。

基于android-17.0.0_r1
AndroidBinder驱动内存管理源码阅读

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、freelistbinder_alloc_init()否
映射发布vm_start、buffer_size、初始 free buffer、mappedbinder_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()

cpp
#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();
}

这段用户态代码说明了三个事实:

  1. mmap() 使用 PROT_READ,用户态不会通过这个映射主动写 Binder transaction buffer;
  2. MAP_PRIVATE | MAP_NORESERVE 只请求一段地址空间,不等于立即提交同等大小的物理内存;
  3. 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

c
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()

c
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()

c
#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()

c
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()

c
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

c
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()

c
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()

c
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()

c
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()

c
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()

c
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()

c
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()

c
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()

c
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()

c
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()

c
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()

c
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 不同-EINVALbuffer_size 仍为 0不进入已映射状态
已有 buffer_size-EBUSY原映射保持不变不能用第二次 mmap 替换旧映射

8.2 初始化失败 ​

kvcalloc(pages) 失败时,handler 清零 vm_start 和 buffer_size;初始 buffer 分配失败时还会 先 kvfree(alloc->pages),再执行同样的回滚。错误标签不是形式上的 goto 排版,它保证失败后 binder_alloc_deferred_release() 不会误以为已经发布了完整映射。

c
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()

c
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。

从本文可以得到的主线是:

text
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 中执行以下搜索,把这三个边界接回真实入口:

bash
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 映射 误当成普通共享内存。