Binder驱动与协议(二)

“九层之台,始于垒土;千里之行,始于足下”,不论binder机制在Android源码中多庞大繁杂,总归它得从地基开始–Binder驱动,我们知道Android系统是基于Linux的内核的,因而Binder驱动也必须是一个标准的Linux驱动。具体来说,Binder Driver 会将自己注册为一个misc device,并向上层提供一个/dev/binder节点,当然我们需要牢记,Binder并不真实对应硬件设备。

一、binder驱动

Binder驱动运行于内核态,提供open()、mmap()、ioctl()等常用的文件操作接口。
一般来说Binder驱动源码在Kernel工程的 drivers/staging/android目录中,Linux的字符设备通常要经过alloc_chrdev_region(),cdev_init()等一系列操作才能在内核中注册自己,但misc类型驱动的注册相对简单,只需要调用misc_register()即可,主设备号统一为10,次设备号则为每种设备所独有,也可由系统动态分配次设备号。Binder中与驱动注册相关代码如下:

/*drivers/staging/android/Binder.c*/
static struct miscdevice binder_miscdev = {
    .minor = MISC_DYNAMIC_MINOR,//动态分配次设备号
    .name = "binder",//驱动名称
    .fops = &binder_fops//Binder驱动支持的文件操作
};
static int __init binder_init(void)
{
    ....
    ret = misc_register(binder_miscdev);
    ....
}
static const struct file_operations binder_fops = {
    .owner = THIS_MODULE,
    .poll = binder_poll,
    .unlocked_ioctl = binder_ioctl,
    .mmap = binder_mmap,
    .open = binder_open,
    .flush = binder_flush,
    .release = binder_release,
};

从这里可以看出,Binder驱动总共为上层应用提供了6个接口–其中使用最多的就是binder_ioctl、binder_mmap、binder_open,常见的read、write在这里没有看到,因为binder_ioctl接口可以代替读写操作,因此一个接口就实现了两种操作,另外Binder所使用的设备驱动入口函数不是module_init(),而是:

device_initcall(binder_init);

这样做的目的可能是Android系统不想支持动态编译的驱动模块,我们可以将其改为动态编译,也就是将设备驱动入口函数改为module_init和module_exit

1、binder_open–打开Binder驱动

上层进程在访问Binder驱动时,首先就是打开/dev/binder节点,这个操作最终的实现是在binder_open()中。

/*drivers/staging/android/Binder.c*/
static int binder_open(truct inode *nodp, struct file *filp)
{
    struct binder_proc *proc;
    ...
    proc = kzalloc(sizeof(*proc), GFP_KERNEL); //分配空间
    if (proc == NULL)
        return -ENOMEM;
    
    get_task_struct(current);
    proc->tsk = current;
    // 初始化binder_proc
    INIT_LIST_HEAD(&proc->todo);
    init_waitqueue_head(&proc->wait);
    proc->default_priority = task_nice(current);    
    //加入Binder的全局管理中
    binder_lock(__func__);
    binder_stats_created(BINDER_STAT_PROC);
    // 添加到全局列表binder_procs中
    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_unlock(__func__);
    return 0;
}

在Binder驱动中,通过binder_procs记录了所有使用Binder的进程。每个初次打开Binder设备的进程都会被添加到这个列表中的。binder_open()已经为用户创建了一个Binder驱动的binder_proc实体,之后用户对Binder设备的操作就是以这个对象为基础的。

2、binder_mmap–映射内存空间

binder_mmap函数对应了mmap系统调用的处理,这个函数也是Binder驱动的精华所在,使用Binder机制,数据只需要经历一次拷贝就可以了,其原理就在这个函数中,
在这里插入图片描述binder_mmap这个函数中,进程B会申请一块物理内存,然后在用户空间和内核空间同时对应到这块内存上。在这之后,当进程A要发送数据给进程B的时候,只需一次,将进程A发送过来的数据拷贝到进程B的内核空间指定的内存地址即可,由于这个内存地址在服务端已经同时映射到用户空间,因此无需再做一次复制,进程B即可直接访问,我们来看源码:

/*drivers/staging/android/Binder.c
*vm_area_struct *vma描述了一块供应用程序使用的虚拟内存,
*其中vma->vm_start和vma->end分别是这块连续的虚拟内存起止点
*/
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
    //step1、Binder驱动的虚拟内存
    struct vm_struct *area;
    /*step2、这里的proc结构体在binder_open()已经看到过,它是Binder驱动为应用进程
    分配的一个数据结构,用于存储和该进程有关的所有信息,如内存分配、线程管理
    */
    struct binder_proc *proc = filp->private_data;
    const char *failure_string;
    struct binder_buffer *buffer;
    ...
    /*step3、在做实际工作前,Binder驱动会判断应用程序申请的内存大小是否合理
    ,它最多能申请4MB的mmap操作
    */
    if((vma -> vm_end - vma -> vm_start ) > SZ_4M)
    {
        vma -> vm_end = vma -> vm_start + SZ_4M
    }
     // 在内核空间获取一块地址范围,也就是说此时还没有分配实际的物理地址
    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);
    
    //这里只是分配了pages数组控件,用于管理物理页面的,也就是用于指示
    //Binder申请的物理页面状态
    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;
    // 通过下面这个函数真正完成内存的申请和地址的映射
    // 初次使用,先申请一个PAGE_SIZE大小的内存
    ret = binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma);
    ...

我们解释下tatic int binder_update_page_range(struct binder_proc *proc, int allocate,
void *start, void *end,
struct vm_area_struct *vma)函数的参数:

1、proc:申请内存的进程所持有的binder_proc对象
2、allocate:是申请还是释放。1 表示申请,0 表示释放
3、start:Binder中虚拟内存起点(页表映射代表从虚拟内存到物理内存)
4、end:Binder中虚拟内存终点

对照上面函数的传参就能看出来,在mmap中Binder驱动实际上只为进程分配了一页物理内存,虽然最多支持4MB的mmap,继续看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)
{
    void *page_addr;
    unsigned long user_page_addr;
    struct vm_struct tmp_area;
    struct page **page;
    struct mm_struct *mm;
 
    ...
 
    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];
 
        BUG_ON(*page);
        // 真正进行内存的分配
        *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
        if (*page == NULL) {
            pr_err("%d: binder_alloc_buf failed for page at %p\n",
                proc->pid, page_addr);
            goto err_alloc_page_failed;
        }
        tmp_area.addr = page_addr;
        tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
        page_array_ptr = page;
        // 在内核空间进行内存映射
        ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);
        if (ret) {
            pr_err("%d: binder_alloc_buf failed to map page at %p in kernel\n",
                   proc->pid, page_addr);
            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) {
            pr_err("%d: binder_alloc_buf failed to map page at %lx in userspace\n",
                   proc->pid, user_page_addr);
            goto err_vm_insert_page_failed;
        }
        /* vm_insert_page does not seem to increment the refcount */
    }
    if (mm) {
        up_write(&mm->mmap_sem);
        mmput(mm);
    }
 
    preempt_disable();
 
    return 0;
...

3、binder_ioctl

这是Binder接口中工作量最大的一个函数,承担了Binder驱动的大部分业务,看看接口提供了哪些操作:

如果命令是BINDER_WRITE_READ,并且
    如果 bwr.write_size > 0,则调用binder_thread_write
    如果 bwr.read_size > 0,则调用binder_thread_read
如果命令是BINDER_SET_MAX_THREADS,则设置进程的max_threads,即进程支持的最大线程数
如果命令是BINDER_SET_CONTEXT_MGR,ServiceManager专用,将自己设置为Binder大管家,系统中只能有一个SM存在
如果命令是BINDER_THREAD_EXIT,则调用binder_free_thread,释放binder_thread
如果命令是BINDER_VERSION,则返回当前的Binder版本号

这其中,最关键的就是binder_thread_write方法。当Client请求Server的时候,便会发送一个BINDER_WRITE_READ命令,同时框架会将将实际的数据包装好。关于这个命令,可以查资料或者翻看源码。
最后来总结一下,Binder驱动依然是Linux的典型驱动模型,提供多个文件操作接口。其中binder_ioctl实现了应用程序与Binder驱动之间的命令交互,承载了Binder驱动的绝大部分工作。
接下来分析“DNS服务器”–ServiceManager[Binder Server](三)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值