开篇故事
面试的时候很多面试官都喜欢问IPC,然而我比较菜,只能说ipc的几种方式,然后说四大组件实现的跨进程通信就是对Binder的封装,然后面试官就会祭出自己的多命大杀器:
面试官:”你知道binder的实现机制么?“
我:”...共享内存...“
面试官:”你这了解的不够深入啊,再见”
为了不再这样被面试官装一脸,还是决定深入分析一下binder机制
从java层看Binder
由于intent mesager 这些底层都是binder不方便观察 我们就从一个自己写的AIDL中由表及里的观察了:
// Book.aidl 声明自定义对象
package com.example.aidl;
// Declare any non-default types here with import statements
parcelable Book;
// IBookManager.aidl 声明BookManager接口
package com.example.aidl;
import com.example.aidl.Book;
// Declare any non-default types here with import statements
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
在这里找到AIDL生成的文件
../build/generated/aidl_source_output_dir/debug/compileDebugAidl/out/com/example/aidl/IBookManager.java
首先这个类很长一大串,如果直接很容易懵逼,比如之前的我看到这里直接放弃了,所以我在这里做了一些处理,等下还要把图附上去:
根据AIDL生成的接口类:IBookManager
public interface IBookManager extends android.os.IInterface
{
public java.util.List<com.example.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.example.aidl.Book book) throws android.os.RemoteException;
}
本地侧的跨进程实现类:
public static abstract class Stub extends android.os.Binder implements com.example.aidl.IBookManager
{
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.aidl.IBookManager interface,
* generating a proxy if needed.
*/
public static com.example.aidl.IBookManager asInterface(android.os.IBinder obj)
{
...
}
@Override public android.os.IBinder asBinder()
{
...
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
...
}
}
这里着重讲解一下 asInterface() 方法:
用于将服务端的BInder对象转换成客户端所需的AIDL接口类型对象,如果客户端和服务端位于同一进程那么此方法返回的就是服务端的stub对象本身,否则返回的是系统封装后的Stub.proxy,具体实现如下:
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.aidl.IBookManager))) {
return ((com.example.aidl.IBookManager)iin);
}
return new com.example.aidl.IBookManager.Stub.Proxy(obj);
本地侧的跨进程实现类的代理类:
private static class Proxy implements com.example.aidl.IBookManager
{
@Override public android.os.IBinder asBinder()
{
...
}
...
@Override public java.util.List<com.example.aidl.Book> getBookList() throws android.os.RemoteException
{
...
}
@Override public void addBook(com.example.aidl.Book book) throws android.os.RemoteException
{
...
}
}
这个类就没啥特别要说的就是拿着stub类一顿操作,由于我们讲的是IPC所以这里忽略.
Binder IBookManager
\ / \
Stub Proxy
那么如何通信需要看Stub类的 onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) :
这个方法在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法处理:code可以确定要操作的目标方法,data从获取目标方法所需的参数--> 执行目标方法 --> 向reply中写返回值完成通信
能不能更深入一点看Binder?
binder解决了哪些问题:
1.跨进程管理对象的生命周期问题:Binder在驱动中建立了一张所有进程的引用对象和实体对象关联表,同时Binder又必须保证用户进程的实体对象或者引用对象和驱动中的一致,为了达到这个目标,BInder定义了自己的跨进程引用计数机制
2.普通的IPC传递参数时要经历两次数据复制的过程:一次时从调用这的数据缓存区复制到内核的缓冲区,第二次时从内核的缓冲区复制到接收进程的缓冲区,binder是如何减少一次复制的呢?
这就需要了解下什么是mmap,当用户创建打开Binder设备后会调用mmap()在驱动中创建一块内存空间用于接收传递给本进程的Binder数据
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
...
//为用户进程分配一块内存空间作为缓冲区
area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
···
//在内核中分配了同样页数大小的空间,并把他们的物理内存地址绑定在一起
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;
}
...
return ret;
}
从而实现了应用和内核共享同一块内存空间:
数据会从发送金层复制到内核空间,驱动会在接收进程的缓冲区中寻找一块合适的空间来存放数据,因为接收进程的用户空间缓冲区和内核空间的缓冲区是共享的所以接收进程不需要将数据再从内核空间复制到用户空间.
用户空间缓冲区 内核空间缓冲区
\ /
\ /
\ /
物理内存