Binder - 6、Binder中的一次拷贝

一、前言

众所周知,Binder之所以高效,是因为它只发生了一次内存拷贝,那么它的"一次拷贝"到底是怎么实现的呢?

我们在之前在分析binder_transaction的时候,提到了一个方法,这个方法是一次拷贝的核心,我们在这里来仔细分析一下

二、源码分析

2.1 入口-binder_transaction

Kernel\drivers\android\binder.c

c++ 复制代码
static void binder_transaction(struct binder_proc *proc,

                   struct binder_thread *thread,

                   struct binder_transaction_data *tr, int reply,

                   binder_size_t extra_buffers_size)

{

    //申请内存

    t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,

        tr->offsets_size, extra_buffers_size,

        !reply && (t->flags & TF_ONE_WAY), current->tgid);

    //拷贝数据

    if (binder_alloc_copy_user_to_buffer(

                &target_proc->alloc,

                t->buffer, 0,

                (const void __user *)

                    (uintptr_t)tr->data.ptr.buffer,

                tr->data_size)) {

        //error

    }

}

在这个方法中,有两个函数对拷贝数据非常重要,一个是binder_alloc_new_buf,另一个是binder_alloc_copy_user_to_bufferbinder_alloc_new_buf是用来申请内存用的,而binder_alloc_copy_user_to_buffer是执行具体的数据拷贝。

可以看到这里两个方法传递的参数都是target_proc的,代表我们此时在操纵接收端的内存。

我们分开来看一下,先看下binder_alloc_new_buf

2.2 buffer申请-binder_alloc_new_buf

c++ 复制代码
struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc,

                       size_t data_size,

                       size_t offsets_size,

                       size_t extra_buffers_size,

                       int is_async,

                       int pid)

{

    struct binder_buffer *buffer;

  


    mutex_lock(&alloc->mutex);

    buffer = binder_alloc_new_buf_locked(alloc, data_size, offsets_size,

                         extra_buffers_size, is_async, pid);

    mutex_unlock(&alloc->mutex);

    return buffer;

}

直接调用了binder_alloc_new_buf_locked方法:

c++ 复制代码
static struct binder_buffer *binder_alloc_new_buf_locked(

                struct binder_alloc *alloc,

                size_t data_size,

                size_t offsets_size,

                size_t extra_buffers_size,

                int is_async,

                int pid)

{

    struct rb_node *n = alloc->free_buffers.rb_node;

    struct binder_buffer *buffer;

    size_t buffer_size;

    struct rb_node *best_fit = NULL;

    void __user *has_page_addr;

    void __user *end_page_addr;

    size_t size, data_offsets_size;

    int ret;

  


    //数据对齐

    data_offsets_size = ALIGN(data_size, sizeof(void *)) +

        ALIGN(offsets_size, sizeof(void *));

    size = data_offsets_size + ALIGN(extra_buffers_size, sizeof(void *));

    /* Pad 0-size buffers so they get assigned unique addresses */

    size = max(size, sizeof(void *));

    //寻找有没有空闲的buffer

    while (n) {

        buffer = rb_entry(n, struct binder_buffer, rb_node);

        buffer_size = binder_alloc_buffer_size(alloc, 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;

        }

    }

    //包含该数据的页面

    has_page_addr = (void __user *)

        (((uintptr_t)buffer->user_data + buffer_size) & PAGE_MASK);

    //user_data结尾对应的页面

    end_page_addr =

        (void __user *)PAGE_ALIGN((uintptr_t)buffer->user_data + size);

    if (end_page_addr > has_page_addr)

        end_page_addr = has_page_addr;

    ret = binder_update_page_range(alloc, 1, (void __user *)

        PAGE_ALIGN((uintptr_t)buffer->user_data), end_page_addr);

    buffer->free = 0;

    buffer->allow_user_free = 0;

    //插入allocated_buffers红黑树中

    binder_insert_allocated_buffer_locked(alloc, buffer);

    buffer->data_size = data_size;

    buffer->offsets_size = offsets_size;

    return buffer;

}

这个方法还是很长的,总体来说,干了这么几件事:

  • 1、数据对齐

  • 2、寻找binder_alloc中有没有空闲的节点,如果有并且足够大,那么就可以用这个

  • 3、计算我们内存对应的页面

  • 4、执行binder_update_page_range方法

  • 5、将使用的Buffer插入到allocated_buffers

中间的这个binder_update_page_range方法,看起来比较可疑,我们跳进去看下:

2.2.1 binder_update_page_range

c++ 复制代码
static int binder_update_page_range(struct binder_alloc *alloc, int allocate,

                    void __user *start, void __user *end)

{

    void __user *page_addr;

    unsigned long user_page_addr;

    struct binder_lru_page *page;

    struct vm_area_struct *vma = NULL;

    struct mm_struct *mm = NULL;

    bool need_mm = false;

  


    //是否需要申请页面,我们这里肯定是需要的,所以need_mm为true

    for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {

        page = &alloc->pages[(page_addr - alloc->buffer) / PAGE_SIZE];

        if (!page->page_ptr) {

            need_mm = true;

            break;

        }

    }

  


    if (mm) {

        mmap_read_lock(mm);

        vma = alloc->vma;

    }

  


    //判断物理Page是否存在

    for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {

        int ret;

        bool on_lru;

        size_t index;

  


        index = (page_addr - alloc->buffer) / PAGE_SIZE;

        page = &alloc->pages[index];

  


        if (page->page_ptr) {

            //页面存在

            continue;

        }

        //申请物理Page

        page->page_ptr = alloc_page(GFP_KERNEL |

                        __GFP_HIGHMEM |

                        __GFP_ZERO);

        page->alloc = alloc;

        INIT_LIST_HEAD(&page->lru);

  


        user_page_addr = (uintptr_t)page_addr;

        //将page插入到vma中

        ret = vm_insert_page(vma, user_page_addr, page[0].page_ptr);

    }

    if (mm) {

        mmap_read_unlock(mm);

        mmput(mm);

    }

    return 0;

}

哦,原来binder_update_page_range是用来申请物理页面的,按需申请Page并将其插入vma中,且被binder_alloc所持有,那么binder_alloc是何许人也?

2.2.2 binder_alloc是什么?


图2.1 - binder_alloc基础结构

通过上图可以看到,binder_alloc持有vmabinder_buffer的空闲列表以及在使用中的列表,还有物理页面,它持有了申请内存相关的数据。

2.2.3 binder_buffer是什么?


图2.2 - binder_buffer基础结构

binder_buffer持有当次通信所需的数据地址以及数据大小,并且被binder_alloc持有。

2.2.4 小结

到这里binder_alloc_new_buf就分析的差不多了,最重要的是申请binder_buffer,以及申请物理Page。

接下来我们看binder_alloc_copy_user_to_buffer

2.3 数据拷贝 - binder_alloc_copy_user_to_buffer

c++ 复制代码
unsigned long

binder_alloc_copy_user_to_buffer(struct binder_alloc *alloc,

                 struct binder_buffer *buffer,

                 binder_size_t buffer_offset,

                 const void __user *from,

                 size_t bytes)

{

    //检查Buffer

    if (!check_buffer(alloc, buffer, buffer_offset, bytes))

        return bytes;

  


    while (bytes) {

        unsigned long size;

        unsigned long ret;

        struct page *page;

        pgoff_t pgoff;

        void *kptr;

        //从binder_alloc中获得Page

        page = binder_alloc_get_page(alloc, buffer,

                         buffer_offset, &pgoff);

        size = min_t(size_t, bytes, PAGE_SIZE - pgoff);

        //将该Page映射到Kernel地址空间

        kptr = kmap(page) + pgoff;

        //从用户空间拷贝数据到kptr

        ret = copy_from_user(kptr, from, size);

        //释放page

        kunmap(page);

        if (ret)

            return bytes - size + ret;

        bytes -= size;

        from += size;

        buffer_offset += size;

    }

    return 0;

}

这个方法可以说是重中之重,

  • 1、首先是拿到我们之前申请到的物理页面

  • 2、将该Page映射到Kernel的内存地址空间之中,得到地址kptr,这样kptr这个地址也就指向了我们申请到的Page中,也就是说,kptr和用户空间实际上指向同一个物理页面,如果修改kptr的数据,也就是在修改用户空间的数据

  • 3、执行数据拷贝

  • 4、释放页面

我们回顾一下,刚才说kptr和用户空间指向的同一个物理页面,而且只执行了一次从写入端用户空间到内核态的数据拷贝,就完成了数据的传递,这就是"一次拷贝"的真正实现。

三、总结

讲到这里其实就比较清楚了,"一次拷贝"的核心正是Binder驱动与接收进程同时映射到同一个物理页面,发送进程在发生调用时,只需要将其拷贝给内核层的地址中,就完成了数据的传递。

图3.1 - 数据传递示意图

四、疑问

既然Binder驱动和Server进程可以通过内存映射的形式映射到同一个页面,那么我们能不能把Client端的也给映射过去呢?这样甚至都不需要拷贝。

其实这种做法就跟共享内存一样了,存在一些问题:

  • 1、存在一定的安全问题,多个客户端都可以访问该内容

  • 2、数据同步很难处理,同时存在两个甚至多个进程对这段内存作出读写,很容易就出现数据同步问题

  • 3、内存不好管理

所以IPC要想做到快速且安全,至少是需要发生一次拷贝的。

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui