什么是进程间通信 百度百科中对进程间通信的解释:进程间通信(IPC,Interprocess communication)是一组编程接口。能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息, 简单来说就是让我们能够在不同进程间,进行数据的交互。
为什么要用进程间通信 在理解了什么是进程间通信,就有一个值得思考的问题。为什么要有进程间通信,放在一起不行么?在回答这个问题之前,想象一下,所有的用户进程都认为是一个小房子,原本大家都住在自己的房间里,没有了障碍,我就能去你家,随意的拿走你家的任何东西,
操作系统为了阻挡你去串门,给你设置了一个障碍,让你不能够所以的访问其他进程的信息,这个障碍在操作系统中有一个专门的名字: 进程隔离
,其目的就是为保护操作系统中进程互不干扰。
那么为什么就一定需要进程间通信呢?设想一下,大家都住在一个楼里面,难免会遇到家里没酱油了,就需要去隔壁家借一点酱油,操作系统就不能不让你去借,这就是所谓的进程间通信。
进程间通信有哪些方式 ContentProvider为存储和获取数据了提供统一的接口,它可以在不同的应用程序之间共享数据,本身就是适合进程间通信的。ContentProvider底层实现也是Binder,但是使用起来比AIDL要容易许多。系统中很多操作都采用了ContentProvider,例如通讯录,音视频等,这些操作本身就是跨进程进行通信。
两个进程通过读写同一个文件来进行数据共享,共享的文件可以是文本、XML、JOSN。文件共享适用于对数据同步要求不高的进程间通信。
序列化指的是Serializable/Parcelable,Serializable是Java提供的一个序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。Parcelable接口是Android中的序列化方式,更适合在Android平台上使用,用起来比较麻烦,效率很高。
Bundle Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间传输。Acitivity、Service、Receiver都是在Intent中通过Bundle来进行数据传递。 上面的方式。除了文件共享,看一下其他的几种方法,都是使用Binder去实现进程间通信。
Binder是什么 Binder 前生是Be Inc公司开发的OpenBinder框架,后来后来OpenBinder的作者Dianne Hackborn加入了Google公司,并负责Android平台的开发工作,顺便把这项技术也带进了Android。Binder本身是Android上的进程间通信技术,
Android平台绝大部分的跨进程都是通过Binder完成的.而他也承载着极其重要的作用。
为什么要用Binder 回答为什么要用Binder 之前,其实还有一个问题,除了Binder 我们还有哪些选择?
众说周知,Android是基于Linux内核的,Linux 本身就提供了很多IPC机制,Linux IPC通信的过程,进程A通过copy_from_user
的函数将数据拷贝到内核空间的缓冲区,内核空间通过copy_to_user
的函数将数据在拷贝给进程B,一共经历了2次数据拷贝
👆Linux IPC 通信过程
既然已经可以通过Linux这种IPC机制去做进程间通信,为什么还要搞出Binder机制呢。先放上一个Binder的流程图,与Linux 传统的IPC 通信进行对比
👆Binder 通信过程
性能 binder,通过一次copy将数据复制到内核空间的缓冲区,然后内核空间通过内存映射(mmap)的方式,将数据映射到数据接收缓存区,在以同样的方式将数据接收缓存区的数据映射到进程B,相比Linux 少了一次copy 的过程,其性能也就更高了。相比于内存共享,多了一次拷贝
IPC方式 数据copy次数 共享内存 0 Binder 1 Linux 传统方式 2
其次,Linux方式 将数据冲内核空间复制到进程B的过程中,不知道其需要多大的空间,所以需要竟可能申请更大的内存去使用。会造成一定的内存浪费。
稳定性 Binder 基于 C/S 架构,架构清晰,职责明确,其稳定性完全高于全依赖于内存共享的方式,共享内存没有分层,难以控制,并发同步访问临界资源时,可能还会产生死锁
安全性 传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份,Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志
总结
从性能上,内存共享肯定是最好的。Binder 其次,最差的是Linux 传统的方式。从稳定性上和安全性分析,Binder 最好。综合考虑,Binder 就成为了Android上跨进程通讯的首选方式
Binder 如何通信的 架构分析 在分析Binder 之前,要知道Binder 是基于C/S 架构。即 Client/Servcie ,Service 需要先注册到ServiceManager. Client 在通过ServiceManager 去获取服务,从而完成连接关系。如下图所示架构
但是发现,ServiceManager属于一个单独的进程,Client/Service和ServiceManager都存在着进程隔离,如此,Servcie注册到ServiceManager进程依然需要Binder去完成,所以最终的进程间通信应该如下所示
如何发送数据 借用之前写 使用AIDL进程间通信 中的示例 传递一个int 类型的参数观察其传递过程,了解去传递数据的过程是如果进行的调用自己创建的方法。将对象传入
JAVA层 先看到JAVA层。执行代理类,数据会被包装成Parcel对象,(注意 Parcel 是 java 的 Parcel 对象).然后执行BinderProxy代理对象,最终执行native 方法
1 2 3 var info = 100 aidl?.getAge(info)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private static class Proxy implements com .starot .lib_intent .aidl .ITest { private android.os.IBinder mRemote; @Override public com.starot.lib_intent.aidl.BookInfo updateBookOut (com.starot.lib_intent.aidl.BookInfo book) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(age); boolean _status = mRemote.transact(Stub.TRANSACTION_updateBookOut, _data, _reply, 0 ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 public final class BinderProxy implements IBinder { public native boolean transactNative (int code, Parcel data, Parcel reply, int flags) throws RemoteException ; public boolean transact (int code, Parcel data, Parcel reply, int flags) throws RemoteException { return transactNative(code, data, reply, flags); } }
Navite 层 数据继续往下层传递。此时就进入Navite层,在这里原先的 java 层 Parcel 就转成了 native 的 Parcel。通过BpBinder将数据传入到IPCThreadState中进行处理,处理的方式是将原先native的数据通过memcpy进行复制,保存在mOut变量中。这个mOut 也是一个Parcel对象。最终走到 ioctl 方法 调用驱动层的代码.将数据进行内存映射。
frameworks/base/core/jni/android_util_Binder.cpp
1 2 3 4 5 6 7 8 9 10 11 12 static jboolean android_os_BinderProxy_transact (JNIEnv* env, jobject obj, jint code, jobject dataObj, jobject replyObj, jint flags) { Parcel* data = parcelForJavaObject(env, dataObj); Parcel* reply = parcelForJavaObject(env, replyObj); IBinder* target = getBPNativeData(env, obj)->mObject.get(); status_t err = target->transact(code, *data, reply, flags); return JNI_FALSE; }
1 2 3 4 5 6 7 8 9 10 11 12 13 status_t BpBinder::transact ( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { if (mAlive) { status_t status = IPCThreadState::self()->transact( mHandle, code, data, reply, flags); if (status == DEAD_OBJECT) mAlive = 0 ; return status; } return DEAD_OBJECT; }
1 2 3 4 5 6 7 8 9 10 11 12 13 status_t IPCThreadState::transact (int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { err = writeTransactionData(BC_TRANSACTION_SG, flags, handle, code, data, nullptr ); err = waitForResponse(reply); err = waitForResponse(&fakeReply); err = waitForResponse(nullptr , nullptr ); return err; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 status_t IPCThreadState::writeTransactionData (int32_t cmd, uint32_t binderFlags, int32_t handle, uint32_t code, const Parcel& data, status_t * statusBuffer) { binder_transaction_data_sg tr_sg; tr_sg.transaction_data.data_size = data.ipcDataSize(); tr_sg.transaction_data.data.ptr.buffer = data.ipcData(); tr_sg.transaction_data.offsets_size = data.ipcObjectsCount()*sizeof (binder_size_t ); tr_sg.transaction_data.data.ptr.offsets = data.ipcObjects(); tr_sg.buffers_size = data.ipcBufferSize(); mOut.write(&tr_sg, sizeof (tr_sg)); return NO_ERROR; }
1 2 3 4 5 6 7 8 9 10 status_t Parcel::write (const void * data, size_t len) { void * const d = writeInplace(len); if (d) { memcpy (d, data, len); return NO_ERROR; } return mError; }
👆system/libhwbinder/Parcel.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 status_t IPCThreadState::waitForResponse (Parcel *reply, status_t *acquireResult) { while (1 ) { if ((err=talkWithDriver()) < NO_ERROR) break ; reply->ipcSetDataReference( reinterpret_cast <const uint8_t *>(tr.data.ptr.buffer), tr.data_size, reinterpret_cast <const binder_size_t *>(tr.data.ptr.offsets), tr.offsets_size/sizeof (binder_size_t ), freeBuffer, this ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 status_t IPCThreadState::talkWithDriver (bool doReceive) { binder_write_read bwr; bwr.write_size = outAvail; bwr.write_buffer = (uintptr_t )mOut.data(); do { #if defined(__ANDROID__) if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0 ) err = NO_ERROR; else err = -errno; #else err = INVALID_OPERATION; #endif } while (err == -EINTR); }
到这里为止,数据已经从java层传到了binder驱动。
内核层 Binder 驱动 使用mmap 的方式 内存映射,中间过程暂时没有研究出来,回头在细看一下
总结 通过一步一步的分析,可以看到数据被封装成驱动能够识别的数据类型,然后通过 ioctl 方法 将数据传入到驱动,在Binder驱动中,通过mmap 的方式,对数据进行内存映射,从而完成数据从java层到内核的过程。下面是发送流程。
如何接受数据 Java层 这里我们反推,先从Java层入手,获取client传过来的数据,往上看。一步一步分析Java层的数据哪里是源头
1 2 3 4 5 6 class MyBinder : ITest.Stub() { override fun getAge (age: Int) : Int { return 10001 } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static abstract class Stub extends android .os .Binder implements com .starot .lib_intent .aidl .ITest { @Override public boolean onTransact (int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { java.lang.String descriptor = DESCRIPTOR; switch (code){ case TRANSACTION_getAge: { data.enforceInterface(descriptor); int _arg0; _arg0 = data.readInt(); int _result = this .getAge(_arg0); reply.writeNoException(); reply.writeInt(_result); return true ; } } } }
1 2 3 4 5 6 7 private boolean execTransactInternal (int code, long dataObj, long replyObj, int flags, int callingUid) { res = onTransact(code, data, reply, flags); return res; } `
1 2 3 4 5 6 7 @UnsupportedAppUsage private boolean execTransact (int code, long dataObj, long replyObj, int flags) { return execTransactInternal(code, dataObj, replyObj, flags, callingUid); }
Native层 通过注释Entry point from android_util_Binder.cpp's onTransact
可以知道 Java Binder.java 的 execTransact 方法是在 frameworks/base/core/jni/android_util_Binder.cpp 中被触发
1 2 3 4 5 6 7 8 9 10 11 class JavaBBinder : public BBinder{ status_t onTransact ( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0 ) override { jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact, code, reinterpret_cast <jlong>(&data), reinterpret_cast <jlong>(reply), flags); return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static int int_register_android_os_Binder (JNIEnv* env) { jclass clazz = FindClassOrDie(env, kBinderPathName); gBinderOffsets.mClass = MakeGlobalRefOrDie(env, clazz); gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact" , "(IJJI)Z" ); gBinderOffsets.mGetInterfaceDescriptor = GetMethodIDOrDie(env, clazz, "getInterfaceDescriptor" , "()Ljava/lang/String;" ); gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject" , "J" ); return RegisterMethodsOrDie( env, kBinderPathName, gBinderMethods, NELEM(gBinderMethods)); }
总结 通过倒退,能够知道在Binder虚拟机完成工作以后,执行到了 BBinder 的 onTransact 方法,然后到了 java Binder 的 execTransact 方法 最终执行到了 Stub 的 onTransact 方法。
总结 通过AIDL的方式我们能够看到数据如果从Client进程到Service进程。
最后 Binder 是个很大的东西。其中很多部分还没有看懂。以后还是需要在继续看一下。争取彻底搞懂其中每个部分的工作流程。上文的分析,完全是通过数据传递的过程分析。其实还有数据的校验,MMAP 的过程。和Binder驱动交互的过程。等很多细节。
参考