目录
1. 一次拷贝流程, mmap内存映射
2. AIDL角度讲解4者的关系和binder机制
3. 从native层完全讲解binder机制
4. 代理模式和AIDL原理
5. 跨进程方式优缺点比较
1. 一次拷贝流程, mmap内存映射
1.1 普通跨进程二次拷贝的过程:
数据从用户空间拷贝到内核空间:第一次拷贝:copy_from_user()
数据从内核空间拷贝到用户空间:第二拷贝:copy_to_user()
这种传统的 IPC 通信方式有两个问题:
- 性能低下,一次数据传递需要经历:内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝;
- 接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,
因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。
1.2 一次拷贝的过程:
Binder IPC正是基于内存映射(mmap)来实现的
内存映射:Binder IPC 机制中涉及到的内存映射通过mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映
射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
1). 首先Binder 驱动在内核空间创建一个数据接收缓存区;
2). 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收
缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;(数据缓存区-------->数据接收缓存区)
3). 发送方进程通过系统调用copyfromuser() 将数据copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也
就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信
2. AIDL角度讲解4者的关系和binder机制
详细解读:ServiceManager、Binder Client、Binder Server 处于不同的进程,他们三个都在用户空间,而Binder 驱动在内核空间。
总结4者的关系 :
1). 注册到serverManager : server会把自己注册到serverManager(通过binder驱动):
2). 获取Server实体: Client 进程向ServiceManager 查询Server 进程(通过binder驱动)Binder 实体的引用
3). 服务端代理: Server进程中的真实对象转换成代理对象Proxy,返回这个代理对象给Client 进程
4). Client进程拿到了这个代理对象Proxy,然后调用这个代理对象的方法(通过binder驱动)
3. 从native层完全讲解binder机制
1). servermanager注册, 成为 Binder 驱动管理者
2). servermanager进入循环等待其他service注册
3). 找到serverManager, 通过binder驱动 (在binder驱动中),通过handler值=0找到serverManager
4). server通过binder驱动注册到servermanager, 通过bp binder, 打开驱动, 得到了servermanager就可以添加 , 同时把handler, name, 写入binder驱动,
5). client请求aidl连接: 通过servermanager查询, 拿到服务代理, 返回给客户端
6). BinderProxy 将我们的请求参数发送给 ServiceManager, 通过共享内存的方式使用内核方法 copy_from_user() 将我们的参数先拷贝到内核空间,这时我们的客户端进入等待状态,
7).内存映射: 服务端拿到client的数据, 执行完之后把执行结果通过 copy_to_user() 将内核的结果拷贝到用户空间(这里只是执行了拷贝命令,并没有拷贝数据,binder只进行一次拷贝),唤醒等待的客户端并把结果响应回来
4. 代理模式和AIDL原理
4.1 核心类
1). IInterface , IInterface 代表的就是 Server 进程对象具备什么样的能力
public interface IInterface
{
public IBinder asBinder();
}
2) .IBinder, IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。
public class Binder implements IBinder {
在这里,如果链接成功返回回来的。
private val mConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
val imageManager: IImageManager = IImageManager.Stub.asInterface(service)
3).Binder Java 层的 Binder 类,代表的其实就是 Binder 本地对象
4).stub: 自动生成的, 而这个类最核心的成员是 Stub类 和 Stub的内部代理类Proxy。
这个类继承了 Binder,它实现了 IInterface 接口!
Stub充当服务端角色,持有Binder实体(本地对象)。远程进程执行onTransact()函数
- 获取客户端传过来的数据,根据方法 ID 执行相应操作。
- 将传过来的数据取出来,调用本地写好的对应方法。
- 将需要回传的数据写入 reply 流,传回客户端。
4.2 具体的案例
public interface BookManager extends IInterface {
List<Book> getBooks() throws RemoteException;
void addBook(Book book) throws RemoteException;
}
public abstract class Stub extends Binder implements BookManager {
private static final String DESCRIPTOR = "com.baronzhang.ipc.server.BookManager";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
public static BookManager asInterface(IBinder binder) {
if (binder == null)
return null;
IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
if (iin != null && iin instanceof BookManager)
return (BookManager) iin;
return new Proxy(binder);
}
@Override
public IBinder asBinder() {
return this;
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case TRANSAVTION_getBooks:
data.enforceInterface(DESCRIPTOR);
List<Book> result = this.getBooks();
reply.writeNoException();
reply.writeTypedList(result);
return true;
case TRANSAVTION_addBook:
data.enforceInterface(DESCRIPTOR);
Book arg0 = null;
if (data.readInt() != 0) {
arg0 = Book.CREATOR.createFromParcel(data);
}
this.addBook(arg0);
reply.writeNoException();
return true;
}
return super.onTransact(code, data, reply, flags);
}
public class Proxy implements BookManager {
private static final String DESCRIPTOR = "com.baronzhang.ipc.server.BookManager";
private IBinder remote;
public Proxy(IBinder remote) {
this.remote = remote;
}
public String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public List<Book> getBooks() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel replay = Parcel.obtain();
List<Book> result;
try {
data.writeInterfaceToken(DESCRIPTOR);
remote.transact(Stub.TRANSAVTION_getBooks, data, replay, 0);
replay.readException();
result = replay.createTypedArrayList(Book.CREATOR);
} finally {
replay.recycle();
data.recycle();
}
return result;
}
@Override
public void addBook(Book book) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel replay = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
if (book != null) {
data.writeInt(1);
book.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0);
replay.readException();
} finally {
replay.recycle();
data.recycle();
}
}
@Override
public IBinder asBinder() {
return remote;
}
}
流程总结:
1.在 Proxy 中的 addBook() 方法中首先通过 Parcel 将数据序列化,然后调用 remote.transact()
Client 进程通过系统调用陷入内核态,Client 进程中执行 addBook(), 里面 transact()的线程挂起等待返回;
驱动完成一系列的操作之后唤醒 Server 进程,调用 Server 进程本地对象的 onTransact()。最终又走到了 Stub 中的 onTransact() 中
onTransact() 根据函数编号调用相关函数(在 Stub 类中为 BookManager 接口中的每个函数中定义了一个编号,只不过上面的源码中我们简化掉了;在跨进程调用的时候,不会传递函数而是传递编号来指明要调用哪个函数
onTransact()代表回调, transact()代表主动!