美文网首页Android进阶之路Android技术知识
FrameWork源码【Binder 驱动解析】

FrameWork源码【Binder 驱动解析】

作者: 码农的地中海 | 来源:发表于2022-07-15 17:04 被阅读0次

    前言

    我们知道 Binder 驱动主要用于跨进程的通信, 它的身影出现在 Androrid 系统方方面面, 是 Android 系统框架中非常重要, 又非常难懂以搞懂的一部分 。

    Binder作为Android进程间通信的机制,可以看做是一个驱动,因为本身也是一个Java类,Binder.java : IBinder

    在Android中,常见的进程间通信例如系统类的:打电话、闹钟等;自己创建的:像WebView、视频播放、音频播放、大图浏览等。


    f31cdf173f1e9af2d09192b76a19c8a6.jpeg

    优势

    • 内存
      Android 虚拟机 会给每个进程分配内存,如果是单一进程,像WebView,或者大图浏览会占用进程的很多内存,严重的时候还是会出现OOM,因此如果采用多进程的方式,那么虚拟机会给每个进程都分配内存,能够避免出现内存不足的情况
    • 风险隔离
      如果是单个进程,某个模块出现问题,那么就会导致整个app崩溃;如果采用多进程的方式,其中某个进程崩溃,主app不会受到影响。

    binder架构原理

    Binder是一个类,主要用在Service组件应用中。

    /framework/base/core/java/Android/os/Binder.java(包含内部类BinderProxy)

    image.png image.png

    binder相对来说还是比较复杂的,有framework层与native层的binder。framework层的binder采用JNI技术来调用native(C/C++ framework)层的binder架构,从而为上层应用程序提供服务。需要注意的是:

    (1) framework层的Binder是建立在Native层架构基础之上的,核心逻辑都是交予Native层方法来处理

    (2)framework层的Service Manager类与Native层的功能并不完全对应,java层的Service Manager类的实现最终是通过BinderProxy传递给Native层来完成的。


    image.png

    图中红色代表整个framework层binder架构相关组件;Binder类代表Server端,BinderProxy类代表Client端;图中蓝色代表Native层Binder架构相关组件。

    1 Binder框架层

    a、Native Binder框架层包含以下类(frameworks/native/libs/binder):IInterface,BnInterface,BpInterface,等。

    b、Java框架层包含以下类(frameworks/base/core/java/android/os):

    IBinder,Binder,IInterface,ServiceManagerNative,ServiceManager,BinderInternal,IServiceManager,ServiceManagerProxy

    Java框架层的类的部分方法的实现在本地代码里(frameworks/base/core/jni)。

    2 Binder核心层

    Binder核心层主要是IBinder及它的两个子类,即BBinder和BpBinder,分别代表了最基本的服务端及客户端。源码位于frameworks/native/libs/binder目录下。

    binder service服务端实体类会继承BnInterface,而BnInterface会继承自BBinder,服务端可将BBinder对象注册到servicemananger进程。
    
    客户端程序和驱动交互时只能得到远程对象的句柄handle,它可以调用调用ProcessState的getStrongProxyForHandle函数,利用句柄handle建立BpBinder对象,然后将它转为IBinder指针返回给调用者。这样客户端每次调用IBinder指针的transact方法,其实是执行BpBinder的transact方法。
    

    3 binder adapter层

    主要是IPCThreadState.cpp和ProcessState.cpp,源码位于frameworks/native/libs/binder目录下,这两个类都采用了单例模式,主要负责和驱动直接交互。
    

    a、ProcessState,进程相关的类,负责打开binder设备,进行一些初始化设置并做内存映射;
    void ProcessState::startThreadPool()该方法启动的新线程,并通过joinThreadPool读取binder设备,查看是否有请求。
    b、IPCThreadState,线程相关的类,负责直接和binder设备通信,使用ioctl读写binder驱动数据。

    4 binder驱动层

    Android因此添加了binder驱动,其设备节点为/dev/binder,主设备号为10,binder驱动程序在内核中的头文件和代码路径如下:

    kernel/drivers/staging/binder.h
    
    kernel/drivers/staging/binder.c
    
    binder驱动层的主要作用是完成实际的binder数据传输。
    

    Service与客户端通信,有两种方式,AIDL和Messenger。AIDL基于Binder,而Messenger基于AIDL。AIDL只是方便使用Binder,也可以不用AIDL。在IPC的时候是需要使用Service的,通过启动后Service才能获取到远程Binder,这样就可以通信了。不过还是使用AIDL更方便些,客户端启动绑定Service后在onServiceConnected获取到Binder,通过Stub的asInterface能很简单的转化成对应的业务接口。

    Binder驱动源码分析

    从App启动,Binder是怎样被初始化的,还有就是一些细节的处理,从最底层的C++代码看Binder驱动是怎么实现的

    如果大家想要看系统的源码,其实不用去下载源码,直接在在线看就行在线看系统源码,点击就可以看


    image.png

    1.binder_init

    binder_init是binder的初始化操作

    
    /drivers/staging/android/binder.c
    
    static int __init binder_init(void)
    {
        int ret;
    
        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);
        ret = misc_register(&binder_miscdev);                   
        ……
        }
        return ret;
    }
    
    device_initcall(binder_init);
    
    static struct miscdevice binder_miscdev = {
        .minor = MISC_DYNAMIC_MINOR,
        .name = "binder",
        .fops = &binder_fops
    };
    
    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_init函数中,调用了misc_register函数,其中传入的参数binder_miscdev,定义了misc设备的名称”binder“,就是注册了一个binder设备,MISC_DYNAMIC_MINOR是动态分配的一个设备号;还有一个数据结构,binder_fops,其中包含了binder_open、binder_ioctl、binder_mmap等重要的函数,也是在这个时候完成了初始化,具体怎么完成的初始化,接着往下看

    # /drivers/char/misc.c
    int misc_register(struct miscdevice * misc)
    {
        dev_t dev;
        ……
    
        misc->this_device = device_create(misc_class, misc->parent, dev,
                          misc, "%s", misc->name);
        if (IS_ERR(misc->this_device)) {
            int i = DYNAMIC_MINORS - misc->minor - 1;
            if (i < DYNAMIC_MINORS && i >= 0)
                clear_bit(i, misc_minors);
            err = PTR_ERR(misc->this_device);
            goto out;
        }
        /*
         * Add it to the front, so that later devices can "override"
         * earlier defaults
         */
        list_add(&misc->list, &misc_list);
        out:
        mutex_unlock(&misc_mtx);
        return err;
    }
    

    misc_register中调用了device_create函数,device_create就是用来创建binder misc device设备

    # /drivers/base/core.c
    
    struct device *device_create(struct class *class, struct device *parent,
                     dev_t devt, void *drvdata, const char *fmt, ...)
    {
        va_list vargs;
        struct device *dev;
    
        va_start(vargs, fmt);
        dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
        va_end(vargs);
        return dev;
    }
    
    device_create_groups_vargs(struct class *class, struct device *parent,
                   dev_t devt, void *drvdata,
                   const struct attribute_group **groups,
                   const char *fmt, va_list args)
    {
        struct device *dev = NULL;
        //给设置分配内存  GFP_KERNEL
        dev = kzalloc(sizeof(*dev), GFP_KERNEL);
        ……
        retval = device_add(dev);
        ……
    }
    

    所以binder_init的主要作用就是:
    1 初始化binder misc设备
    2 给设备分配内存
    3 调用list_add,将misc device设备放入设备列表中misc_list

    2.binder_open

    当Android要进行进程间通信的时候,通过客户端或者服务端调用binder_open,打开Binder驱动,例如在app中,调用bindService,就是通过Framework层,JNI调用底层binder_open打开驱动

    static int binder_open(struct 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;
        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);
        //将proc添加到链表中
        hlist_add_head(&proc->proc_node, &binder_procs);
        proc->pid = current->group_leader->pid;
        INIT_LIST_HEAD(&proc->delivered_death);
        
        return 0;
    }
    

    在binder_open中,首先给binder_proc分配内存,然后将当前进程的信息,保存到binder_proc中,然后将binder_proc添加到binder_procs表中(hlist_add_head),每个进程拥有一个binder_proc,独一份!

    3.binder_mmap

    这里就到了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;
        ……
        //进程的虚拟内存 不能超过4M
        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;
        ……
        //分配物理内存
        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;
        
        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;
        }
        
        vma->vm_ops = &binder_vm_ops;
        vma->vm_private_data = proc;
        ……
        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;
    ……
    }
    

    首先明白几个变量代表的含义
    vm_area_struct *vma : 用户态的虚拟内存
    vm_struct *area : 内核态的虚拟内存

    代码分析

    1-----
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
            vma->vm_end = vma->vm_start + SZ_4M;
    

    在这里,驱动做了限制,用户态的虚拟内存,不能超过4M,应用所占用的大概有1M - 8K(异步为一半,这里是同步的标准),所以为什么Intent传递数据时,不能超过1M,就是因为Intent传递数据基于Binder机制,因此不能超过1M,这是Binder驱动定的。

    2-----
    area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
    

    在内核空间中,分配一块跟用户空间大小相同的虚拟内存

    3-----
    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;
    }
    
    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;
        ……
        if (allocate == 0)
            goto free_range;
    
    
        for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
            int ret;
        
            page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
        
            BUG_ON(*page);
            *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
            ……
            tmp_area.addr = page_addr;
            tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
            //内核虚拟内存与物理内存完成映射
            ret = map_vm_area(&tmp_area, PAGE_KERNEL, page);
        
            user_page_addr =
                (uintptr_t)page_addr + proc->user_buffer_offset;
            //用户空间与物理内存完成映射
            ret = vm_insert_page(vma, user_page_addr, page[0]);
            ……
        }
    

    allocate == 1,意味着分配1页的物理内存(4KB),并与内核空间的虚拟内存完成映射(map_vm_area);vm_insert_page 将用户空间的虚拟内存与物理内存完成映射


    image.png

    看一下这个图就能明白binder_mmap的作用了

    3.binder_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;
        unsigned int size = _IOC_SIZE(cmd);
        void __user *ubuf = (void __user *)arg;
        ……
        switch (cmd) {
        case BINDER_WRITE_READ:
            ret = binder_ioctl_write_read(filp, cmd, arg, thread);
            if (ret)
                goto err;
            break;
        case BINDER_SET_MAX_THREADS:
        //设置ServiceManager为大管家,下一节会走到这个地方
        case BINDER_SET_CONTEXT_MGR:
        case BINDER_THREAD_EXIT:
        case BINDER_VERSION: 
        ……
    }
    

    从源码中可以看到,binder_ioctl只要是通过cmd命令来执行相应的操作,这里主要看下BINDER_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 (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
            ret = -EFAULT;
            goto out;
        }
        ……
        if (bwr.write_size > 0) {
            ret = binder_thread_write(proc, thread,
                          bwr.write_buffer,
                          bwr.write_size,
                          &bwr.write_consumed);
            trace_binder_write_done(ret);
            if (ret < 0) {
                bwr.read_consumed = 0;
                if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                    ret = -EFAULT;
                goto out;
            }
        }
        if (bwr.read_size > 0) {
            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) {
                if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                    ret = -EFAULT;
                goto out;
            }
        }
        ……
        if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
            ret = -EFAULT;
            goto out;
        }
    }
    

    读写操作主要是判断write_size和read_size是否存在读写的数据,通过copy_from_user 和 copy_to_user完成数据的读写操作


    07Java Binder中系统服务的注册过程.png 06Java Binder的初始化.png

    更多framework学习;可以前往:Frame Work源码解析手册

    文末

    其实这里主要的作用就是,实现Java层和native层册映射关系,在Java能够调用native层的方法

    从Binder架构图中可以看到,JNI主要就是Framework层与Native层交互,通过native与内核层的数据读写,返回到Server端

    相关文章

      网友评论

        本文标题:FrameWork源码【Binder 驱动解析】

        本文链接:https://www.haomeiwen.com/subject/uukcirtx.html