前言
Android开发应用中,经常使用到多进程通信,还有面试时候经常问到多进程通信,经过这一阵对Binder机制的学习使用,总结一篇我对于Binder机制的理解。
为什么要使用多进程通信?
1、突破进程内存限制,比如图片占用内存过多
2、功能稳定行,独立的通信机制保持长链接
3、规避系统内存泄露,比如独立的webView进程可以阻隔内存泄露导致的问题
4、隔离风险,有一些不稳定功能,业务允许情况下可以放到独立进程,防止主进程崩溃
那么知道了为什么要多进程通信后,看下Linux系统下进程通讯的方案,在看具体通信方案钱,先了解进程间怎么实现的通信。
进程间怎么通信的呢?
首先要明确几个概念。
系统划分
内存被操作系统划分为两块,用户空间和内核空间,这两个空间相互之间是隔离的,所以用户程序崩溃,内核空间之间也不会受到任何影响
用户空间
用户程序代码运行的地方
内核空间
内核代码运行的地方
通信方式如图所示
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机制的通信原理
总结:
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();
}
}
}