Android Binder 驱动分析1

Android Binder 驱动分析1

Binder驱动的属于Binder 框架的最核心,无论是应用层的Binder Client 还是Binder Server,或者是Service Manager都是它的Client.

Binder驱动的源码位于:

  • kernel/drivers/android/Binder.c
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,
};

和其他字符驱动一样,binder驱动也提供了实现了一套file_operations, 接下来逐项分析这些函数

binder_open

首先分析binder_open函数,每一个需要完成进程间通信的进程都需要完成这个接口调用。实际上就是打开/dev/binder这个设备。

static int binder_open(struct inode *nodp, struct file *filp)
{           
    struct binder_proc *proc;

    binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_open: %d:%d\n",
             current->group_leader->pid, current->pid);
    //current的是当前调用open的应用层进程        
    proc = kzalloc(sizeof(*proc), GFP_KERNEL);
    if (proc == NULL)
        return -ENOMEM;
    get_task_struct(current); //增加task_struct的引用计数
    proc->tsk = current;                                                                                                                                                                                     
    INIT_LIST_HEAD(&proc->todo);
    init_waitqueue_head(&proc->wait);
    proc->default_priority = task_nice(current);

    binder_lock(__func__);

    binder_stats_created(BINDER_STAT_PROC);
    hlist_add_head(&proc->proc_node, &binder_procs);//保存至binder_procs
    proc->pid = current->group_leader->pid;
    INIT_LIST_HEAD(&proc->delivered_death);
    filp->private_data = proc;

    binder_unlock(__func__);

    if (binder_debugfs_dir_entry_proc) {
        char strbuf[11];

        snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
        proc->debugfs_entry = debugfs_create_file(strbuf, S_IRUGO,
            binder_debugfs_dir_entry_proc, proc, &binder_proc_fops);
    }    

    return 0;
}
  1. 这里面使用了个类似于全局变量的current的结构体,它的类型是task_struct.

    The current pointer refers to the user process currently executing. During the execution of a system call,
    such as open or read, the current process is the one that invoked the call.

  2. 这个过程主要是创建初始化 binder_proc, binder_proc的信息主要来源于current.即应用层对应进程的相关信息。

    这里穿插一句:在Linux中,内核对于线程的表示并不是process,而是task. 有时候可以理解为task即使应用层进程和线程在内核的
    代表。

  3. 最后把该proc 加入到binder_procs列表中, 同时把该proc放在file的private_data中方便获取

binder_mmap

在frameworks/native/libs/binder/ProcessState.cpp,应用层将调用mmap来把binder映射到用户空间来方便数据交换。其调用的方式大致如此:

 #define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2)) //1016K 

 // mmap the binder, providing a chunk of virtual address space to receive transactions.
 mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);

上述代码为拼凑代码,可以知道需要映射的空间接近1M(1016K)

接下去贴上binder_mmap的代码

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)// struct vm_area_struct是描述用户空间地址
{           
    int ret;
    struct vm_struct *area;//struct vm_struct描述内核空间地址
    //在binder_open中将表示当前应用层的proc信息存储在file的private_data中
    struct binder_proc *proc = filp->private_data;
    const char *failure_string;
    struct binder_buffer *buffer;

    //限制映射的代码在4M 以内(实际上应用层申请的地址空间是不到1M)
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;

    vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;

    mutex_lock(&binder_mmap_lock);
    if (proc->buffer) {
        ret = -EBUSY;
        failure_string = "already mapped";
        goto err_already_mapped;
    }

    //申请分配内核地址空间,注意其大小是struct vm_area_struct中的大小,
    area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);

    proc->buffer = area->addr;//把分配的内核空间地址首地址保存在proc的buffer中
    proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;//计算出进程分配地址空间首地址和内核空间首地址的偏差
    mutex_unlock(&binder_mmap_lock);

    proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);

    proc->buffer_size = vma->vm_end - vma->vm_start;

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

    //分配物理页帧,并把它映射到进程地址空间和内核空间。这样
    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;
    }
    //这个是binder_buffer buffer, 而proc->buffer是void* , 这里有个有趣的地方就是转换,利用binder_buffer来管理申请分配的内存(零数组技巧)
    buffer = proc->buffer;
    INIT_LIST_HEAD(&proc->buffers);
    list_add(&buffer->entry, &proc->buffers);
    buffer->free = 1;
    binder_insert_free_buffer(proc, 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;
    /*pr_info("binder_mmap: %d %lx-%lx maps %p\n",
         proc->pid, vma->vm_start, vma->vm_end, proc->buffer);*/
    return 0;

err_alloc_small_buf_failed:
    kfree(proc->pages);
    proc->pages = NULL;
err_alloc_pages_failed:
    mutex_lock(&binder_mmap_lock);
    vfree(proc->buffer);
    proc->buffer = NULL;
err_get_vm_area_failed:
err_already_mapped:
    mutex_unlock(&binder_mmap_lock);
err_bad_arg:
    pr_err("binder_mmap: %d %lx-%lx %s failed %d\n",
           proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
    return ret;
  1. 代码中有两个很像的结构体,理解他们对于理解代码有很大的帮助: struct vm_area_structstruct vm_struct 这两者的区别如下:

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地址空间来
映射非连续的物理页面。

  1. 代码中申请内核地址空间和用户地址空间通过物理叶帧建立关联,即访问内核和用户层访问虚拟地址对应的是同一个物理地址。

  2. 将这个内核地址空间用binder_buffer管理起来,添加进proc的buffers中链表中

binder_update_page_range

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

    trace_binder_update_page_range(proc, allocate, start, end);
    //PAGE_SIZE的大小是4K         
    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结构中
        *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);

        //tmp_area 是struct vm_struct,代表内核线性地址空间段
        tmp_area.addr = page_addr;//申请的内核地址空间的首地址
        tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
        page_array_ptr = page;
        //与内核地址空间与对应的页帧建立映射,实际上该函数会修改对应的页表项 PTE
        ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);

        //通过内核空间地址 + 内核地址与进程空间地址的偏移 = 进程地址空间地址,所以user_page_addr是page_addr对应的
        //进程空间地址,其中page_addr已经完成了物理页帧关联
        user_page_addr =(uintptr_t)page_addr + proc->user_buffer_offset;
        //同样样该物理页帧与进程地址空间建立映射关系
        ret = vm_insert_page(vma, user_page_addr, page[0]);

        /* vm_insert_page does not seem to increment the refcount */
    }          
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值