Android IPC机制5-Binder驱动探究

1 概述

Client进程通过RPC(Remote Procedure Call Protocol)与Server通信,整个通信过程从client进程到server进程,从内核态到用户态都涉及到了,前面几篇文章也讲了很多。而真正在Client和Server两端建立通信的基础设施便是Binder Driver。总结一下,Android系统Binder机制中的四个组件Client、Server、Service Manager和Binder驱动程序的关系如下图所示 (图片来自老罗博客

Binder通信架构

Binder驱动是Android专用的,但底层的驱动架构与Linux驱动一样。binder驱动在以misc设备进行注册,作为虚拟设备,没有直接操作硬件,只是对设备内存的处理。主要是驱动设备的初始化(binder_init),打开 (binder_open),映射(binder_mmap),数据操作(binder_ioctl)。后面会详细讲解这几个函数,但为了后面的理解,先来看看内核态、用户态、系统调用等概念。

1.1 用户态与内核态

以创建进程来说,无论是Linux亦或是windows,对于任何操作系统来说,其都是属于核心功能,因为它要做很多底层细致地工作,消耗系统的物理资源,比如分配物理内存,从父进程拷贝相关信息,拷贝设置页目录页表等等,这些显然不能随便让哪个程序就能去做。类似的操作还有很多,所以就操作系统就自然引出特权级别的概念,显然,最关键性的权力必须由高特权级的程序来执行,这样才可以做到集中管理,减少有限资源的访问和使用冲突。

特权级显然是非常有效的管理和控制程序执行的手段,因此在硬件上对特权级做了很多支持,就Intel x86架构的CPU来说一共有0~3四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查,相关的概念有CPL、DPL和RPL,这里不再过多阐述。硬件已经提供了一套特权级使用的相关机制,软件自然就是好好利用的问题,这属于操作系统要做的事情,对于Unix/Linux来说,只使用了0级特权级和3级特权级。也就是说在Unix/Linux系统中,一条工作在0级特权级的指令具有了CPU能提供的最高权力,而一条工作在3级特权级的指令具有CPU提供的最低或者说最基本权力。

而arm cpu的特权模式就比较多了,有7种。但是只有一种是用户模式,其他的都是特权模式。

现在我们从特权级的调度来理解用户态和内核态就比较好理解了,以x86来说当程序运行在3级特权级上时,就可以称之为运行在用户态,因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;反之,当程序运行在0级特权级上时,就可以称之为运行在内核态。
虽然用户态下和内核态下工作的程序有很多差别,但最重要的差别就在于特权级的不同,即权力的不同。

1.2 如何切换用户态和内核态

Linux提供了系统调用作为切换用户态与内核态的机制。其底层是通过中断机制来实现的。简单的说,当用户态执行到需要内核态执行的代码时,会产生一个中断信号,cpu收到信号后会根据中断信号传递的信息进入不同的中断函数,当中断函数执行完毕后,就会回到用户态。

2. 核心方法

下面以serviceManager的启动为例,详细分析Binder驱动的几个核心函数,先来看看ServiceManager的启动过程(代码位于service_manager.c)

int main(int argc, char **argv)
{
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;

    bs = binder_open(128*1024);

    if (binder_become_context_manager(bs)) {
        LOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }

    svcmgr_handle = svcmgr;
    binder_loop(bs, svcmgr_handler);
    return 0;
}

可以发现,其主要有以下几个步骤

  • 打开binder驱动: binder_open(128*1024);
  • 通知Binder驱动程序它成为Binder通信的上下文管理者:binder_become_context_manager(bs);
  • 进入循环等待请求的到来:binder_loop(bs, svcmgr_handler);

2.1 binder_open(service_manager)

struct binder_state *binder_open(unsigned mapsize)
{
    struct binder_state *bs;

    bs = malloc(sizeof(*bs));
    if (!bs) {
        errno = ENOMEM;
        return 0;
    }

    bs->fd = open("/dev/binder", O_RDWR);
    if (bs->fd < 0) {
        fprintf(stderr,"binder: cannot open device (%s)\n",
                strerror(errno));
        goto fail_open;
    }

    bs->mapsize = mapsize;
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    if (bs->mapped == MAP_FAILED) {
        fprintf(stderr,"binder: cannot map device (%s)\n",
                strerror(errno));
        goto fail_map;
    }

        /* TODO: check version */

    return bs;

fail_map:
    close(bs->fd);
fail_open:
    free(bs);
    return 0;
}

通过文件操作函数open来打开/dev/binder设备文件。设备文件/dev/binder是在Binder驱动程序模块初始化的时候创建的,我们先看一下这个设备文件的创建过程。进入到kernel/common/drivers/staging/android目录中,打开binder.c文件,可以看到模块初始化入口binder_init:

2.2 binder_init:

static int __init binder_init(void)
{
    int ret;
    //创建名为binder的工作队列
    binder_deferred_workqueue = create_singlethread_workqueue("binder"); 
    if (!binder_deferred_workqueue)
        return -ENOMEM;

    binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL); 
    if (binder_debugfs_dir_entry_root)
        binder_debugfs_dir_entry_proc = debugfs_create_dir("proc",
                         binder_debugfs_dir_entry_root);

     // 注册misc设备
    ret = misc_register(&binder_miscdev);   
    if (binder_debugfs_dir_entry_root) {
        ... //在debugfs文件系统中创建一系列的文件
    }
    return ret;
}

static struct miscdevice binder_miscdev = {
    .minor = MISC_DYNAMIC_MINOR, //次设备号 动态分配
    .name = "binder",     //设备名
    .fops = &binder_fops  //设备的文件操作结构,这是file_operations结构
};

static const struct file_operations binder_fops = {
    .owner = THIS_MODULE,
    .poll = binder_poll,
    .unlocked_ioctl = binder_ioctl,
    .compat_ioctl = binder_ioctl,
    .mmap = binder_mmap,
    .open = binder_open,
    .flush = binder_flush,
    .release = binder_release,
};

debugfs_create_dir是指在debugfs文件系统中创建一个目录,返回值是指向dentry的指针。注册misc设备,miscdevice结构体,便是前面注册misc设备时传递进去的参数,file_operations结构体,指定相应文件操作的方法。

回到2.1,前面的前面的binder_open函数执行语句会执行到bs->fd = open("/dev/binder", O_RDWR);,这句代码就是一个系统调用,最终会执行到binder驱动的binder_open方法

2.3 binder_open(Binder驱动)

static int binder_open(struct inode *nodp, struct file *filp)
{
    struct binder_proc *proc; // binder进程 【见附录3.1】

    proc = kzalloc(sizeof(*proc), GFP_KERNEL); // 为binder_proc结构体在分配kernel内存空间
    if (proc == NULL)
        return -ENOMEM;
    get_task_struct(current);
    proc->tsk = current;   //将当前线程的task保存到binder进程的tsk
    INIT_LIST_HEAD(&proc->todo); //初始化todo列表
    init_waitqueue_head(&proc->wait); //初始化wait队列
    proc->default_priority = task_nice(current);  //将当前进程的nice值转换为进程优先级

    binder_lock(__func__);   //同步锁,因为binder支持多线程访问
    binder_stats_created(BINDER_STAT_PROC); //BINDER_PROC对象创建数加1
    hlist_add_head(&proc->proc_node, &binder_procs); //将proc_node节点添加到binder_procs为表头的队列
    proc->pid = current->group_leader->pid;
    INIT_LIST_HEAD(&proc->delivered_death);
    filp->private_data = proc;       //file文件指针的private_data变量指向binder_proc数据
    binder_unlock(__func__); //释放同步锁

    return 0;
}

这个函数的主要作用是创建一个struct binder_proc数据结构来保存打开设备文件/dev/binder的进程的上下文信息,并且将这个进程上下文信息保存在打开文件结构struct file的私有数据成员变量private_data中,这样,在执行其它文件操作时,就通过打开文件结构struct file来取回这个进程上下文信息了。这个进程上下文信息同时还会保存在一个全局哈希表binder_procs中,驱动程序内部使用。binder_procs定义在文件的开头:static HLIST_HEAD(binder_procs);其保存了所有的binder_proc结构,每次新创建的binder_proc对象都会加入binder_procs链表中。binder_proc见4.1

这样,打开设备文件/dev/binder的操作就完成了,接着是对打开的设备文件进行内存映射操作mmap:

2.4 binder_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;

    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;  //保证映射内存大小不超过4M

    mutex_lock(&binder_mmap_lock);  //同步锁
    //分配一个连续的内核虚拟空间,与进程虚拟空间大小一致
    area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP); 
    if (area == NULL) {
        ret = -ENOMEM;
        failure_string = "get_vm_area";
        goto err_get_vm_area_failed;
    }
    proc->buffer = area->addr; //指向内核虚拟空间的地址
    //地址偏移量 = 用户虚拟地址空间 - 内核虚拟地址空间
    proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer; 
    mutex_unlock(&binder_mmap_lock); //释放锁
    
    ...
    //分配物理页的指针数组,大小等于用户虚拟地址内存/4k;
    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;
    }
    proc->buffer_size = vma->vm_end - vma->vm_start;

    vma->vm_ops = &binder_vm_ops;
    vma->vm_private_data = proc;

    //分配物理页面,同时映射到内核空间和进程空间,目前只分配1个page的物理页 【见2.5】
    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 = proc->buffer; //binder_buffer对象 指向proc的buffer地址
    INIT_LIST_HEAD(&proc->buffers); //创建进程的buffers链表头
    list_add(&buffer->entry, &proc->buffers); //将binder_buffer地址 加入到所属进程的buffers队列
    buffer->free = 1;
    //将空闲buffer放入proc->free_buffers中
    binder_insert_free_buffer(proc, buffer); 
    //异步可用空间大小为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;
    return 0;

    ...// 错误flags跳转处,free释放内存之类的操作
    return ret;
}

binder_mmap通过加锁,保证一次只有一个进程分配内存,保证多进程间的并发访问。其中user_buffer_offset是虚拟进程地址与虚拟内核地址的差值,也就是说同一物理地址,当内核地址为kernel_addr,则进程地址为proc_addr = kernel_addr + user_buffer_offset。可以用下面的图片来方便理解


binder_mmap

在上面的函数里同时出现了struct vm_area_struct和struct vm_struct。struct vm_area_struct,它表示的是一块连续的虚拟地址空间区域,struct vm_struct,这个数据结构也是表示一块连续的虚拟地址空间区域,那么,这两者的区别是什么呢?在Linux中,struct vm_area_struct表示的虚拟地址是给进程使用的,而struct vm_struct表示的虚拟地址是给内核使用的,它们对应的物理页面都可以是不连续的。struct vm_area_struct表示的地址空间范围是0~3G,而struct vm_struct表示的地址空间范围是(3G + 896M + 8M) ~ 4G。struct vm_struct表示的地址空间范围为什么不是3G~4G呢?原来,3G ~ (3G + 896M)范围的地址是用来映射连续的物理页面的,这个范围的虚拟地址和对应的实际物理地址有着简单的对应关系,即对应0~896M的物理地址空间,而(3G + 896M) ~ (3G + 896M + 8M)是安全保护区域(例如,所有指向这8M地址空间的指针都是非法的),因此struct vm_struct使用(3G + 896M + 8M) ~ 4G地址空间来映射非连续的物理页面。

2.5 binder_update_page_range

static int binder_update_page_range(struct binder_proc *proc, int allocate,
                    void *start, void *end,  struct vm_area_struct *vma)    
{
    ...
    for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
        int ret;
        struct page **page_array_ptr;
        page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
        *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);  //分配物理内存
        if (*page == NULL) {
            goto err_alloc_page_failed;
        }
        tmp_area.addr = page_addr;
        tmp_area.size = PAGE_SIZE + PAGE_SIZE;
        page_array_ptr = page;
        ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr); //物理空间映射到虚拟内核空间
        if (ret) {
            goto err_map_kernel_failed;
        }
        user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;
        ret = vm_insert_page(vma, user_page_addr, page[0]); //物理空间映射到虚拟进程空间
        if (ret) {
            goto err_vm_insert_page_failed;
        }
    }
    ...
}

binder_update_page_range 主要完成工作:分配物理空间,将物理空间映射到内核空间,将物理空间映射到进程空间。 当然binder_update_page_range
既可以分配物理页面,也可以释放物理页面。

2.6 binder_ioctl

讲了这么多,service_managermain()的binder_open执行完毕了,接下来代码就走到了binder_become_context_manager(bs),来看看这个函数

int binder_become_context_manager(struct binder_state *bs)
{
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

这个函数内部非常简单,直接调用的ioctl函数,通过上面的经验,我们可以知道ioctl也是个系统调用,最终是执行的是内核态的binder_ioctl函数

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret;
    struct binder_proc *proc = filp->private_data;
    struct binder_thread *thread;  // binder线程
    unsigned int size = _IOC_SIZE(cmd);
    void __user *ubuf = (void __user *)arg;
    //进入休眠状态,直到中断唤醒
    ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
    if (ret)
        goto err_unlocked;

    binder_lock(__func__);
    //获取binder_thread
    thread = binder_get_thread(proc); 
    if (thread == NULL) {
        ret = -ENOMEM;
        goto err;
    }

    switch (cmd) {
    case BINDER_WRITE_READ:  //进行binder的读写操作
        ret = binder_ioctl_write_read(filp, cmd, arg, thread); //
        if (ret)
            goto err;
        break;
    case BINDER_SET_MAX_THREADS: //设置binder最大支持的线程数
        if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
            ret = -EINVAL;
            goto err;
        }
        break;
    case BINDER_SET_CONTEXT_MGR: //成为binder的上下文管理者,也就是ServiceManager成为守护进程
        ret = binder_ioctl_set_ctx_mgr(filp);
        if (ret)
            goto err;
        break;
    case BINDER_THREAD_EXIT:   //当binder线程退出,释放binder线程
        binder_free_thread(proc, thread);
        thread = NULL;
        break;
    case BINDER_VERSION: {  //获取binder的版本号
        struct binder_version __user *ver = ubuf;

        if (size != sizeof(struct binder_version)) {
            ret = -EINVAL;
            goto err;
        }
if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
                 &ver->protocol_version)) {
            ret = -EINVAL;
            goto err;
        }
        break;
    }
    default:
        ret = -EINVAL;
        goto err;
    }
    ret = 0;
err:
    if (thread)
        thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
    binder_unlock(__func__);
    wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
        
err_unlocked:
    trace_binder_ioctl_done(ret);
    return ret;
}

binder_ioctl()函数负责在两个进程间收发IPC数据和IPC reply数据。是一个非常重要的函数,其参数列表为ioctl(文件描述符,ioctl命令,数据类型)

  • 文件描述符,是通过open()方法打开Binder Driver后返回值;
  • ioctl命令和数据类型是一体的,不同的命令对应不同的数据类型,如声明成为service_manager声明称为上下文管理者就是BINDER_SET_CONTEXT_MGR
    继续分析这个函数之前,又要解释两个数据结构了,一个是struct binder_thread结构体,顾名思久,它表示一个线程,这里就是执行binder_become_context_manager函数的线程了。具体定义见4.2
    proc表示这个线程所属的进程。struct binder_proc有一个成员变量threads,它的类型是rb_root,它表示一查红黑树,把属于这个进程的所有线程都组织起来,struct binder_thread的成员变量rb_node就是用来链入这棵红黑树的节点了。looper成员变量表示线程的状态,它可以取下面这几个值:
enum {  
    BINDER_LOOPER_STATE_REGISTERED  = 0x01,  // 已注册
    BINDER_LOOPER_STATE_ENTERED     = 0x02,  // 已进入
    BINDER_LOOPER_STATE_EXITED      = 0x04,  // 已退出
    BINDER_LOOPER_STATE_INVALID     = 0x08,  // 非法
    BINDER_LOOPER_STATE_WAITING     = 0x10,  // 等待中
    BINDER_LOOPER_STATE_NEED_RETURN = 0x20  // 需要返回
};  

另外一个数据结构是struct binder_node,详见4.3。它表示一个binder实体, rb_node和dead_node组成一个联合体。 如果这个Binder实体还在正常使用,则使用rb_node来连入proc->nodes所表示的红黑树的节点,这棵红黑树用来组织属于这个进程的所有Binder实体;如果这个Binder实体所属的进程已经销毁,而这个Binder实体又被其它进程所引用,则这个Binder实体通过dead_node进入到一个哈希表中去存放。proc成员变量就是表示这个Binder实例所属于进程了。

下面来看看ioctl中的几个重要函数

2.6.1 binder_get_thread()

static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
    struct binder_thread *thread = NULL;
    struct rb_node *parent = NULL;
    struct rb_node **p = &proc->threads.rb_node;
    while (*p) {  //根据当前进程的pid,从binder_proc中查找相应的binder_thread
        parent = *p;
        thread = rb_entry(parent, struct binder_thread, rb_node);
        if (current->pid < thread->pid)
            p = &(*p)->rb_left;
        else if (current->pid > thread->pid)
            p = &(*p)->rb_right;
        else
            break;
    }
    if (*p == NULL) {
        thread = kzalloc(sizeof(*thread), GFP_KERNEL); //新建binder_thread结构体
        if (thread == NULL)
            return NULL;
        binder_stats_created(BINDER_STAT_THREAD);
        thread->proc = proc;
        thread->pid = current->pid;  //保存当前进程(线程)的pid
        init_waitqueue_head(&thread->wait);
        INIT_LIST_HEAD(&thread->todo);
        rb_link_node(&thread->rb_node, parent, p);
        rb_insert_color(&thread->rb_node, &proc->threads);
        thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
        thread->return_error = BR_OK;
        thread->return_error2 = BR_OK;
    }
    return thread;
}

这里把当前线程current的pid作为键值,在进程proc->threads表示的红黑树中进行查找,看是否已经为当前线程创建过了binder_thread信息。在这个场景下,由于当前线程是第一次进到这里,所以肯定找不到,即*p == NULL成立,于是,就为当前线程创建一个线程上下文信息结构体binder_thread,并初始化相应成员变量,并插入到proc->threads所表示的红黑树中去,下次要使用时就可以从proc中找到了。

2.6.2 binder_ioctl_write_read()

static int binder_ioctl_write_read(struct file *filp,
                unsigned int cmd, unsigned long arg,
                struct binder_thread *thread)
{
    int ret = 0;
    struct binder_proc *proc = filp->private_data;
    unsigned int size = _IOC_SIZE(cmd);
    void __user *ubuf = (void __user *)arg;
    struct binder_write_read bwr;

    if (size != sizeof(struct binder_write_read)) {
        ret = -EINVAL;
        goto out;
    }
    if (copy_from_user(&bwr, ubuf, sizeof(bwr))) { //把用户空间数据ubuf拷贝到bwr
        ret = -EFAULT;
        goto out;
    }

    if (bwr.write_size > 0) {
        //当写缓存中有数据,则执行binder写操作
        ret = binder_thread_write(proc, thread,
                      bwr.write_buffer, bwr.write_size, &bwr.write_consumed); 
        trace_binder_write_done(ret); 
        if (ret < 0) { //当写失败,再将bwr数据写回用户空间,并返回
            bwr.read_consumed = 0;
            if (copy_to_user(ubuf, &bwr, sizeof(bwr))) 
                ret = -EFAULT;
            goto out;
        }
    }
    if (bwr.read_size > 0) {
        //当读缓存中有数据,则执行binder读操作
        ret = binder_thread_read(proc, thread, 
                      bwr.read_buffer, bwr.read_size, &bwr.read_consumed,
                      filp->f_flags & O_NONBLOCK); 
        trace_binder_read_done(ret);
        if (!list_empty(&proc->todo))
            wake_up_interruptible(&proc->wait); //进入休眠,等待中断唤醒
        if (ret < 0) { //当读失败,再将bwr数据写回用户空间,并返回
            if (copy_to_user(ubuf, &bwr, sizeof(bwr))) 
                ret = -EFAULT;
            goto out;
        }
    }

    if (copy_to_user(ubuf, &bwr, sizeof(bwr))) { //将内核数据bwr拷贝到用户空间ubuf
        ret = -EFAULT;
        goto out;
    }
out:
    return ret;
}

具体流程如下:

  1. 首先把用户空间数据拷贝到内核空间bwr;
  2. 当bwr写缓存中有数据,则执行binder写操作;当写失败,再将bwr数据写回用户空间,并退出;
  3. 当bwr读缓存中有数据,则执行binder读操作;当读失败,再将bwr数据写回用户空间,并退出;
  4. 最后把内核数据bwr拷贝到用户空间。

这里涉及两个核心方法binder_thread_write()和binder_thread_read()方法,请求处理过程是通过binder_thread_write()方法,该方法用于处理Binder协议中的请求码。当binder_buffer存在数据,binder线程的写操作循环执行。

响应处理过程是通过binder_thread_read()方法,该方法根据不同的binder_work->type以及不同状态,生成相应的响应码。

2.7 小节

上面列出了这么多代码,其实有些细节我也没有深入学习,不过对于binder通信的主要流程还算是比较熟悉了。其主要流程有以下几点(以service_manager启动并成为上下文管理者为例)

  1. binder_init: 注册驱动,
  2. binder_open: 创建一个struct binder_proc数据结构来保存打开设备文件/dev/binder的进程的上下文信息
  3. binder_mmap : 映射进程虚拟地址和内核虚拟地址到同一个物理地址
  4. binder_ioctl : 两个进程间收发IPC数据和IPC reply数据

总结

在上面的一章里,通过代码详细分析了binder通信的几个核心函数(binder_thread_write()与binder_thread_write()没有展开讲),下面从宏观上做些总结

内存关系

先上个图(感谢gityuan大大)

binder_physical_memory

这个图生动的说明了binder通信的内存原理。一直说的binder驱动的只需一次copy的奥妙就是内存映射,android通过一块物理内存地址映射到一个进程中的用户空间和内核空间的两个地址,就可以让client到server的数据传递只需从client的用户空间copy到内核空间,然后由于server端的用户空间映射了和内核空间data同一个地址,所以就能直接得到数据,避免二次copy,提高了性能。

图中的进程所有的4g内存是虚拟内存,并非进程实际能得到4g,至于为什么是4g,主要是由于32位cpu的内存寻址能力2^32,实际上binder能映射的虚拟地址空间大小为3G+896M+8M~4G=120m,而实际的物理内存更是被限制为<4m,所以binder机制不适合大数据的传输。

对于进程和内核虚拟地址映射到同一个物理内存的操作是发生在数据接收端,而数据发送端还是需要将用户态的数据复制到内核态。那为何不直接让发送端和接收端直接映射到同一个物理空间,那样就连一次复制的操作都不需要了,0次复制操作那就与Linux标准内核的共享内存的IPC机制没有区别了,对于共享内存虽然效率高,但是对于多进程的同步问题比较复杂,而管道/消息队列等IPC需要复制2两次,效率较低。这里就不先展开讨论Linux现有的各种IPC机制跟Binder的详细对比,总之Android选择Binder的基于速度和安全性的考虑。
下面这图是从Binder在进程间数据通信的流程图,从图中更能明了Binder的内存转移关系:


通信模型

下面这张张图生动的描述一次ipc 事物的完整流程:


结构体附录

4.1 binder_proc

struct binder_proc {  
    struct hlist_node proc_node;  
    struct rb_root threads;  //binder_thread红黑树的根节点
    struct rb_root nodes;   //binder_node红黑树的根节点,保存进程中的binder实体
    struct rb_root refs_by_desc;  //binder_ref红黑树的根节点(以handle为key)
    struct rb_root refs_by_node;  //binder_ref红黑树的根节点(以ptr为key)
    int pid;                //创建binder_proc的进程id
    struct vm_area_struct *vma;   //指向进程虚拟地址空间的指针(用户态使用)
    struct task_struct *tsk;  
    struct files_struct *files;  
    struct hlist_node deferred_work_node;  
    int deferred_work;  
    void *buffer;    // 映射的内核空间的起始地址
    ptrdiff_t user_buffer_offset;    //内核空间与用户空间的地址偏移量
  
    struct list_head buffers;  
    struct rb_root free_buffers;  
    struct rb_root allocated_buffers;  
    size_t free_async_space;  
  
    struct page **pages;  
    size_t buffer_size;    //映射的内核空间大小
    uint32_t buffer_free;   //可用内存总大小
    struct list_head todo;  
    wait_queue_head_t wait;  
    struct binder_stats stats;  
    struct list_head delivered_death;  
    int max_threads;    //最大binder线程数
    int requested_threads;  
    int requested_threads_started;  
    int ready_threads;  
    long default_priority;  
};  

4.2 binder_thread

struct binder_thread {  
    struct binder_proc *proc;   //线程所属的进程
    struct rb_node rb_node;  
    int pid;    //线程pid
    int looper;  //looper的状态
    struct binder_transaction *transaction_stack;  
    struct list_head todo;  
    uint32_t return_error; /* Write failed, return error code in read buf */  
    uint32_t return_error2; /* Write failed, return error code in read */  
        /* buffer. Used when sending a reply to a dead process that */  
        /* we are also waiting on */  
    wait_queue_head_t wait;  
    struct binder_stats stats;  
};  

4.3 binder_node

struct binder_node {  
    int debug_id;  
    struct binder_work work;  
    union {  
        struct rb_node rb_node;  
        struct hlist_node dead_node;  
    };  
    struct binder_proc *proc;  
    struct hlist_head refs;  
    int internal_strong_refs;  
    int local_weak_refs;  
    int local_strong_refs;  
    void __user *ptr;  
    void __user *cookie;  
    unsigned has_strong_ref : 1;  
    unsigned pending_strong_ref : 1;  
    unsigned has_weak_ref : 1;  
    unsigned pending_weak_ref : 1;  
    unsigned has_async_transaction : 1;  
    unsigned accept_fds : 1;  
    int min_priority : 8;  
    struct list_head async_todo;  
};  

4.4 binder_buffer

BInder地址空间被划分为一段一段,每一段都是由binder_buffer 来描述

struct binder_buffer {  
    struct list_head entry;   //    buffer实体的地址
    struct rb_node rb_node;   //红黑树节点,用于挂在binder_proc中的红黑树上
    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];  
};  

写在最后

Binder机制确实是android中非常重要的一环,学习难度也很大。不过通过Binder的学习,也让我对android系统有了更深入的认识。在这个过程中,也对linux的ipc机制、系统调用、内存管理有了些了解,也终于是搞懂了红黑树,这也算是一些附带成果吧。
最后再次感谢罗升阳gityuan两位老师,他们的博客使我受益匪浅。希望以后自己也能有更多高质量的原创博文。

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

推荐阅读更多精彩内容