Binder驱动 映射内存分配

参考源码

binder.h
binder.c
page.h
kernel.h
vmalloc.c



binder_proc->buffer

指向【内核虚拟空间】管理结构体vm_struct vm的开始地址

binder_proc->buffer_size

vm的大小,等于进程调用mmap("dev/binder")时请求分配的虚拟空间大小。

binder_proc->pages

物理页数组,用于存放物理页管理结构体page,通过数组下标与虚拟空间形成相对位置关系

linxu将物理内存划分成大小为PAGE_SIZE的块进行管理,称为物理页,page则是物理页的管理结构体。

  • pages的下标

    通过数组下标与虚拟空间形成相对位置关系:

        PAGE_SIZE=4k时
    
        vm[0k ~  4k] = page[0]
        vm[4k ~  8k] = page[1]
        vm[8k ~ 12k] = page[2]
    

    虚拟地址可以通过计算,查找所在的page在数组中的下标。

        index = (虚拟地址 - binder_proc->buffer(vm起始地址)) / PAGE_SIZE
        page = pages[index]
    
  • 数组大小

    请求分配的虚拟空间大小所需要的物理页数量,对应page结构体数量所需要请求的物理内存大小。

        所需内存大小 = page结构体大小 * (虚拟空间大小 / PAGE_SIZE)
    
  • PAGE_SIZE

    PAGE_SIZE一般=4k。
    从ARMv8架构开始,可以启用CONFIG_ARM64_64K_PAGES使PAGE_SIZE=64k。

    #ifdef CONFIG_ARM64_64K_PAGES
    #define PAGE_SHIFT      16
    #else
    #define PAGE_SHIFT      12
    #endif
    #define PAGE_SIZE       (_AC(1,UL) << PAGE_SHIFT) 
    #define PAGE_MASK       (~(PAGE_SIZE-1))
    
  • PAGE_MASK

    虚拟地址通过&PAGE_MASK,可以获取所在物理页的起始虚拟地址。

        PAGE_SIZE=4k时
        
        PAGE_SIZE = 0000-0000 0000-0000 0001-0000 0000-0000
        PAGE_MASK = 1111-1111 1111-1111 1111-0000 0000-0000
    


binder_mmap()

进程在打开binder驱动后,通过mmap("dev/binder")指定内存映射区域的大小。mmap系统调用过程中,会调用binder驱动的binder_mmap()函数。

  • 申请vm

    通过get_vm_area()申请与【用户虚拟空间】vm_area_struct vma大小相同的vm,并记录vm的起始地址到proc->buffer

    get_vm_area()__get_vm_area_node()函数的调用包装:

    • 通过kmalloc_node()申请vm_struct结构体的所需内存;
    • 通过alloc_vmap_area()在vmalloc区域中寻找合适的区域,获取返回的vmap_area结构体;
    • 通过setup_vmalloc_vm()vmap_area的数据同步到vm_struct中;
    • 最后返回vm_struct

    get_vm_area()vmalloc()的区别:
    vmalloc()在调用__get_vm_area_node()获取vm_struct之后,还通过__vmalloc_area_node()进行了物理页映射。

  • 初始化pages数组

    通过kzalloc()申请pages数组所需的内存。

  • 构建首个binder_buffer

    proc->buffer(即vm的起始地址)创建首个binder_buffer,并添加到空闲队列binder->free_buffers中。
    这使得当前进程binder通信的可用负载容量等于这个binder_buffer负载容量

    在创建binder_buffer之前,因为proc->buffer并没有映射到物理页,需要先进行映射。映射过程封装在binder_update_page_range()函数,此处通过该函数申请了大小为PAGE_SIZE的物理页映射。

static int binder_mmap(
        struct file *filp,              // 进程打开binder驱动时分配的file结构体
        struct vm_area_struct *vma      // mmap过程中分配的用户虚拟空间
) { 
        
    struct vm_struct *area;
    struct binder_proc *proc = filp->private_data;
    struct binder_buffer *buffer;
    
    /*
     * 限制vma大小,最大4M
     */
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;
        
    /*
     * 通过get_vm_area()获取vm
     */
    area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
    
    /*
     * proc->buffer 指向vm的开始地址
     */
    proc->buffer = area->addr;
    
    /*
     * proc->user_buffer_offset 记录 vma/vm 起始地址的差值
     * 通过差值,可以快速计算 vma/vm 中对应的虚拟内存
     */
    proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
   
    /*
     * 通过kzalloc()申请pages数组所需内存
     */
    proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
    
    /*
     * proc->buffer_size 记录vm的大小
     */
    proc->buffer_size = vma->vm_end - vma->vm_start;
    
    /*
     * 覆盖vma的钩子函数,其中:
     *      .fault:不执行缺页处理,直接返回VM_FAULT_SIGBUS,进程在收到SIGBUS信号时会终止
     * 这是因为binder驱动在发生虚拟内存的读写前,会先处理好相关的物理页映射,正常情况下不会产生缺页异常
     */
    vma->vm_ops = &binder_vm_ops;
    
    /*
     * 为首个binder_buffer 所在的虚拟空间映射物理页
     * page = pages[0]
     */
    if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {
        goto err_alloc_small_buf_failed;
    }
    
    /*
     * 初始化binder_buffer
     */
    buffer = proc->buffer;
    
    /*
     * 初始化proc->buffers链表
     * 将binder_buffer添加到proc->buffers链表
     */
    INIT_LIST_HEAD(&proc->buffers);
    list_add(&buffer->entry, &proc->buffers);
    
    /*
     * 将binder_buffer标记为空闲
     * 将binder_buffer添加到free_buffers红黑树
     */ 
    buffer->free = 1;
    binder_insert_free_buffer(proc, buffer);
    
    /*
     * 更新异步消息的空闲负载容量,大小等于同步消息空闲负载容量的一半
     */
    proc->free_async_space = proc->buffer_size / 2;
}




binder_update_page_range()

为指定的虚拟空间建立物理内存映射,或释放物理内存映射。

  • vma区间

    传入参数是vm中的区间,函数中通过proc->user_buffer_offset找到对应的vma区间。

  • 建立与释放映射

    在指定虚拟空间区间执行步进为PAGE_SIZE的for循环,在循环中:

    • 如果是建立映射,则创建对应区间所在的page,并建立与vma/vm的映射;
    • 如果是释放映射,则释放对应区间所在的pagevma/vm的映射,并释销毁page
static int binder_update_page_range(
        struct binder_proc *proc,       // binder_proc
        int allocate,                   // 0: 释放映射 1: 建立映射
        void *start,                    // vm区间起始地址
        void *end,                      // vm区间结尾地址
        struct vm_area_struct *vma      // vma
) {
        
    void *page_addr;                    // 临时变量,当前操作的vm起始地址
    unsigned long user_page_addr;       // 临时变量,当前操作的vma起始地址
    
    struct vm_struct tmp_area;
    struct page **page;

    /*
     * 省略源码:
     * - 校验逻辑
     * - vma获取和校验逻辑
     */
     
    /*
     * 执行释放逻辑,跳到free_range
     */
    if (allocate == 0)
        goto free_range;

    /*
     * for循环,创建对应区间所在的page,并建立映射
     */
    for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
        int ret;
        
        /*
         * 获取pages数组中对应位置的page结构体
         */
        page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
        
        /*
         * 通过alloc_page()申请物理页
         */
        *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);

        /*
         * 通过map_vm_area()建立内核虚拟空间和物理内存的映射
         *
         * 在映射区间的最后添加大小为PAGE_SIZE的保护页
         *
         * 当进程读写操作超出映射区域时,由于保护页没有绑定实际物理内存,
         * 会抛出缺页异常 page fault,而binder驱动对缺页异常的处理是直接返回VM_FAULT_SIGBUS,
         * 进程就会收到SIGBUS信号,默认处理是终止当前进程。
         */
        tmp_area.addr = page_addr;
        tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
        ret = map_vm_area(&tmp_area, PAGE_KERNEL, page);
    
        /*
         * 计算vma对应区间的起始地址
         */
        user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;
        
        /*
         * 通过vm_insert_page()建立用户虚拟空间和物理内存的映射
         */
        ret = vm_insert_page(vma, user_page_addr, page[0]);
    }
    return 0;


free_range:
    /*
     * 释放逻辑
     */
    for (page_addr = end - PAGE_SIZE; page_addr >= start; page_addr -= PAGE_SIZE) {
    
        /*
         * 获取pages数组中对应位置的page结构体
         */
        page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
        
        /*
         * 通过zap_page_range()解除vma区间的物理内存映射
         */ 
        if (vma)
            zap_page_range(vma, (uintptr_t)page_addr + proc->user_buffer_offset, PAGE_SIZE, NULL);
            
err_vm_insert_page_failed:
        /*
         * 通过unmap_kernel_range()解除vm区间的物理内存映射
         */ 
        unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);

err_map_kernel_failed:
        /*
         * 销毁page
         */ 
        __free_page(*page);
        *page = NULL;
        
err_alloc_page_failed:
        ;
    }
    return -ENOMEM;
}

binder_mmap.png



binder_insert_free_buffer()

将指定的binder_buffer插入到binder_proc->free_buffers红黑树,索引为buffer的负载容量,通过binder_buffer_size()计算。

static void binder_insert_free_buffer(
        struct binder_proc *proc,           // binder_proc
        struct binder_buffer *new_buffer    // buffer
) {

    /*
     * 计算binder_buffer的负载大小
     */
    size_t new_buffer_size;
    new_buffer_size = binder_buffer_size(proc, new_buffer);
    
    /*
     * 省略源码:
     * - 插入到红黑树合适的位置
     */
}




binder_buffer_size()

计算binder_buffer的负载容量。

  • binder_buffer负载空间

    binder_buffer的负载空间指的是在proc->buffer中,当前binder_buffer到下一个binder_buffer之间的空间。binder_buffer本身没有记录负载空间的信息,通过结构体尾端的柔性数组uint8_t data[0]充当负载空间的起始地址。

  • 柔性数组
      在一个结构体的最后,申明一个长度为0的数组,就可以使得这个结构体是可变长的。
      对于编译器来说,此时长度为0的数组并不占用空间。
      因为数组名本身不占空间,它只是一个偏移量, 数组名这个符号本身代表了一个不可修改的地址常量。
        
      优点:
      比起在结构体中声明一个指针变量、再进行动态分 配的办法,这种方法效率要高。
      因为在访问数组内容时,不需要间接访问,避免了两次访存。
    
      缺点:
      在结构体中,数组为0的数组必须在最后声明,使用上有一定限制。
    

负载容量的大小不能通过binder_buffer直接获取,需要通过计算获得。

static size_t binder_buffer_size(
        struct binder_proc *proc,       // binder_proc
        struct binder_buffer *buffer    // buffer
) {
    
    /*
     * binder->buffers的末尾项时:
     * binder->buffer末尾地址 - binder_buffer->data
     */
    if (list_is_last(&buffer->entry, &proc->buffers))
        return proc->buffer + proc->buffer_size - (void *)buffer->data;
        
    /*
     * binder->buffers的非末尾项时:
     * binder_buffer(next) - binder_buffer->data
     */
    return (size_t)list_entry(buffer->entry.next, struct binder_buffer, entry) - (size_t)buffer->data;
}
binder_buffer_size.png

其实可以将虚拟空间看成一个容器,binder_buffer看成容器内分割空间的隔板,隔板与隔板之间就用于存放数据的负载空间了。



binder_alloc_buf()

binder_transaction()跨进程传输数据的过程中,为传输的数据分配一个负载空间大小合适的binder_buffer

  • 逻辑流程

    函数的逻辑分为2个部分,查找合适的空闲binder_buffer和分割binder_buffer

  • 查找负载空间合适大小的空闲binder_buffer

    proc->free_buffers红黑树中,查找负载空间满足存放传输数据、且最接近传输数据大小的空闲binder_buffer

    proc->free_buffers红黑树在mmap("def/binder")过程中添加了首个binder_buffer。首次查找时,则会命中这个binder_buffer

static struct binder_buffer *binder_alloc_buf(
        struct binder_proc *proc,   // binder_proc
        size_t data_size,           // 传输的普通数据的大小
        size_t offsets_size,        // 传输的binder数据的大小
        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;

    /*
     * 计算传输数据的总大小
     *
     * 对数据进行字节对齐
     * ALIGN:字节对齐(void指针:32-4bit,64-8bit)
     */
    size = ALIGN(data_size, sizeof(void *)) + ALIGN(offsets_size, sizeof(void *));

    /* 
     * 检查异步任务的空闲负载容量是否满足所需传输需求
     * 传输数据所需容量 = binder_buffer结构体大小 + 传输数据大小
     *
     * binder驱动定义:
     *      进程异步任务的空闲负载容量 = 当前总空闲负载容量 / 2
     * 所以要做额外检测。
     *
     * 空闲容量满足要求,也不一定可以成功获取binder_buffer,
     * 如果碎片化严重,可能会不存在大容量的binder_buffer。
     */
    if (is_async && proc->free_async_space < size + sizeof(struct binder_buffer)) {
        return NULL;
    }

    /*
     * 遍历binder->free_buffers,获取合适的binder_buffer
     */
    while (n) {
        buffer = rb_entry(n, struct binder_buffer, rb_node);
        buffer_size = binder_buffer_size(proc, 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;
        }
    }
    
    /*
     * 没有合适的binder_buffer
     * 正常情况可能是因为数据过大,也可能是因为碎片化
     */
    if (best_fit == NULL) {
        return NULL;
    }
    
    /*
     * binder_buffer、binder_size 重新定位best_fit对应的项
     * 
     * n == null,表示循环不是通过break结束的,而是遍历至n == null结束的,
     * 此时 binder_buffer、binder_size 都是循环中的临时项,所以需要重新定位到best_fit对应的项
     */
    if (n == NULL) {
        buffer = rb_entry(best_fit, struct binder_buffer, rb_node);
        buffer_size = binder_buffer_size(proc, buffer);
    }
    
    /*
     * -----------------------
     * 已找到合适的binder_buffer
     * -----------------------
     */
     
    ...
  • 分割binder_buffer

    尝试对查找到的binder_buffer进行分割,减少内存消耗。

    查找算法找出的binder_buffer负载空间一般情况下会比传输数据的体积要大。例如首次查找时,找到的首个binder_buffer负载空间几乎等于mmap的内存大小(需要减去binder_buffer的体积)。

    分割过程如上诉的比喻一样,往容器中插入隔板。
    在原来的binder_buffer的负载空间中满足传输要求大小的位置后面,插入一个新的binder_buffer

    最后,还需要为分割出来的binder_buffer负载空间与新加入的binder_buffer分配物理内存。

binder_buffer_split.png
    ...
    
    /*
     * -----------------------
     * 已找到合适的binder_buffer
     * -----------------------
     */
     
    /*
     * 负载空间 需要建立映射的最后一个物理页的下边界
     *
     * 通过负载空间的末尾地址,计算出负载空间末尾所在物理页的起始虚拟地址
     * 在此地址前的虚拟空间,需要在本次处理中进行物理页映射
     * 
     * 负载空间的末尾地址有2种情况:
     * 1. 下一个binder_buffer的起点
     *    意味虚拟空间的末尾地址已映射物理页,
     *    所以通过[&PAGE_MASK],将内存地址在物理页中的页内偏移部分设置位0,消除页内偏移,
     *    以定位到所在物理页的上边界
     *
     * 2. vma末尾地址:
     *    vma末尾地址应该是物理页对齐的,此时内存地址等于最后的物理页下边界
     *    假如不是对齐,由于binder->pages申请内存的逻辑,放不下最后的没对齐page结构体,
     *    运行到物理页映射的阶段会导致pages数组越界
     * 
     * 此时 buffer_size = [binder_buffer负载容量]
     */
    has_page_addr = (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK);
    
    ...
has_page_addr.png
    ...
    
    /*
     * 计算所需映射物理内存的大小,保存到buffer_size
     *
     * n == null 的情况,表示binder_buffer [负载容量 > 传输数据],尝试分割
     * 
     * 负载空间需要满足填充传输数据后,还可以存放新的binder_buffer结构体,
     * 如果分割后剩余空间不满足存放新的binder_buffer结构体,那么放弃分割
     */ 
    if (n == NULL) {
        /*
         * 是否能放得下新的binder_buffer结构体
         *
         * +4用于新的binder_buffer负载检测,
         * 如果新的binder_buffer结构体负载空间比4还小,那么也没有分割的必要
         *
         * 不太清楚4bit的空间能干什么...
         */
        if (size + sizeof(struct binder_buffer) + 4 >= buffer_size)
            /*
             * 放不下
             * 需要分配的物理内存大小 = 数据大小
             */
            buffer_size = size; /* no room for other buffers */
        else
            /*
             * 放得下
             * 需要分配的物理内存大小 = 数据大小 + buffer结构体大小
             */
            buffer_size = size + sizeof(struct binder_buffer);
    }
    
    /*
     * 实际所需内存 需要建立映射的最后一个物理页的下边界
     */
    end_page_addr = (void *)PAGE_ALIGN((uintptr_t)buffer->data + buffer_size);
   
    ... 
end_page_addr.png
    ...
    
    /*
     * 防止越界,具体看图
     */
    if (end_page_addr > has_page_addr)
        end_page_addr = has_page_addr;
    
    ...    
over_addr.png

    ...
    /*
     * 通过binder_update_page_range()进行物理页映射
     * 
     * 起始地址也进行PAGE对齐,得到binder_buffer所在物理页的下边界,
     * 逻辑一样,binder_buffer所在的内存空间必然是已映射物理页的
     */
    if (binder_update_page_range(proc, 1, (void *)PAGE_ALIGN((uintptr_t)buffer->data), end_page_addr, NULL))
        return NULL;
        
    /*
     * -------------
     * 已完成物理页映射
     * -------------
     */
     
    ...
addr.png
    ...

    /*
     * -------------
     * 已完成物理页映射
     * -------------
     */

    /*
     * 设置binder_buffer使用状态
     * 
     * 移出binder_proc->free_buffers
     * binder_buffer->free设置0(使用中)
     * 加入binder_proc->allocated_buffers
     * 
     * binder_insert_allocated_buffer()
     * 只是简单的红黑树操作,排序也没有逻辑上的意义,此处忽略解析
     */
    rb_erase(best_fit, &proc->free_buffers);
    buffer->free = 0;
    binder_insert_allocated_buffer(proc, buffer);
    
    /*
     * 如果buffer_size不等于size,说明需要分割,
     * 需要创建新的binder_buffer
     * 
     * 注意在binder->buffer中,
     * 新的binder_buffer添加到被分割的binder_buffer之后,
     * 保持链表的binder_buffer元素在逻辑内存上的顺序
     */
    if (buffer_size != size) {
        struct binder_buffer *new_buffer = (void *)buffer->data + size;
    
        list_add(&new_buffer->entry, &buffer->entry);
        new_buffer->free = 1;
        binder_insert_free_buffer(proc, new_buffer);
    }

    /*
     * 设置binder_buffer数据
     * 
     * buffer->data_size            设置为传输普通数据的大小
     * buffer->offsets_size         设置为传输binder数据的大小
     * buffer->async_transaction    设置是否为异步消息
     */
    buffer->data_size = data_size;
    buffer->offsets_size = offsets_size;
    buffer->async_transaction = is_async;
    
    /*
     * 如果是异步任务,还需要更新异步任务的空闲负载容量
     * 
     * 减少大小 = binder_buffer结构体 + 传输数据的大小
     */
    if (is_async) {
        proc->free_async_space -= size + sizeof(struct binder_buffer);
    }
    
    return buffer;
}




binder_free_buf()

回收目标binder_buffer。

  • 逻辑流程

    函数的逻辑分为2个部分,解除负载空间内存映射和合并空闲的binder_buffer

  • 解除负载空间内存映射

    释放binder_buffer负载空间已映射的物理页。

  • 合并空闲的binder_buffer

    分割的逆向过程,尝试合并两端的binder_buffer,以还原为负载空间较大的binder_buffer

    binder_buffer是在逻辑内存上有序的,顺序就是binder_proc->buffers链表的顺序,binder_buffer的合并即是尝试合并链表中的前后项。

static void binder_free_buf(
        struct binder_proc *proc,       // binder_proc
        struct binder_buffer *buffer    // 需要释放的binder_buffer
) {
    
    size_t size, buffer_size;
    
    /*
     * 获取负载容量
     */
    buffer_size = binder_buffer_size(proc, buffer);

    /*
     * 获取传输数据大小
     *
     * 对数据进行字节对齐
     */
    size = ALIGN(buffer->data_size, sizeof(void *)) + ALIGN(buffer->offsets_size, sizeof(void *));

    /*
     * 如果是异步任务,还需要更新异步任务的空闲负载容量
     * 
     * 增加大小 = binder_buffer结构体 + 传输数据的大小
     * binder_alloc_buf()的逆向过程
     */
    if (buffer->async_transaction) {
        proc->free_async_space += size + sizeof(struct binder_buffer);
    }

    /*
     * 通过binder_update_page_range()释放物理页映射
     * 
     * 释放负载空间的除 边界物理页 外的物理页映射
     * - 起始地址: 负载空间映射的起始物理页的下边界
     * - 结尾地址: 负载空间映射的末尾物理页的上边界
     *
     * 因为边界物理页必然被binder_buffer使用中,所以先处理确定可以释放的区间
     * - 负载空间起始地址:不是物理页边界的情况下,所在物理页被当前binder_buffer占用
     * - 负载空间结尾地址:不是物理页边界的情况下,所在物理页被下一个binder_buffer占用
     */
    binder_update_page_range(proc, 0,
        (void *)PAGE_ALIGN((uintptr_t)buffer->data),
        (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK),
        NULL);
    
    /*
     * 设置buffer使用状态
     * 
     * 移出binder_proc->allocated_buffers
     * binder_buffer->free设置1(空闲)
     * 
     * 暂时不加入binder_proc->free_buffers,合并过程可能会销毁这个binder_buffer
     */
    rb_erase(&buffer->rb_node, &proc->allocated_buffers);
    buffer->free = 1;
    
    /*
     * 尝试合并下一个binder_buffer
     * 如果buffer是逻辑内存的最后一项,则跳过这个流程
     */
    if (!list_is_last(&buffer->entry, &proc->buffers)) {
        /*
         * 找到下一个binder_buffer next
         */
        struct binder_buffer *next = list_entry(buffer->entry.next, truct binder_buffer, entry);
        
        /*
         * 如果next是空闲的,则通过移除next进行合并
         */
        if (next->free) {
            /*
             * 先把next移出proc->free_buffers,
             * 然后通过binder_delete_free_buffer()销毁next
             */
            rb_erase(&next->rb_node, &proc->free_buffers);
            binder_delete_free_buffer(proc, next);
        }
    }
    
    /*
     * 尝试合并上一个binder_buffer
     * 如果是buffer是逻辑内存的第一项,则跳过这个流程
     */
    if (proc->buffers.next != &buffer->entry) {
        /*
         * 找到上一个binder_buffer prev
         */
        struct binder_buffer *prev = list_entry(buffer->entry.prev, struct binder_buffer, entry);
        
        /*
         * 如果prev是空闲的,则通过移除buffer进行合并
         */
        if (prev->free) {
            /*
             * 通过binder_delete_free_buffer()销毁buffer
             */
            binder_delete_free_buffer(proc, buffer);
            
            /*
             * 合并后,prev的负载容量发生了改变,
             * 所以要移出proc->free_buffers,后面重新插入
             */
            rb_erase(&prev->rb_node, &proc->free_buffers);
            buffer = prev;
        }
    }
    
    /*
     * 把合并后的buffer添加到binder_proc->free_buffers
     */
    binder_insert_free_buffer(proc, buffer);
}




binder_delete_free_buffer()

销毁空闲的binder_buffer,并判断是否需要解除所在物理页的映射。

/*
 * 专用函数,获取传入binder_buffer的起始地址,所在的物理页的起始地址
 */
static void *buffer_start_page(struct binder_buffer *buffer) {
    return (void *)((uintptr_t)buffer & PAGE_MASK);
}

/*
 * 专用函数,获取传入binder_buffer的结束地址,所在的物理页的起始地址
 */
static void *buffer_end_page(struct binder_buffer *buffer) {
    return (void *)(((uintptr_t)(buffer + 1) - 1) & PAGE_MASK);
}

static void binder_delete_free_buffer(
        struct binder_proc *proc,           // binder_proc
        struct binder_buffer *buffer        // 需要销毁的binder_buffer
) {
    
    struct binder_buffer *prev, *next = NULL;
    int free_page_end = 1;          // 是否释放起始地址所在的物理页
    int free_page_start = 1;        // 是否释放结尾地址所在的物理页

    /*
     * 前向对比
     * 获取前一个binder_buffer prev
     *
     * 这里不需要做是否是首个binder_buffer的校验,不可能出现这样的情况
     */
    prev = list_entry(buffer->entry.prev, struct binder_buffer, entry);
    
    /*
     * prev末尾地址是否与buffer的起始地址在同一个物理页,是则不要释放该物理页
     */
    if (buffer_end_page(prev) == buffer_start_page(buffer)) {
        free_page_start = 0;
        
        /*
         * prev末尾地址是否与buffer的末尾地址在同一个物理页,是则不要释放该物理页
        */
        if (buffer_end_page(prev) == buffer_end_page(buffer))
            free_page_end = 0;
    }

    /* 
     * 校验是否末尾项,是则不进行后向对比
     * 原理与前向对比一致
     */
    if (!list_is_last(&buffer->entry, &proc->buffers)) {
        next = list_entry(buffer->entry.next, struct binder_buffer, entry);
        if (buffer_start_page(next) == buffer_end_page(buffer)) {
            free_page_end = 0;
            if (buffer_start_page(next) == buffer_start_page(buffer))
                free_page_start = 0;
        }
    }
    
    /*
     * 将buffer从proc->buffers中移除
     */
    list_del(&buffer->entry);
    
    /*
     * 如果需要释放物理页,则通过binder_update_page_range()处理
     * 根据 free_page_start / free_page_end 进行了物理页的边界处理
     */
    if (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);
    }
}




最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,757评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,478评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,540评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,593评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,903评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,329评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,659评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,383评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,055评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,337评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,864评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,227评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,820评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,999评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,750评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,365评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,260评论 2 258

推荐阅读更多精彩内容