Binder驱动之内存管理

内存映射

用户空间在使用Binder进行IPC前,需要对Binder驱动进行初始化,这个过程主要执行了Binder驱动的open和mmap操作。mmap映射Binder传输使用的内存空间,大小为(1M - 8K),但仅仅是进行虚拟地址空间映射,实际的物理内存分配会在数据传输时进行。mmap的源码如下,

    static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
    {
        int ret;
        struct vm_struct *area;
        struct binder_proc *proc = filp->private_data;
        const char *failure_string;
        struct binder_buffer *buffer;
    if (proc->tsk != current)
        return -EINVAL;

    // 映射空间不能大于4M
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;
    ......
    // fork的子进程无法复制映射空间,并且不允许修改写属性
    vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;
    ......
    // 获取内核虚拟地址空间
    area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
    ......
    proc->buffer = area->addr;
    proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
    mutex_unlock(&binder_mmap_lock);
    ......
    // 创建物理页结构体
    proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
    ......
    proc->buffer_size = vma->vm_end - vma->vm_start;

    vma->vm_ops = &binder_vm_ops;
    vma->vm_private_data = proc;
    // 分配一个物理页,并映射到虚拟地址空间
    if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {
    ......
    // 创建buffers链表,插入第一个free buffer
    buffer = proc->buffer;
    INIT_LIST_HEAD(&proc->buffers);
    list_add(&buffer->entry, &proc->buffers);
    buffer->free = 1;
    binder_insert_free_buffer(proc, buffer);
    // 异步传输可用空间大小设置为映射大小的一半
    proc->free_async_space = proc->buffer_size / 2;
    barrier();
    proc->files = get_files_struct(current);
    proc->vma = vma;
    proc->vma_vm_mm = vma->vm_mm;
    ......
}</code></pre>

几个相关细节说明一下,

  • kernel空间限制了mmap的大小不能超过4M,而Android中用户空间限制为(1M - 8K)。所以理论上是可以修改用户空间的限制到最大为4M。
  • mmap时分配了一个页进行映射,用来存放第一个binder_buffer,这时整个Binder内存只有一个free buffer。映射还有一个作用就是在初始化时验证内存管理的有效性,有问题时及时终止。
  • 数据结构binder_buffer也是存放在mmap空间的,那么实际Binder可传输的数据大小不等于映射的空间大小。
  • mmap完成后,proc->buffers队列上只有一个节点,指向proc->buffer,大小为整个map空间。proc->free_buffers树上也只有proc->buffer一个节点,proc->allocated_buffers树为空。

物理内存的分配和虚拟地址空间的映射是通过binder_update_page_range()进行的。物理内存的回收同样也使用这个函数,通过参数allocate来区分。

static int binder_update_page_range(struct binder_proc *proc, int allocate,
                    void *start, void *end,
                    struct vm_area_struct *vma)
{
        void *page_addr;
        unsigned long user_page_addr;
        struct vm_struct tmp_area;
        struct page **page;
        struct mm_struct *mm;
        ......
        // 只有mmap时vma不为NULL,其他情况都会根据proc来获取内存相关数据
        if (vma)
            mm = NULL;
        else
            // 获取内存描述符,并增加用户计数,防止mm_struct被释放
            mm = get_task_mm(proc->tsk);
    if (mm) {
        down_write(&amp;mm-&gt;mmap_sem);
        vma = proc-&gt;vma;
        if (vma &amp;&amp; mm != proc-&gt;vma_vm_mm) {
            pr_err("%d: vma mm and task mm mismatch\n",
                proc-&gt;pid);
            vma = NULL;
        }
    }

    // 回收内存时allocate为0
    if (allocate == 0)
        goto free_range;
    ......
    // 循环分配物理页,每次分配一页
    for (page_addr = start; page_addr &lt; end; page_addr += PAGE_SIZE) {
        int ret;
        struct page **page_array_ptr;
        page = &amp;proc-&gt;pages[(page_addr - proc-&gt;buffer) / PAGE_SIZE];

        BUG_ON(*page);
        // 分配一个物理页,保存地址到proc中
        *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
        ......
        tmp_area.addr = page_addr;
        tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
        page_array_ptr = page;
        // 建立页表与物理页的映射
        ret = map_vm_area(&amp;tmp_area, PAGE_KERNEL, &amp;page_array_ptr);
        ......
        user_page_addr =
            (uintptr_t)page_addr + proc-&gt;user_buffer_offset;
        // 插入物理页到用户虚拟地址空间
        ret = vm_insert_page(vma, user_page_addr, page[0]);
        ......
    }
    if (mm) {
        up_write(&amp;mm-&gt;mmap_sem);
        // 减少内存描述符的用户计数
        mmput(mm);
    }
    return 0;

free_range:
    for (page_addr = end - PAGE_SIZE; page_addr &gt;= start;
         page_addr -= PAGE_SIZE) {
        page = &amp;proc-&gt;pages[(page_addr - proc-&gt;buffer) / PAGE_SIZE];
        if (vma)
            // 解除用户虚拟地址空间与物理页的映射
            zap_page_range(vma, (uintptr_t)page_addr +
                proc-&gt;user_buffer_offset, PAGE_SIZE, NULL);
err_vm_insert_page_failed:
        // 解除物理页与内核页表的映射
        unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);
err_map_kernel_failed:
        // 释放物理页
        __free_page(*page);
        *page = NULL;
err_alloc_page_failed:
        ;
    }
........

}

内存分配

Binder内存分配函数为binder_alloc_buf(),直接看源码。

static struct binder_buffer *binder_alloc_buf(struct binder_proc *proc,
                          size_t data_size,
                          size_t offsets_size, int is_async)
{
    struct rb_node *n = proc->free_buffers.rb_node;
    struct binder_buffer *buffer;
    size_t buffer_size;
    struct rb_node *best_fit = NULL;
    void *has_page_addr;
    void *end_page_addr;
    size_t size;
    ......
    // size按指针字节数对齐
    size = ALIGN(data_size, sizeof(void *)) +
        ALIGN(offsets_size, sizeof(void *));
    ......
    // 在free_buffers上寻找匹配size大小的节点。循环结束后,如果n==NULL
    // 表示没有匹配节点,则best_fit为最接近的大于size的节点
    while (n) {
        buffer = rb_entry(n, struct binder_buffer, rb_node);
        BUG_ON(!buffer->free);
        buffer_size = binder_buffer_size(proc, buffer);
    if (size &lt; buffer_size) {
        best_fit = n;
        n = n-&gt;rb_left;
    } else if (size &gt; buffer_size)
        n = n-&gt;rb_right;                     
    else {
        best_fit = n;
        break;
    }
}
......
// free_buffers没有找到匹配的节点,使用best_fit节点来分配
if (n == NULL) {
    buffer = rb_entry(best_fit, struct binder_buffer, rb_node);
    buffer_size = binder_buffer_size(proc, buffer);
}
// best_fit节点最后一页的起始地址
has_page_addr =
    (void *)(((uintptr_t)buffer-&gt;data + buffer_size) &amp; PAGE_MASK);
// best_fit节点的内存大小,在分配size后如果不足以创建新的buffer,则不拆分
if (n == NULL) {
    if (size + sizeof(struct binder_buffer) + 4 &gt;= buffer_size)
        buffer_size = size; /* no room for other buffers */
    else
        buffer_size = size + sizeof(struct binder_buffer);
}
// 需要分配空间的最后一页的结束地址
end_page_addr =
    (void *)PAGE_ALIGN((uintptr_t)buffer-&gt;data + buffer_size);
// end_page&gt;has_page表明要分配的空间的结束地址在best_fit节点的最后一页上
// 这是修正分配的结束地址到has_page,因为最后一页已经映射过
if (end_page_addr &gt; has_page_addr)
    end_page_addr = has_page_addr;
// 分配物理页
if (binder_update_page_range(proc, 1,
    (void *)PAGE_ALIGN((uintptr_t)buffer-&gt;data), end_page_addr, NULL))
    return NULL;

// 将best_fit从free_buffers上擦除,让后将新建的buffer插入allocated_buffers
rb_erase(best_fit, &amp;proc-&gt;free_buffers);
buffer-&gt;free = 0;
binder_insert_allocated_buffer(proc, buffer);
// 多余空间进行拆分,插入free_buffers
if (buffer_size != size) {
    struct binder_buffer *new_buffer = (void *)buffer-&gt;data + size;
    list_add(&amp;new_buffer-&gt;entry, &amp;buffer-&gt;entry);
    new_buffer-&gt;free = 1;
    binder_insert_free_buffer(proc, new_buffer);
}
......
buffer-&gt;data_size = data_size;
buffer-&gt;offsets_size = offsets_size;
buffer-&gt;async_transaction = is_async;
......
return buffer;

}

分配过程的重点在起始地址的计算上。如果在free_buffers树上可以找到匹配的buffer则使用。如果找不到匹配的buffer,就使用大于需求大小且最接近的buffer。因为proc->buffers队列上不会有连续的两个free buffer(在释放buffer时会合并),所以计算起始地址时就要考虑已分配的情况。当需求分配的起始地址在一个allocated buffer上,就不需要再申请这个页。

  • 物理内存是按页分配的。
  • proc->buffers上不会有连续的两个free buffer。
  • 分配的起始地址按页向下对齐,PAGE_ALIGN((uintptr_t)buffer->data)。
  • 分配的结束地址如果和下一个buffer的起始地址在同一页,则按页向上对齐,(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK)。
  • 分配的结束地址如果在一个空闲页上,则按页向下对齐,PAGE_ALIGN((uintptr_t)buffer->data + buffer_size)。

通过一张图来描述Binder内存的变化。
binder_driver_buffer_1.png

 内存回收

同样看一下binder_free_buf()的源码。

static void binder_free_buf(struct binder_proc *proc,
                struct binder_buffer *buffer)
{
    size_t size, buffer_size;
    // 获取要释放的buffer的大小
    buffer_size = binder_buffer_size(proc, buffer);
    // size按指针字节数对齐
    size = ALIGN(buffer->data_size, sizeof(void *)) +
        ALIGN(buffer->offsets_size, sizeof(void *));
    ......
    // 释放物理页
    binder_update_page_range(proc, 0,
        (void *)PAGE_ALIGN((uintptr_t)buffer->data),
        (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK),
        NULL);
    // 将释放的buffer从allocated_buffers树上擦除
    rb_erase(&buffer->rb_node, &proc->allocated_buffers);
    buffer->free = 1;
    // 如果proc->buffers中下一个buffer为free,则合并到正在释放的buffer上
    if (!list_is_last(&buffer->entry, &proc->buffers)) {
        struct binder_buffer *next = list_entry(buffer->entry.next,
                        struct binder_buffer, entry);
        if (next->free) {
            rb_erase(&next->rb_node, &proc->free_buffers);
            binder_delete_free_buffer(proc, next);
        }
    }
    // 如果proc->buffers中上一个buffer为free,则合并释放的buffer到上一个buffer中
    if (proc->buffers.next != &buffer->entry) {
        struct binder_buffer *prev = list_entry(buffer->entry.prev,
                        struct binder_buffer, entry);
        if (prev->free) {
            binder_delete_free_buffer(proc, buffer);
            rb_erase(&prev->rb_node, &proc->free_buffers);
            buffer = prev;
        }
    }
    // 将处理好的buffer插入到free_buffers树中
    binder_insert_free_buffer(proc, buffer);
}

在内存回收的过程中,需要注意的还是边界地址的计算。与分配时相似,需要考虑释放的buffer与其他buffer在同一页上的情况。

  • 释放的开始地址按页向下对齐,PAGE_ALIGN((uintptr_t)buffer->data。
  • 释放的结束地址按页向上对齐,(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK)。

内存回收时还要进行相连free buffer的合并,找到合并的buffer后,使用binder_delete_free_buffer()将下一buffer删除掉。

static void binder_delete_free_buffer(struct binder_proc *proc,
                      struct binder_buffer *buffer)
{
    struct binder_buffer *prev, *next = NULL;
    int free_page_end = 1;
    int free_page_start = 1;
// 获取被删除buffer的前一个buffer
prev = list_entry(buffer-&gt;entry.prev, struct binder_buffer, entry);
// 如果被删除buffer与前一个buffer会共用到同一物理页,则不删除起始页,将free_page_start设置为0
if (buffer_end_page(prev) == buffer_start_page(buffer)) {
    free_page_start = 0;
    // 如果被删除buffer结束页也在这一页,则不会删除物理页,free_page_end也设为0
    if (buffer_end_page(prev) == buffer_end_page(buffer))
        free_page_end = 0;
    ......
}

if (!list_is_last(&amp;buffer-&gt;entry, &amp;proc-&gt;buffers)) {
    // 获取被删除buffer的下一个buffer
    next = list_entry(buffer-&gt;entry.next,
              struct binder_buffer, entry);
    // 如果被删除buffer与下一个buffer会共用到同一物理页,则不删除结束页
    if (buffer_start_page(next) == buffer_end_page(buffer)) {
        free_page_end = 0;
        // 如果被删除buffer起始页也在这一页,则不会删除物理页
        if (buffer_start_page(next) ==
            buffer_start_page(buffer))
            free_page_start = 0;
        ......
}
// 将buffer从proc-&gt;buffers列表中删除
list_del(&amp;buffer-&gt;entry);
if (free_page_start || free_page_end) {
    ......
    // 释放物理页,参考了free_page_start和free_page_end
    binder_update_page_range(proc, 0, free_page_start ?
        buffer_start_page(buffer) : buffer_end_page(buffer),
        (free_page_end ? buffer_end_page(buffer) :
        buffer_start_page(buffer)) + PAGE_SIZE, NULL);
}

}

最后用图来展示内存回收时buffer的变化。
binder_driver_buffer_2.png

转载自:https://segmentfault.com/a/1190000020846555

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值