Android 学习笔记之进程间通信Binder机制

226 阅读4分钟

前言

Android开发应用中,经常使用到多进程通信,还有面试时候经常问到多进程通信,经过这一阵对Binder机制的学习使用,总结一篇我对于Binder机制的理解。

为什么要使用多进程通信?

1、突破进程内存限制,比如图片占用内存过多

2、功能稳定行,独立的通信机制保持长链接

3、规避系统内存泄露,比如独立的webView进程可以阻隔内存泄露导致的问题

4、隔离风险,有一些不稳定功能,业务允许情况下可以放到独立进程,防止主进程崩溃

那么知道了为什么要多进程通信后,看下Linux系统下进程通讯的方案,在看具体通信方案钱,先了解进程间怎么实现的通信。

进程间怎么通信的呢?

首先要明确几个概念。

系统划分

内存被操作系统划分为两块,用户空间和内核空间,这两个空间相互之间是隔离的,所以用户程序崩溃,内核空间之间也不会受到任何影响

用户空间

用户程序代码运行的地方

内核空间

内核代码运行的地方

通信方式如图所示

image.png

Binder与传统IPC通信之间的比较

性能

Binder:需要一次拷贝

共享内存:无需拷贝

Socket:需要两次拷贝

特点

Binder:基于C/S 架构,易用性高

共享内存:控制复杂,易用性差

Socket:基于C/S架构,传输效率低,开销大

安全性

Binder:为每个APP分配UID,支持实名和匿名

共享内存:依赖上层协议,访问接入点开放,不安全

Socket:依赖上层协议,访问接入点开放,不安全

所以以上可以看到,Binder是最安全的进程间通信协议方式,那么进入正题,什么是Binder呢?

什么是Binder?

1、Binder是进程间的通信机制

2、Binder也是一个虚拟物理设备驱动,因为没有对应的实际硬件设备

3、Binder是一个能发起通信的java类,Binder.java,实现了IBinder接口

Binder机制的通信原理

image.png

总结:

1、Binder的一次拷贝,就是从进程A到内核空间,使用的copy_from_user()

2、内核空间与接收进程B之间有一个物理内存空间 binder_map

3、内核空间与进程B接收方之间使用的是mmap(),不存在拷贝,共用一个物理内存实现

mmap工作原理

Linux通过将一块虚拟内存区域与磁盘上的一个对象关联,来初始化这个虚拟内存内容,这个过程称为内存映射,也就是mmap(memory mapping)

mmap写文件,首先创建一个物理内存,比如指定一个文件地址,通过mmap去跟这个地址关联,从而操作这个地址,就可以去修改虚拟内存里的东西,类似于通过指针去操作这个空间,这个空间又是指向需要修改的地方。 mmap就是通过一个副本映射到虚拟内存区域,操作这个副本,就可以操作这个内存区域。

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;
    ……
}

为什么Intent传递的数据不能超过1M?

Binder里dev/binder 里mmap传参时内存限制大小为1M-8K

#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)//这里的限制是1MB-4KB*2

ProcessState::ProcessState(const char *driver)
{
    if (mDriverFD >= 0) {
        // mmap the binder, providing a chunk of virtual address space to receive transactions.
        // 调用mmap接口向Binder驱动中申请内核空间的内存
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            // *sigh*
            ALOGE("Using %s failed: unable to mmap transaction memory.\n", mDriverName.c_str());
            close(mDriverFD);
            mDriverFD = -1;
            mDriverName.clear();
        }
    }
}