安卓源码解析:Binder进程间通讯机制(3)-内核空间Binder驱动详解(Binder驱动内存管理)

目录

  1. Binder进程间通讯机制(1)-简况
  2. Binder进程间通讯机制(2)-内核空间Binder驱动详解(基础数据结构)
  3. Binder进程间通讯机制(3)-内核空间Binder驱动详解(Binder驱动内存管理)
  4. Binder进程间通讯机制(4)-内核空间Binder驱动详解(Binder驱动库 C/C++接口简介)
  5. Binder进程间通讯机制(5)-从ServiceManager的角度分析IPC原理

Binder初始化

binder初始化从kernel/drivers/staging/android/binder.cbinder_init方法开始
https://www.androidos.net.cn/androidkernel/2.6.29/xref/drivers/staging/android/binder.c

static int __init binder_init(void)
{
    int ret;
    //创建所有使用了binder进程间通信的进程的文件目录
    binder_proc_dir_entry_root = proc_mkdir("binder", NULL);
    if (binder_proc_dir_entry_root)
        binder_proc_dir_entry_proc = proc_mkdir("proc", binder_proc_dir_entry_root);
    //创建binder设备
    ret = misc_register(&binder_miscdev);
    if (binder_proc_dir_entry_root) {
        create_proc_read_entry("state", S_IRUGO, binder_proc_dir_entry_root, binder_read_proc_state, NULL);
        create_proc_read_entry("stats", S_IRUGO, binder_proc_dir_entry_root, binder_read_proc_stats, NULL);
        create_proc_read_entry("transactions", S_IRUGO, binder_proc_dir_entry_root, binder_read_proc_transactions, NULL);
        create_proc_read_entry("transaction_log", S_IRUGO, binder_proc_dir_entry_root, binder_read_proc_transaction_log, &binder_transaction_log);
        create_proc_read_entry("failed_transaction_log", S_IRUGO, binder_proc_dir_entry_root, binder_read_proc_transaction_log, &binder_transaction_log_failed);
    }
    return ret;
}

函数binder_proc_dir_entry_root = proc_mkdir("binder", NULL); 创建/proc/binder/proc目录,每个使用了Binder进程间通讯机制的进程都在这个目录下有对应的文件,通过这个文件就可以读取到各个进程的Binder线程池,实体对象,引用对象以及内核缓冲区.

函数binder_proc_dir_entry_proc = proc_mkdir("proc", binder_proc_dir_entry_root); 用来创建Binder设备,使用结构体miscdevice 对象 binder_miscdev 定义.

static struct file_operations binder_fops = {
    .owner = THIS_MODULE,
    .poll = binder_poll,
    .unlocked_ioctl = binder_ioctl,  //管理缓冲区操作
    .mmap = binder_mmap,  //内存映射
    .open = binder_open,  //打开binder设备
    .flush = binder_flush,
    .release = binder_release,
};
static struct miscdevice binder_miscdev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "binder",
    .fops = &binder_fops //定义了binder的所有操作类型
};

binder的所有操作定义在&binder_fops即全局变量结构体file_operations 中,包括binder_open(打开Binder设备),binder_mmap(内存映射),binder_ioctl(binder缓冲区输入指令进行管理)


binder_open

这个函数用来让一个进程需要和binder进行通信的时候,需要将自己注册到/dev/binder文件目录下,并返回文件描述符,让binder可以通过文件目录访问到注册在binder中的进程.

static int binder_open(struct inode *nodp, struct file *filp)
{
    //描述应用程序进程
    struct binder_proc *proc;

    if (binder_debug_mask & BINDER_DEBUG_OPEN_CLOSE)
        printk(KERN_INFO "binder_open: %d:%d\n", current->group_leader->pid, current->pid);

    proc = kzalloc(sizeof(*proc), GFP_KERNEL);
    if (proc == NULL)
        return -ENOMEM;
    get_task_struct(current);
    proc->tsk = current;
    INIT_LIST_HEAD(&proc->todo);
    init_waitqueue_head(&proc->wait);
    proc->default_priority = task_nice(current);
    mutex_lock(&binder_lock);
    binder_stats.obj_created[BINDER_STAT_PROC]++;
    hlist_add_head(&proc->proc_node, &binder_procs);
    proc->pid = current->group_leader->pid;
    INIT_LIST_HEAD(&proc->delivered_death);
    filp->private_data = proc; //将binder_proc保存在filp的private_data参数中
    mutex_unlock(&binder_lock);

    if (binder_proc_dir_entry_proc) {
        char strbuf[11];
        snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
        remove_proc_entry(strbuf, binder_proc_dir_entry_proc);
        create_proc_read_entry(strbuf, S_IRUGO, binder_proc_dir_entry_proc, binder_read_proc_proc, proc);
    }

    return 0;
}

方法一开始首先初始化了 结构体binder_proc 对象proc,前面有说过这个binder_proc用来描述一个使用了Binder进程间通信的应用程序进程.接着将proc保存在了参数filp 的变量private_data 中,即方法 filp->private_data = proc;,参数filp指向一个打开文件结构体.当进程调用binder_open函数后,就会创建一个文件描述符,这个文件描述符与filp参数关联在一起,这样就能通过返回的文件描述符访问到filp->private_data 所指向的结构体binder_proc,函数的最后几行通过调用函数binder_read_proc_proc 读取文件内容.通过读取文件内容获取到之前所说的文件中存放的Binder线程池,引用对象,实体对象,内核缓冲区.


binder_mmap

当打开了/dev/binder文件后,还需要调用mmap函数将设备文件映射到进程地址空间,然后才可以进行进程间通讯.将它映射到进程的地址空间主要是为进程分配缓冲区,因为各个进程之间在linux 环境中是相互独立的,有各自的内存空间,不能互相调用,所以需要这个缓冲区进行进程间通讯传输数据.

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
    //vm_area_struct 和 vm_struct 描述一块虚拟地址空间
    //vm_area_struct *vma为内核空间地址
    //vm_struct *area为用户空间地址
    struct vm_struct *area;
    //将binder_open所初始化的binder_proc从filp中取出并赋值
    struct binder_proc *proc = filp->private_data;
    const char *failure_string;
    struct binder_buffer *buffer;

    //vm_end 和 vm_start 指定了映射控件地址的范围
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        //指定大小为4M
        vma->vm_end = vma->vm_start + SZ_4M;
        ...

    //判断这块虚拟空间的权限是否为可读状态,必须为可读,因为用户空间不可以对虚拟空间有写入操作,只能读取
    if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
        ret = -EPERM;
        failure_string = "bad vm_flags";
        goto err_bad_arg;
    }
    vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;

    //proc->buffer所描述的为binder所分配管理的内核缓冲区必须为空,因为不能重复初始化缓冲区.不然报错goto err.
    if (proc->buffer) {
        ret = -EBUSY;
        failure_string = "already mapped";
        goto err_already_mapped;
    }

    //下面开始分配内核缓冲区
    //调用get_vm_area在进程的内核空间分配内存区域.
    area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);

    //分配成功area不为null,失败报错
    if (area == NULL) {
        ret = -ENOMEM;
        failure_string = "get_vm_area";
        goto err_get_vm_area_failed;
    }

    //将分配的内核缓冲区的地址addr指向proc->buffer,既proc->buffer可以访问到这块内和缓冲区
    proc->buffer = area->addr;
    //地址偏移量,通过vm_area_struct描述的内核空间地址+偏移量可以算出 vm_struct所描述的用户空间地址
    proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;

    ...
    //创建物理页面pages
    proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
    if (proc->pages == NULL) {
        ret = -ENOMEM;
        failure_string = "alloc page array";
        goto err_alloc_pages_failed;
    }
    //binder驱动为进程所分配的缓冲区大小buffer_offset
    proc->buffer_size = vma->vm_end - vma->vm_start;

    //指定这块缓冲区的操作函数在结构体vm_opertions_struct对象binder_vm_ops中
    //打开函数为binder_vma_open
    //关闭函数为binder_vma_close
    vma->vm_ops = &binder_vm_ops;
    vma->vm_private_data = proc;

    //binder_update_page_range为结构体vm_area_struct所描述的内核空间vma分配物理页面
    //分配到porc->buffer中
    if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {
        ret = -ENOMEM;
        failure_string = "alloc small buf";
        goto err_alloc_small_buf_failed;
    }
    //将内核缓冲区赋值给buffer
    buffer = proc->buffer;
    INIT_LIST_HEAD(&proc->buffers);
    //将buffer加入到proc->buffers中,buffers为内核缓冲区列表
    list_add(&buffer->entry, &proc->buffers);
    buffer->free = 1;
    //初始化的时候缓冲区为空闲状态 加入到空闲缓冲区大小free_buffers中
    binder_insert_free_buffer(proc, buffer);
    proc->free_async_space = proc->buffer_size / 2;
    barrier();
    proc->files = get_files_struct(current);
    proc->vma = vma;

    ...
    return ret;
}

上面函数调用了binder_update_page_range为结构体vm_area_struct所描述的内核空间vma分配物理页面,以及调用
binder_insert_free_buffer 将缓冲区加入到空闲buffers中.


binder内核缓冲区管理

Binder驱动为进程维护了一个内核缓冲区池,内核缓冲区池中每一块内存都使用一个binder_buffer结构体描述,并且保存在一个列表中,用来进程间通信传输数据.binder将已经分配的内存块和空闲内存块分别保存在binder_procbuffersfree_buffers中,binder驱动需要新的内存块时,会从free_buffers中获取,交给进程使用.

binder驱动通过内存缓冲区的分配,释放,查询来管理缓冲区.

1.分配内核缓冲区

当一个进程需要向另一个进程传递数据时,需要先向binder驱动发送BC_TRANSACTION或者BC_REPLY指令,这时候binder驱动会将用户空间的数据拷贝到内核空间,在将内核空间的数据发送到目标进程,这时候binder驱动需要在目标进程的内存池中分配出一块小的内核缓冲区保存这些数据,供他使用.内核缓冲区的分配由函数binder_alloc_buf 执行

binder_alloc_buf

//结构体binder_proc对象proc用来描述目标进程
//data_size 用来描述缓冲区大小
//offsets_size 偏移数组缓冲区大小
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 *));

    ...

    //合法性检测
    ...

    //while循环用来检测目标进程中的空闲缓冲区列表是否有最适合的缓冲区可用
    while (n) {
        buffer = rb_entry(n, struct binder_buffer, rb_node);
        BUG_ON(!buffer->free);
        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;
        }
    }

    //如果为null,返回
    if (best_fit == NULL) {
        printk(KERN_ERR "binder: %d: binder_alloc_buf size %zd failed, "
               "no address space\n", proc->pid, size);
        return NULL;
    }

    ....
    //binder_update_page_range 为结构体vm_area_struct所描述的内核空间vma分配物理页面
    if (binder_update_page_range(proc, 1,
        (void *)PAGE_ALIGN((uintptr_t)buffer->data), end_page_addr, NULL))
        return NULL;

    rb_erase(best_fit, &proc->free_buffers);
    buffer->free = 0;

    //将分配好的缓冲区添加到目标进程 结构体binder_proc的变量allocated_buffers中
    binder_insert_allocated_buffer(proc, buffer);

    ...

    //设置新分配的内核缓冲区的大小以及偏移数组缓冲区的大小
    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);
        if (binder_debug_mask & BINDER_DEBUG_BUFFER_ALLOC_ASYNC)
            printk(KERN_INFO "binder: %d: binder_alloc_buf size %zd "
                   "async free %zd\n", proc->pid, size,
                   proc->free_async_space);
    }

    //返回新分配的内核缓冲区
    return buffer;
}

binder_insert_allocated_buffer

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

    //进程已分配物理页面的内核缓冲区以他们内核地址作为关键字保存在红黑树allocated_buffers中
    //while循环中就是在这个集合中找到一个合适的插入位置p;
    while (*p) {
        parent = *p;
        buffer = rb_entry(parent, struct binder_buffer, rb_node);
        BUG_ON(buffer->free);

        if (new_buffer < buffer)
            //合适的插入位置p
            p = &parent->rb_left;
        else if (new_buffer > buffer)
            p = &parent->rb_right;
        else
            BUG();
    }

    //将已经分配好的内核缓冲区new_buffer插入到集合中
    rb_link_node(&new_buffer->rb_node, parent, p);
    rb_insert_color(&new_buffer->rb_node, &proc->allocated_buffers);
}

分配过程大致如下 :

  1. 设置要分配的缓冲区大小size以及做一些合法性检测
  2. 从目标进程的缓冲区列表中是否有大小为size的缓冲区可用,并找到合适的缓冲区
  3. 为结构体vm_area_struct所描述的内核空间vma分配物理页面
  4. 将分配好的缓冲区添加到目标进程 结构体binder_proc的变量allocated_buffers红黑树中
  5. 设置这个新缓冲区的一些参数
  6. 返回这个新缓冲区

2.释放内核缓冲区

当binder需要通知service处理数据,会发送BR_TRANSACTION 或者BR_REPLY协议码,当处理完成之后,它就会使用协议码BC_FREE_BUFFER来通知Binder驱动程序释放相应的内核缓冲区,以免浪费系统内存.在Binder 驱动程序中,释放内核缓冲区的操作由binder_free_buf实现.

binder_free_buf

static void binder_free_buf(
    struct binder_proc *proc, struct binder_buffer *buffer)
{
    size_t size, buffer_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 *));

    ...

    //如果为异步
    if (buffer->async_transaction) {
        proc->free_async_space += size + sizeof(struct binder_buffer);
        if (binder_debug_mask & BINDER_DEBUG_BUFFER_ALLOC_ASYNC)
            printk(KERN_INFO "binder: %d: binder_free_buf size %zd "
                   "async free %zd\n", proc->pid, size,
                   proc->free_async_space);
    }

    //binder_update_page_range 释放内核缓冲区buffer用来保存数据的地址空间所占有的物理页面
    binder_update_page_range(proc, 0,
        (void *)PAGE_ALIGN((uintptr_t)buffer->data),
        (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK),
        NULL);

    //从目标进程proc的allocated_buffers 中删除,即已经占用的内核缓冲区列表
    rb_erase(&buffer->rb_node, &proc->allocated_buffers);

    ...

    //添加到目标进程proc的空闲缓冲区列表中
    binder_insert_free_buffer(proc, buffer);
}

释放过程大致如下:

  1. 计算要回收内核缓冲区大小
  2. 释放内核缓冲区buffer用来保存数据的地址空间所占有的物理页面
  3. 从目标进程binder_proc的结构体的变量allocated_buffers中删除
  4. 添加到binder_proc的结构体的变量free_buffers空闲缓冲区中

3.查询内核缓冲区

当进程使用完一个内核缓冲区之后,要通过协议码BC_FREE_BUFFER来通知Binder驱动释放内核缓冲区,Binder释放缓冲区需要知道用来描述内核缓冲区binder_buffer的结构体,根据这个结构体信息释放内存,使用binder驱动提供的函数binder_buffer_lookup根据一个用户空间的地址来查询一个内核缓冲区.

//proc为目标进程
//binder驱动程序将内核缓冲区的数据传输给目标进程时,它只把数据缓冲区的用户地址传递给目标进程,既将一个binder_buffer
//结构体的变量data指向一块数据缓冲区的用户地址传递给目标进程,user_ptr即是指向data的地址.
static struct binder_buffer *binder_buffer_lookup(
    struct binder_proc *proc, void __user *user_ptr)
{
    struct rb_node *n = proc->allocated_buffers.rb_node;
    struct binder_buffer *buffer;
    struct binder_buffer *kern_ptr;

    //根据用户空间地址user_ptr获取对应的binder_buffer的内核空间地址
    kern_ptr = user_ptr - proc->user_buffer_offset
        - offsetof(struct binder_buffer, data);

    //查找红黑树中是否有这个节点
    while (n) {
        buffer = rb_entry(n, struct binder_buffer, rb_node);
        BUG_ON(buffer->free);

        if (kern_ptr < buffer)
            n = n->rb_left;
        else if (kern_ptr > buffer)
            n = n->rb_right;
        else
            //如果有,返回这个节点
            return buffer;
    }
    return NULL;
}

猜你喜欢

转载自blog.csdn.net/hfyd_/article/details/81944836