Skip to content

binder_alloc内存分配器

追踪 binder_alloc 的地址空间、best-fit 分配、按需装页、异步配额、空闲合并和 shrinker 回收。

基于android-17.0.0_r1
AndroidBinderbinder_alloc内存管理源码阅读

binder_alloc内存分配器 ​

Binder transaction 的接收空间不是一个普通的全局内核堆。每个 Binder 进程先拥有一段自己的 Binder VMA,binder_alloc 在这段地址空间上维护 buffer 边界;发送事务时,驱动从空闲区间挑出一段地址,在覆盖该区间的页上安装物理页,再把 payload 复制进去。接收进程看到的 binder_transaction_data.data.ptr.buffer 就是这段进程私有 VMA 中的地址。

本文面向已经读过 Binder内存映射、Binder事务发送 和 BR事务返回 的读者。BD014 解释 VMA 如何建立和关闭,BD017 解释事务怎样调用 allocator,本篇专门回答 allocator 内部怎样选择 buffer、为什么异步事务只有一半地址空间、页何时安装、释放后怎样合并以及内存压力下怎样解除用户映射。

本文不把 binder_buffer 的事务字段、对象翻译和 BC_FREE_BUFFER 协议重新展开;那些字段如何参与事务生命周期分别由 BD017、BD022 和后续 BD025 负责。这里的“释放”首先指 allocator 的地址区间和页状态变化,事务对象清理由上层驱动调用者负责。

下面这张图把三个容易混淆的层次分开:free_buffers/allocated_buffers 管理的是虚拟地址区间,pages[] 管理区间覆盖的物理页指针,binder_freelist 管理已经暂时不被活动 buffer 使用的页。一次 buffer 释放可以同时改变三层,但三层并不在同一个时刻完成。

1. 两棵树 ​

1.1 分配器状态 ​

源码文件:kernel/common/drivers/android/binder_alloc.h

相关类型:binder_alloc

c
struct binder_alloc {
    struct mutex mutex;
    struct mm_struct *mm;
    unsigned long vm_start;
    struct list_head buffers;
    struct rb_root free_buffers;
    struct rb_root allocated_buffers;
    size_t free_async_space;
    struct page **pages;
    struct list_lru *freelist;
    size_t buffer_size;
    int pid;
    size_t pages_high;
    bool mapped;
    bool oneway_spam_detected;
};

binder_alloc 嵌在 binder_proc 中,owner 是一个打开 Binder 设备并完成 mmap 的进程。buffers 是按用户地址排列的双向链表,负责回答“某个区间的前一个和后一个是谁”;两棵红黑树承担不同查询:

索引排序键主要消费者
free_buffersbuffer 实际区间大小binder_alloc_new_buf_locked() 做 best-fit
allocated_buffersuser_data 起始地址binder_alloc_prepare_to_free() 校验用户释放地址
buffers用户地址链表顺序前后邻居查找、边界计算、合并

同一个 binder_buffer 的 rb_node 在空闲和已分配状态之间移动,不会同时出现在两棵树里。mutex 保护这些树、链表、async 配额和页数组元数据;页安装还用 release/acquire 发布 alloc->pages[index],避免另一个线程看到半完成的页状态。

1.2 Buffer元数据 ​

源码文件:kernel/common/drivers/android/binder_alloc.h

相关类型:binder_buffer、binder_shrinker_mdata

c
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;
};

struct binder_shrinker_mdata {
    struct list_head lru;
    struct binder_alloc *alloc;
    unsigned long page_index;
};

binder_buffer 描述的是一个地址区间及其事务关联,不是物理页本身。页上的 page_private 保存 binder_shrinker_mdata,这样全局 list_lru 回调能从一个 LRU 节点反查所属 allocator 和页索引。页回收必须先确认该页不再被活动 buffer 使用;区间 free 与页进入 LRU 是两个相关但不相同的状态。

2. 初始化发布 ​

2.1 空间建立 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_alloc_mmap_handler()

c
if (unlikely(vma->vm_mm != alloc->mm)) {
    ret = -EINVAL;
    goto err_invalid_mm;
}

mutex_lock(&binder_alloc_mmap_lock);
if (alloc->buffer_size) {
    ret = -EBUSY;
    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;
alloc->pages = kvcalloc(
        alloc->buffer_size / PAGE_SIZE,
        sizeof(alloc->pages[0]), GFP_KERNEL);

allocator 只接受与 binder_alloc_init() 保存的 mm 相同的 VMA,并把请求长度限制到 SZ_4M。这里的 pages 只是 struct page * 数组,数组分配不等于物理页全部分配;真正的 page object 在某个 buffer 首次覆盖该页时创建。

2.2 初始空闲区 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_alloc_mmap_handler()、binder_insert_free_buffer()

c
buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
if (!buffer)
    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;

binder_alloc_set_mapped(alloc, true);

整个 VMA 最初只有一个 free buffer。free_async_space 从总地址空间的一半开始,而不是单独再分配一块 oneway 区域;它是对 free_buffers 同一地址空间的逻辑配额。只有初始 buffer、页数组和配额都准备好后才发布 mapped = true,因此分配入口可以把 false 解释为“尚未可用”。

2.3 关闭屏障 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_alloc_vma_close()、binder_alloc_is_mapped()

c
void binder_alloc_vma_close(struct binder_alloc *alloc)
{
    binder_alloc_set_mapped(alloc, false);
}

static inline bool binder_alloc_is_mapped(
        struct binder_alloc *alloc)
{
    return smp_load_acquire(&alloc->mapped);
}

VMA close 先撤销 mapped 发布状态,使新的 binder_alloc_new_buf() 返回 -ESRCH,也使页安装和 shrinker 不再把旧地址当作有效 Binder 映射。已经存在的 buffer、事务和页不会因为这个布尔值自动消失,后续 deferred release 仍需逐项清理。

3. 尺寸清洗 ​

3.1 三段长度 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:sanitized_size()、binder_alloc_new_buf()

c
static inline size_t sanitized_size(
        size_t data_size,
        size_t offsets_size,
        size_t extra_buffers_size)
{
    size_t total, tmp;

    tmp = ALIGN(data_size, sizeof(void *)) +
          ALIGN(offsets_size, sizeof(void *));
    if (tmp < data_size || tmp < offsets_size)
        return 0;
    total = tmp + ALIGN(extra_buffers_size,
                        sizeof(void *));
    if (total < tmp || total < extra_buffers_size)
        return 0;
    return max(total, sizeof(void *));
}

分配总长度不是简单的 data_size + offsets_size + extra_buffers_size:三段分别按指针宽度对齐,并通过加法后的回绕检查拒绝溢出。即使三段长度都是 0,也至少分配一个指针宽度,使空 Parcel 也拥有唯一的用户地址,避免两个零长度 buffer 无法在 allocated_buffers 中按地址区分。

3.2 入口错误 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_alloc_new_buf()

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

这三个错误属于不同层次:-ESRCH 表示 allocator 的 VMA 已经关闭,-EINVAL 表示长度组合溢出或非法,-ENOMEM 表示仅为 buffer 元数据预分配的内存失败。地址空间不足和 async 配额不足由锁内 best-fit 函数返回 -ENOSPC,不能把所有失败都归结成物理页不足。

4. Best-fit分配 ​

4.1 空闲树搜索 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_alloc_new_buf_locked()

c
n = alloc->free_buffers.rb_node;
best_fit = NULL;
while (n) {
    buffer = rb_entry(n,
            struct binder_buffer, rb_node);
    buffer_size = binder_alloc_buffer_size(
            alloc, buffer);

    if (size < buffer_size) {
        best_fit = n;
        n = n->rb_left;
    } else if (size > buffer_size) {
        n = n->rb_right;
    } else {
        best_fit = n;
        break;
    }
}
if (!best_fit)
    return ERR_PTR(-ENOSPC);

free_buffers 的比较键是 buffer 实际区间大小,而不是 struct binder_buffer 的地址。搜索过程中遇到第一个足够大的节点就记录并继续向左,最终得到最小的可用区间;大小完全相等时立即命中。它是 best-fit,不是 first-fit,也不是按最低虚拟地址优先。

4.2 区间拆分 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_alloc_new_buf_locked()

c
if (buffer_size != size) {
    buffer = rb_entry(best_fit,
            struct binder_buffer, rb_node);
    new_buffer->user_data =
            buffer->user_data + size;
    list_add(&new_buffer->entry,
            &buffer->entry);
    new_buffer->free = 1;
    binder_insert_free_buffer(
            alloc, new_buffer);
    new_buffer = NULL;
}

rb_erase(&buffer->rb_node,
        &alloc->free_buffers);
buffer->free = 0;
buffer->allow_user_free = 0;
binder_insert_allocated_buffer_locked(
        alloc, buffer);

分配一个大于请求的 free buffer 时,原区间被切成“已分配前段 + 空闲后段”;后段使用调用者预先分配的 new_buffer 元数据。已分配段从 free tree 移出、进入 allocated tree,并把 allow_user_free 置零,直到驱动把事务交付给用户态后才允许 BC_FREE_BUFFER。

4.3 页Lru隔离 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_lru_freelist_del()、binder_alloc_new_buf_locked()

c
next_used_page = (buffer->user_data + buffer_size)
        & PAGE_MASK;
curr_last_page = PAGE_ALIGN(
        buffer->user_data + size);
binder_lru_freelist_del(alloc,
        PAGE_ALIGN(buffer->user_data),
        min(next_used_page, curr_last_page));

分配前要从全局 free-page LRU 移除将被重新使用的页。结束地址取 next_used_page 和本次请求页边界的较小值,是为了保留与相邻已使用 buffer 共享的末页;如果直接按整个 buffer_size 移除,可能把仍被邻居使用的页错误地当成当前分配独占页。

5. 异步配额 ​

5.1 配额扣减 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_alloc_new_buf_locked()

c
if (is_async && alloc->free_async_space < size)
    return ERR_PTR(-ENOSPC);

buffer->async_transaction = is_async;
if (is_async) {
    alloc->free_async_space -= size;
    if (debug_low_async_space_locked(alloc))
        buffer->oneway_spam_suspect = true;
}

is_async 由上层事务路径根据 TF_ONE_WAY 传入。异步 buffer 仍从同一棵 free tree 分配,但会额外消耗 free_async_space;同步 buffer 不受这项半空间配额直接限制。因而“总 free space 还有很多但 oneway 失败”是可能的,原因可能是 async 配额耗尽而不是地址空间耗尽。

5.2 低水位检测 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:debug_low_async_space_locked()

c
if (alloc->free_async_space >=
        alloc->buffer_size / 10) {
    alloc->oneway_spam_detected = false;
    return false;
}

if (num_buffers > 50 ||
        total_alloc_size > alloc->buffer_size / 4) {
    if (!alloc->oneway_spam_detected) {
        alloc->oneway_spam_detected = true;
        return true;
    }
}
return false;

低水位检查只在 async 空间低于总 buffer 的 10% 时开始统计当前 pid 的 async buffer 数量和总大小;超过 50 个或超过总 buffer 的 25% 才把本次 buffer 标记为 oneway_spam_suspect。这些阈值属于驱动的检测条件,不是 oneway 队列的容量定义,也不代表事务必然被拒绝。

6. 按需装页 ​

6.1 Buffer覆盖范围 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_install_buffer_pages()

c
start = buffer->user_data & PAGE_MASK;
final = PAGE_ALIGN(buffer->user_data + size);

for (page_addr = start; page_addr < final;
        page_addr += PAGE_SIZE) {
    index = (page_addr - alloc->vm_start)
            / PAGE_SIZE;
    if (binder_get_installed_page(
            alloc, index))
        continue;
    ret = binder_install_single_page(
            alloc, index, page_addr);
    if (ret)
        return ret;
}

一个 buffer 可能从页中间开始,也可能在末页与邻接 buffer 共享。安装函数按页对齐后的起止地址遍历,只为 alloc->pages[index] == NULL 的页创建和插入 page;已经安装的页直接复用。

6.2 页面创建 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_install_single_page()、binder_page_alloc()、binder_page_insert()

c
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;
}

binder_page_insert() 使用 vm_insert_page() 把新 page 插入目标进程 VMA。并发安装同一页时,-EBUSY 不是失败:另一个线程可能已经完成用户页表插入但尚未更新 alloc->pages[index],当前线程释放自己分配的 page,再通过 binder_page_lookup() 找回已存在的 page。只有找到并发布到数组后,后续复制路径才可以安全使用它。

6.3 发布顺序 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_set_installed_page()、binder_get_installed_page()

c
static inline void binder_set_installed_page(
        struct binder_alloc *alloc,
        unsigned long index, struct page *page)
{
    smp_store_release(&alloc->pages[index], page);
}

static inline struct page *binder_get_installed_page(
        struct binder_alloc *alloc,
        unsigned long index)
{
    return smp_load_acquire(&alloc->pages[index]);
}

VMA 插入和 pages[] 发布是两个步骤;release/acquire 配对让消费者不会在数组中看到 page 指针后,却看不到相应的安装结果。它不替代 allocator mutex,也不负责保护 buffer 树。

7. 释放合并 ​

7.1 释放入口 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_alloc_free_buf()、binder_free_buf_locked()

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

如果 buffer 要求清零,清零发生在取得 allocator mutex 之前,减少大 buffer 清理对树操作的锁竞争。随后锁内完成 async 配额恢复、页 LRU 入队、树迁移和相邻区间合并。

7.2 配额恢复 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_free_buf_locked()

c
if (buffer->async_transaction)
    alloc->free_async_space += 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;

恢复使用的是整个 allocator 区间 buffer_size,不是仅 data、offsets 和 extra 三段的逻辑长度;这是因为 async 配额保护的是地址空间占用。释放后覆盖范围中不再被相邻活动 buffer 使用的页才会进入 free-page LRU。

7.3 邻居合并 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_free_buf_locked()、binder_delete_free_buffer()

c
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;
    }
}
binder_insert_free_buffer(alloc, buffer);

合并依赖 buffers 地址链表,而不是红黑树中的相邻节点:free tree 按大小排序,不能表达虚拟地址邻接。删除被吸收的元数据后,合并后的大区间重新按大小插入 free tree,下一次 best-fit 才能看到新的连续空间。

8. 页回收 ​

8.1 LRU候选 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_lru_freelist_add()、binder_alloc_free_page()

c
ret = list_lru_add(alloc->freelist,
        page_to_lru(page), page_to_nid(page), NULL);
WARN_ON(!ret);

页面在 buffer 释放后进入全局 binder_freelist,但仍保留在用户 VMA 和 alloc->pages[] 中;这允许后续同一地址重新分配时快速复用,不需要立即重新申请 page。LRU 是“可回收候选”,不是已经解除映射。

8.2 Shrinker检查 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_alloc_free_page()

c
if (!mmget_not_zero(mm))
    goto err_mmget;

vma = lock_vma_under_rcu(mm, page_addr);
if (!vma) {
    if (!mmap_read_trylock(mm))
        goto err_mmap_read_lock_failed;
    mm_locked = 1;
    vma = vma_lookup(mm, page_addr);
}

if (!mutex_trylock(&alloc->mutex))
    goto err_get_alloc_mutex_failed;
if (vma && !binder_alloc_is_mapped(alloc))
    goto err_invalid_vma;

shrinker 不能只拿 allocator mutex 就释放 page:它还要确认 mm 仍然有效、找到对应 VMA,并避免在 allocator 正在关闭或映射已失效时操作用户页表。拿不到任一锁时返回 LRU_SKIP,让本次内存回收保留候选而不是冒险破坏映射。

8.3 解除映射 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_alloc_free_page()

c
page_to_free = alloc->pages[index];
binder_set_installed_page(alloc, index, NULL);
list_lru_isolate(lru, item);
spin_unlock(&lru->lock);

if (vma)
    zap_page_range_single(
            vma, page_addr, PAGE_SIZE, NULL);

mutex_unlock(&alloc->mutex);
binder_free_page(page_to_free);
return LRU_REMOVED_RETRY;

顺序是先从 pages[] 撤销 allocator 所有权,再隔离 LRU 节点、清除用户页表,最后释放 page 和 shrinker 元数据。之后如果同一 buffer 再次被分配,binder_install_single_page() 会重新创建 page 并插入 VMA。

9. 失败边界 ​

阶段条件结果owner是否变化
映射VMA 的 vm_mm 不匹配-EINVAL不发布 mapped
映射重复建立地址空间-EBUSY保留原 allocator
尺寸对齐或加法溢出-EINVAL不创建 buffer
选择没有足够大的连续 free 区间-ENOSPCfree tree 不变
asyncfree_async_space < size-ENOSPCasync 配额不扣减
页安装分配 page 失败-ENOMEM已选 buffer 被释放回 tree
页安装VMA/mm 已失效-ESRCHallocator 进入关闭收束
shrinker锁或 mm 检查失败LRU_SKIPpage 留在 LRU

尤其要区分“连续虚拟地址不足”和“物理页不足”:best-fit 找不到区间时即使系统还有大量空闲物理内存也会返回 -ENOSPC;页安装失败则发生在区间已经从 free tree 取出之后,binder_alloc_new_buf() 会调用 binder_alloc_free_buf() 回滚地址区间。

10. 测试输入 ​

10.1 初始状态 ​

源码文件:kernel/common/drivers/android/tests/binder_alloc_kunit.c

相关测试:binder_alloc_test_mmap()

c
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 先建立测试 VMA,再断言 allocator 已发布、allocated tree 为空、free tree 只有覆盖整个映射的初始区间。它证明初始化后的树形不变量,不证明真实 app 的 ProcessState::mmap() 参数。

10.2 穷举分配释放 ​

源码文件:kernel/common/drivers/android/tests/binder_alloc_kunit.c

相关测试:binder_alloc_exhaustive_test()、gen_buf_offsets()、permute_frees()

测试生成不同页对齐、前向/后向布局和所有释放顺序:先分配多个 buffer,断言覆盖页已安装;按排列组合释放,断言相关页进入 LRU;再次分配,断言 LRU 清空;最后调用 binder_alloc_free_page(),断言 pages[] 全部为空。它验证的是 buffer 边界、页共享、合并和回收组合,不是吞吐量或碎片率基准。

10.3 关闭收尾 ​

源码文件:kernel/common/drivers/android/tests/binder_alloc_kunit.c

相关测试:binder_alloc_test_exit()、binder_alloc_vma_close()、binder_alloc_deferred_release()

teardown 关闭 backing file 触发 VMA close,再调用 deferred release,检查测试 LRU 为空。这个路径验证 allocator 可以在映射失效后释放 allocated buffer、已安装页、页数组和 mm 引用;它没有证明活动 Binder transaction 在任意时刻都能安全跳过上层事务清理。

11. 源码导航 ​

遇到 Binder buffer 分配失败,可以按以下顺序定位:

  1. 看 binder_alloc_is_mapped(),排除 VMA 已关闭导致的 -ESRCH;
  2. 用 sanitized_size() 复算 data、offsets、extra 对齐后的总长度;
  3. 查看 free_async_space,判断是否只有 oneway 配额耗尽;
  4. 检查 free_buffers 是否有足够大的连续区间,而不是只看 free 总量;
  5. 如果区间已选中但返回 -ENOMEM/-ESRCH,继续看 binder_install_single_page() 的 VMA 和 page 插入;
  6. 释放后检查 buffers 链表的邻居合并,以及对应页是否进入 LRU;
  7. 内存压力场景再看 binder_alloc_free_page() 是否因锁竞争返回 LRU_SKIP。

源码搜索:

bash
rg -n "binder_alloc_new_buf|sanitized_size|binder_alloc_new_buf_locked" \
  kernel/common/drivers/android/binder_alloc.c

rg -n "binder_install_buffer_pages|binder_install_single_page|binder_page_insert" \
  kernel/common/drivers/android/binder_alloc.c

rg -n "binder_free_buf_locked|binder_alloc_free_buf|binder_delete_free_buffer" \
  kernel/common/drivers/android/binder_alloc.c

rg -n "binder_alloc_free_page|binder_shrink_count|binder_shrink_scan" \
  kernel/common/drivers/android/binder_alloc.c

rg -n "binder_alloc_exhaustive_test|permute_frees|binder_alloc_test_mmap" \
  kernel/common/drivers/android/tests/binder_alloc_kunit.c

如果能回答“为什么 free tree 按大小、allocated tree 按地址”“为什么 async 配额和总 free space 是两个判断”“为什么释放 buffer 后页先进入 LRU 而不是立即 free”“为什么 shrinker 要同时验证 VMA、mm 和 allocator mutex”,就已经能顺着 binder_alloc 的真实状态变化阅读后续 buffer 管理和内存压力路径。