Binder驱动之 binder_buffer的分配与回收

一 相关的数据结构

1.1 struct binder_proc中的相关成员

struct binder_proc {
    …
    struct list_head buffers; /*所有binder_buffer的链表,以起始地址大小生序排列,以方便相邻空闲的binder_buffer合并*/
    struct rb_root free_buffers; /*组织所有空闲的binder_buffer在该红黑树中,以所管理的地址空间大小为序,便于地址空间的分配*/
    struct rb_root allocated_buffers;  /*组织所有已分配的binder_buffer在该红黑树中,以地址大小为序,便于根据地址查找*/
    size_t free_async_space; /*空闲异步可用的地址空间*/

    struct page **pages; /*所有物理页指针数组,所有以分配的物理页在这里管理*/
    size_t buffer_size; /*mmap时请求的总地址空间大小*/
   ...
};

1.2 struct binder_buffer

struct binder_buffer {
    struct list_head entry; /* free and allocated entries by address,链接在proc->buffers所指链表中*/
    struct rb_node rb_node; /* free entry by size(空闲红黑树`free_buffers`以所管理的buffer大小组织)  */
                            /* or allocated entry by address  (已分配红黑树`allocated_buffers`以所管理buffer的虚拟地址大小组织)*/
    unsigned free:1;
    unsigned allow_user_free:1;
    unsigned async_transaction:1;
    unsigned debug_id:29;

    struct binder_transaction *transaction;

    struct binder_node *target_node;
    size_t data_size;
    size_t offsets_size;
    uint8_t data[0]; //数据内容的开始地址
};

二 地址空间分配

2.1 分配binder_buffer ---> binder_alloc_buff

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; /*记录data_size和offset_size在分别与指针大小对齐后的总大小*/

    if (proc->vma == NULL) {
        pr_err("%d: binder_alloc_buf, no vma\n",
               proc->pid);
        return NULL;
    }

    /*data_size,offsets_size分别按照指针长度对齐*/
    size = ALIGN(data_size, sizeof(void *)) +
        ALIGN(offsets_size, sizeof(void *));

    if (size < data_size || size < offsets_size) {
        binder_user_error("%d: got transaction with invalid size %zd-%zd\n",
                proc->pid, data_size, offsets_size);
        return NULL;
    }

    /*如果是异步的,还要检查异步空间是否充足*/
    if (is_async &&
        proc->free_async_space < size + sizeof(struct binder_buffer)) {
        binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
                 "%d: binder_alloc_buf size %zd failed, no async space left\n",
                  proc->pid, size);
        return NULL;
    }

  //在空闲的节点中,查找大小最合适的buffer,空闲节点是以大小为序组织在红黑树中的
    while (n) {
        buffer = rb_entry(n, struct binder_buffer, rb_node);
        BUG_ON(!buffer->free);
        buffer_size = binder_buffer_size(proc, buffer); //获取该节点buffer的大小

        if (size < buffer_size) {//节点的buffer大小比请求的大
            best_fit = n;
            n = n->rb_left;
        } else if (size > buffer_size)//节点的buffer大小比请求的小
            n = n->rb_right;
        else {//节点的buffer大小比请求的相等
            best_fit = n;
            break;
        }
    }//该循环结束后,best_fit指向空闲节点中,buffer的大小与请求大小最接近且满足请求大小的节点
    if (best_fit == NULL) {
        pr_err("%d: binder_alloc_buf size %zd failed, no address space\n",
            proc->pid, size);
        return NULL;
    }
    if (n == NULL) {//没有找到大小与请求大小正好的节点(节点拥有的地址空间本次分配后有剩余),将buffer和buffer_size修正为best_fit指向节点的地址和大小,如果找到大小相等的,这两个值已经是正确的了。
        buffer = rb_entry(best_fit, struct binder_buffer, rb_node);
        buffer_size = binder_buffer_size(proc, buffer);
    }

    binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
             "%d: binder_alloc_buf size %zd got buffer %p size %zd\n",
              proc->pid, size, buffer, buffer_size);

    /*该binder_buffer节点所管理的虚拟地址空间最后一页的起始虚拟地址*/
    has_page_addr =
        (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK);
    if (n == NULL) {/*节点所拥有的空间大于请求分配的空间,需重新计算*/
        if (size + sizeof(struct binder_buffer) + 4 >= buffer_size) /*节点空闲地址空间在本次分配后不足于容纳其他的binder_buffer*/
            buffer_size = size; /* no room for other buffers */  /*不需要新的binder_buffer来记录节点分配后剩余空间信息*/
        else
            buffer_size = size + sizeof(struct binder_buffer);  /*多申请一个binder_buffer,记录节点分配后剩余空间的状态*/
    }
    /*请求地址空间中最后一页的页末地址*/
    end_page_addr =
        (void *)PAGE_ALIGN((uintptr_t)buffer->data + buffer_size);
    if (end_page_addr > has_page_addr)
        end_page_addr = has_page_addr;
    /*分配物理页框,并和内核态地址和用户地址空间建立映射*/
    if (binder_update_page_range(proc, 1,
        /*这里从data开始进行页对齐的原因是:binder_buffer本身所在的页已经分配过物理页框了*/
        (void *)PAGE_ALIGN((uintptr_t)buffer->data), 
        end_page_addr, NULL))
        return NULL;

    /*从空闲buffer红黑树中移除该节点*/
    rb_erase(best_fit, &proc->free_buffers);
    buffer->free = 0;
    /*将分配的binder_buffer插入到已分配地址的红黑树中, 详见2.3*/
    binder_insert_allocated_buffer(proc, buffer);
    /* 如果找到节点所拥有的buffer大小超过请求分配的大小(外加sizeof(struct binder_buffer) + 4),
    *  则将节点剩余未分配的空间重新插入到空闲buffer的红黑树中
    */
    if (buffer_size != size) {
        struct binder_buffer *new_buffer = (void *)buffer->data + size;    
        list_add(&new_buffer->entry, &buffer->entry);/*将new_buffer插入到buffer之后*/
        new_buffer->free = 1;
        binder_insert_free_buffer(proc, new_buffer); /*详见2.3*/
    }
    binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
             "%d: binder_alloc_buf size %zd got %p\n",
              proc->pid, size, buffer);
    /*更新相关字段:data_size, offsets_size, is_async*/
    buffer->data_size = data_size;
    buffer->offsets_size = offsets_size;
    buffer->async_transaction = is_async;
    if (is_async) {//如果是异步的,还需要更新异步空间的大小。
        proc->free_async_space -= size + sizeof(struct binder_buffer);
        binder_debug(BINDER_DEBUG_BUFFER_ALLOC_ASYNC,
                 "%d: binder_alloc_buf size %zd async free %zd\n",
                  proc->pid, size, proc->free_async_space);
    }

    return buffer;
}

binder_alloc_buf首先将请求的data_sizeoffsets_size对齐后相加,求得本次请求分配空间的总大小。如果是异步的,还要进一步判断异步空间是否充足。然后在procfree_buffers红黑树中查找大小合适binder_buffer节点,该红黑树是以binder_buffer所拥有的地址空间的大小为序组织的。如果被分配节点的地址空间在分配完本次请求的大小后还能容纳一个binder_buffer + 4个字节的空间,则需重新创建一个binder_buffer用于管理分配后的剩余地址空间,此时请求binder_update_page_range的分配大小需额外增加一个binder_buffer的大小,否则不需要,然后将求得的所需地址空间大小进行页对齐后,调用binder_update_page_range分配物理页框,建立内核态和用户态的地址映射。紧接着将被分配节点从free_buffers红黑树中删除,更新空闲标识为0,然后插入到已分配红黑树allocated_buffers中,该红黑树以binder_buffer的地址大小为序组织的。如果被分配节点在分配完请求的地址空间大小后还有剩余,需将剩余的地址空间用一个新的binder_buffer管理起来,并插入到procbuffers链表及free_buffers红黑树中。最后更新分配节点的相关字段,如果申请的异步空间,还需更新异步剩余空间,就可以将分配节点地址返回给调用者了。

2.2 将空闲的binder_buffer插入到空闲红黑树(free_buffers)中

static void binder_insert_free_buffer(struct binder_proc *proc,
                      struct binder_buffer *new_buffer)
{
    struct rb_node **p = &proc->free_buffers.rb_node;
    struct rb_node *parent = NULL;
    struct binder_buffer *buffer;
    size_t buffer_size;
    size_t new_buffer_size;

    BUG_ON(!new_buffer->free);

    new_buffer_size = binder_buffer_size(proc, new_buffer);

    binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
             "%d: add free buffer, size %zd, at %p\n",
              proc->pid, new_buffer_size, new_buffer);
  /*查找合适的插入位置,该红黑树以buffer的大小为序组织*/
    while (*p) {
        parent = *p;
        buffer = rb_entry(parent, struct binder_buffer, rb_node);
        BUG_ON(!buffer->free);

        buffer_size = binder_buffer_size(proc, buffer);

        if (new_buffer_size < buffer_size)
            p = &parent->rb_left;
        else
            p = &parent->rb_right;
    }
    rb_link_node(&new_buffer->rb_node, parent, p);
    rb_insert_color(&new_buffer->rb_node, &proc->free_buffers);
}

2.3 将分配的binder_buffer插入到的已分配的红黑树(allocated_buffers)中:

static void binder_insert_allocated_buffer(struct binder_proc *proc,
                       struct binder_buffer *new_buffer)
{
    struct rb_node **p = &proc->allocated_buffers.rb_node;
    struct rb_node *parent = NULL;
    struct binder_buffer *buffer;

    BUG_ON(new_buffer->free);

  /*查找合适的插入位置,该红黑树是以binder_buffer的地址大小为序组织的*/
    while (*p) {
        parent = *p;
        buffer = rb_entry(parent, struct binder_buffer, rb_node);
        BUG_ON(buffer->free);

        if (new_buffer < buffer)
            p = &parent->rb_left;
        else if (new_buffer > buffer)
            p = &parent->rb_right;
        else
            BUG();
    }
    rb_link_node(&new_buffer->rb_node, parent, p);//插入
    rb_insert_color(&new_buffer->rb_node, &proc->allocated_buffers);
}

需要注意proc已分配的binder_buffer红黑树allocated_buffers是以binder_buffer的地址本身的大小为序组织的,而空闲binder_buffer的红黑树free_buffers则是以binder_buffer中所拥有的地址空间大小为序组织的。

三 地址空间会回收

3.1 释放地址空间binder_free_buf

static void binder_free_buf(struct binder_proc *proc,
                struct binder_buffer *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_debug(BINDER_DEBUG_BUFFER_ALLOC,
             "%d: binder_free_buf %p size %zd buffer_size %zd\n",
              proc->pid, buffer, size, buffer_size);

    BUG_ON(buffer->free);
    BUG_ON(size > buffer_size);
    BUG_ON(buffer->transaction != NULL);
    BUG_ON((void *)buffer < proc->buffer);
    BUG_ON((void *)buffer > proc->buffer + proc->buffer_size);

  /*如果是异步事务,要更新异步空闲地址的大小*/
    if (buffer->async_transaction) {
        proc->free_async_space += size + sizeof(struct binder_buffer);

        binder_debug(BINDER_DEBUG_BUFFER_ALLOC_ASYNC,
                 "%d: binder_free_buf size %zd async free %zd\n",
                  proc->pid, size, proc->free_async_space);
    }

  /*释放data所拥有的地址空间的物理页框,并取消内核和用户态对该物理页框的地址映射*/
    binder_update_page_range(proc, 0,
        (void *)PAGE_ALIGN((uintptr_t)buffer->data),
        (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK),
        NULL);
    /*将该binder_buffer从已分配红黑树中移除*/
    rb_erase(&buffer->rb_node, &proc->allocated_buffers);
    /*重新标识该buffer为空闲状态*/
    buffer->free = 1;

    /*如果不是最后节点,且后一个节点也是空闲状态,尝试将其合并*/
    if (!list_is_last(&buffer->entry, &proc->buffers)) {
        struct binder_buffer *next = list_entry(buffer->entry.next,
                        struct binder_buffer, entry);
        if (next->free) {
            /*从free_buffers红黑树中移除后一个节点*/
            rb_erase(&next->rb_node, &proc->free_buffers);
            /*删除后一个`binder_buffer`节点,以将其合并到当前`binder_buffer`中*/
            binder_delete_free_buffer(proc, next);
        }
    }
    /*如果不是第一个节点,且前一个节点也是空闲状态,尝试将其合并*/
    if (proc->buffers.next != &buffer->entry) {
        struct binder_buffer *prev = list_entry(buffer->entry.prev,
                        struct binder_buffer, entry);

        if (prev->free) {
            /*删除当前`binder_buffer`,以合并到前一个`binder_buffer`中*/
            binder_delete_free_buffer(proc, buffer);
            /*从free_buffers红黑树中移除前一个节点*/
            rb_erase(&prev->rb_node, &proc->free_buffers);
            buffer = prev;
        }
    }
    /*将新空闲节点插入free_buffers红黑树中*/
    binder_insert_free_buffer(proc, buffer);
}

binder_free_buf的功能是释放一个binder_buffer所管理的地址空间,具体过程是:它首先释放binder_bufferdata域所占据的地址空间,解除地址内核及用户态的地址映射;接着查看其之后是否也处于空闲状态,如果是的话,合并到当前节点,并尝试释放其后binder_buffer节点所占据的物理页。接着查看前一个的节点,是否处于空闲状态,如果是的话,就将自己合并到前一个节点中,并尝试释放其本身binder_buffer节点所占据的物理页;最后将合并后的节点(如果前后节点是空闲的话)插入到procfree_buffer红黑树中。

3.2 从buffers链表中删除一个空闲节点 —> binder_delete_free_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;

    BUG_ON(proc->buffers.next == &buffer->entry);/*确定被删除的节点不是buffers中的唯一一个节点*/
    prev = list_entry(buffer->entry.prev, struct binder_buffer, entry);/*取出被删除节点在buffers链表中的前一个节点*/
    BUG_ON(!prev->free);/*确保前一个节点是空闲状态,因为这样节点删除后,其管理的地址空间才能合并到前一个节点中*/
    if (buffer_end_page(prev) == buffer_start_page(buffer)) {
        /* 被删除节点的起始物理页框地址与前一个节点的结束地址(不包括data)所在物理页框是同一个,
        *  此时不能释放这个物理页框,将free_page_start置为0 */
        free_page_start = 0;
        if (buffer_end_page(prev) == buffer_end_page(buffer))
        /* 被删除节点的结束地址(不包括data)物理页框地址与前一个binder_buffer节点的结束地址(不包括data)所在物理页框是同一个,
        *  此时不能释放这个物理页框,将free_page_end置为0 */
            free_page_end = 0;
        binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
                 "%d: merge free, buffer %p share page with %p\n",
                  proc->pid, buffer, prev);
    }

    if (!list_is_last(&buffer->entry, &proc->buffers)) {/*不是buffers中的最后一个节点*/
        next = list_entry(buffer->entry.next, /*取出被删除节点在buffers链表中的下一个节点*/
                  struct binder_buffer, entry);
        if (buffer_start_page(next) == buffer_end_page(buffer)) {
        /*被删除节点的结束地址(不包括data)物理页框地址与后一个binder_buffer节点的结束地址所在物理页框是同一个,
        *  此时不能释放这个物理页框,将free_page_end置为0 */
            free_page_end = 0;
            if (buffer_start_page(next) == buffer_start_page(buffer))
             /* 被删除节点的结束地址(不包括data)物理页框地址与后一个binder_buffer节点的开始地址所在物理页框是同一个,
              * 此时不能释放这个物理页框,将free_page_start置为0 */
                free_page_start = 0;
            binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
                     "%d: merge free, buffer %p share page with %p\n",
                      proc->pid, buffer, prev);
        }
    }
    /*在proc的buffers中删除该binder_buffer节点*/
    list_del(&buffer->entry);
    if (free_page_start || free_page_end) {
        binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
                 "%d: merge free, buffer %p do not share page%s%s with %p or %p\n",
                 proc->pid, buffer, free_page_start ? "" : " end",
                 free_page_end ? "" : " start", prev, next);
        /*解除用户态和内核态地址空间对物理页的映射,并释放物理页框*/
        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);
    }
}
  • binder_delete_free_buffer该函数的主要功能是从proc->buffers链表中删除一个空闲的binder_buffer, 在删除一个节点前,要先确保被删除节点的前一个节点是处在空闲状态的,这样才有可能将被删除节点的所管理的地址空间合并到前一个节点。
  • 因为一个binder_buffer节点可能跨物理页框存储,所以用了两个变量free_page_start,free_page_end来表示是否能释放binder_buffer开始地址和结束地址所在页面。
    • free_page_start: 标识是否能将被删除节点开始地址所在页面释放;如果前一个节点的结束地址或者后一个节点的开始地址也落在了该物理页面,则不能释放该物理页框,值置为0。
    • free_page_end: 标识是否能将被删除节点结束地址所在页面释放,注意包括data域的地址空间;如果前一个节点的结束地址或者后一个节点的开始地址也落在了该物理页面,则不能释放该物理页框,值置为0。
  • 最后根据free_page_start,free_page_end决定如何释放物理页:
    • free_page_startfree_page_end都为0: 不需要释放物理页,因为binder_buffer所在的物理页框还在被它之前或者之后节点使用。
    • free_page_start为0,free_page_end为1: 不能释放binder_buffer开始地址所在页框,但是需要释放结束地址所在页框 ——— 此时binder_buffer是跨页框存放的。释放的地址空间范围是binder_buffer结束地址所在页框的起始到结束之间的一页地址空间。
    • free_page_start为1,free_page_end为0: 释放binder_buffer开始地址所在页框,但是不能释放结束地址所在页框 ——— 此时binder_buffer是跨页框存放的。释放的地址空间范围是binder_buffer开始地址所在页框的起始到结束之间的一页地址空间。
    • free_page_start为1,free_page_end为1: 释放binder_buffer开始地址和结束地址所在页框 ——— 此时binder_buffer可能是在同一个页框中,也可能跨页框存放的。如果没有跨页,则释放的地址空间范围是binder_buffer所在那个页框的起始到结束之间的一页地址空间;如果是跨页的,则释放的是binder_buffer地址开始所在页框加上binder_buffer结束地址所在页框,两个页框的地址空间。

3.2.1 计算binder_buffer的起始及结束地址所在页的页面起始地址。

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

推荐阅读更多精彩内容