Android 应用程序中的很多功能都会用到进程间通信(IPC),如 WebView、音视频播放、大图浏览、推送都会创建一个单独的进程。而系统服务,诸如电话、闹钟、AMS 等也是由系统创建的多进程。
使用多进程有如下优点:
- 可以开辟更多可用内存。一个进程分配一个 JVM,每个 JVM 的大小在同一个系统版本中都是固定的(可以通过 getprop dalvik.vm.heapsize 查看),创建多进程就会有多个 JVM 的内存可用
- 风险隔离。将有风险的操作放在多进程中,即便出问题挂掉了也不会影响主进程
1、概述
1.1 Binder 是什么
最容易想到的就是,Binder 是 Android 系统采用的一种线程间通信机制,但除此之外,在不同的场景下,它还有不同的解释:
- 从机制角度,Binder 是一种进程间通信机制;
- 从通信模型角度,Binder 是一种虚拟的物理驱动设备;
- 从代码角度,在应用层,Binder 是 IBinder 接口的实现类;在 native 层有 Binder.cpp 和 binder.c;在 kernel 层也有一个 binder.c。
1.2 为什么是 Binder
为什么在 Linux 已经具备管道、信号量、Socket 和共享内存等 IPC 方式的情况下,还要再为 Android 定制一个 Binder 呢?需要先了解 Android 的进程空间划分。
1.2.1 进程空间划分 & IPC 原理
Android 进程只能运行在自己进程所拥有的虚拟地址空间,每个进程的空间可以分为用户空间与内核空间。在进程之间,用户空间的数据不可共享,而内核空间的数据可以共享。所以两个进程的用户空间不能直接传输数据,只能通过共享的内核空间进行通信。
内存被操作系统划分成两块:用户空间和内核空间,用户空间是用户程序代码运行的地方,内核空间是内核代码运行的地方。为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不受影响。
用户空间和内核空间说的是虚拟内存,它们都需要映射到物理内存(内存条、磁盘)上。所有进程的内核空间映射的都是同一块物理内存,但是用户空间映射的物理内存是隔开的。对于 32 位系统来说,地址空间大小为 4G,用户空间会分配 3G,内核空间分配 1G(可通过参数调整);对于 64 位系统而言,低位的 0~47 位是有效的可变地址,寻址空间为 256T,一般用户空间高 16 位全补 0,内核空间高 16 位全补 1。
传统的 IPC 方式是由进程 A 发起通信,将数据从自己的用户空间先通过 copy_from_user 拷贝到内核空间,再由进程 B 去内核空间通过 copy_to_user 将数据拷贝到用户空间。整个数据传输过程要进行两次数据拷贝,效率比较低下。
1.2.2 Binder 通信原理
与传统 IPC 相比,Binder 通过 mmap() 进行内存映射,实现了内核空间与接收进程的内存共享,使得 IPC 过程仅需一次数据拷贝:
发送数据时仍然是从发送进程的用户空间通过 copy_from_user 拷贝到内核空间,但是接收进程的用户空间与内核空间的地址通过 mmap() 进行了映射,实现了物理地址的共享,这样接收进程不需要进行 copy_to_user 操作就可以在其用户空间内收到发送进程传来的数据。
最后来看下 Binder 与其它 IPC 方式的对比:
Binder 的性能更好更安全:
- 性能:通信过程仅需一次数据拷贝,仅次于无需拷贝的共享内存,但是共享内存会面临多线程环境,需要同步机制,使用起来麻烦
- 安全:Binder 由系统分配 UID,更安全,而其他 IPC 方式是通过判定 PID,并不靠谱。并且 Binder 同时支持实名服务(如系统服务,AMS、PMS 等)和匿名服务(用户自己创建的 Service 是匿名服务,其他进程拿不到)
2、Binder 通信模型
在 Binder 通信模型中有 4 个角色:
- Client 进程:发送数据的进程。
- Server 进程:接收数据的进程。
- ServiceManager:接受 Server 进程的注册,并保存 Server 信息。Client 要向特定的 Server 发起通信时,需要先去 ServiceManager 中获取 Server 实例。本身是一个特殊的 Binder。
- Binder 驱动:前三者互相通信的枢纽,类似一个中转站,完成数据在各个进程间的传递。
需要注意的点:
- 前三者都是在用户空间,而 Binder 驱动在内核空间。
- 看前面 ServiceManager 的描述,似乎它与 Client、Server 之间有直接联系,但实际上并没有,它们之间的联系都是通过与 Binder 驱动通信实现的。
- Client 与 Server 需要应用开发者自己实现,而 ServiceManager 和 Binder 驱动已经由系统实现。
我们再从 Android 系统层次结构的角度看一下 Binder 通信模型:
Framework 层我们能看到,Client 进程并不是直接操作的 Binder 对象,而是一个持有 Binder 引用的代理对象 BinderProxy,通过它去找到 Binder 本体再执行操作。
在 Native 层,BpBinder(客户端)和 BBinder(服务端)都是 IBinder 的派生类,BBinder 需要向 service_manager 先注册服务,而 BpBinder 需要通过 service_manager 获取服务,然后才能使用服务。它们之间的联系用虚线画出,表示它们之间并不是直接通信,而都是通过 ioctl 操作与 Binder 驱动通信,由 Binder 驱动将数据转发给接收对象。
Binder 源码的目录结构正是根据以上层次结构设计的:
/framework/base/core/java/ (Java)
/framework/base/core/jni/ (JNI)
/framework/native/libs/binder (Native)
/framework/native/cmds/servicemanager/ (Native)
/kernel/drivers/staging/android (Driver)
1、2属于 Java Framework 范畴,具体文件:
/framework/base/core/java/android/os/
- IInterface.java
- IBinder.java
- Parcel.java
- IServiceManager.java
- ServiceManager.java
- ServiceManagerNative.java
- Binder.java
/framework/base/core/jni/
- android_os_Parcel.cpp
- AndroidRuntime.cpp
- android_util_Binder.cpp (核心类)
3、4属于 Native Framework:
/framework/native/libs/binder
- IServiceManager.cpp
- BpBinder.cpp
- Binder.cpp
- IPCThreadState.cpp (核心类)
- ProcessState.cpp (核心类)
/framework/native/include/binder/
- IServiceManager.h
- IInterface.h
/framework/native/cmds/servicemanager/
- service_manager.c
- binder.c
5 属于 Kernel:
/kernel/drivers/staging/android/
- binder.c
- uapi/binder.h
源码流程的话,先从 Native 层的 service_manager 看起,因为它是 BpBinder 和 BBinder 正常工作的前提。
3、mmap 是什么
Linux 通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping)。
对文件进行 mmap,会在进程的虚拟内存分配地址空间,创建映射关系。实现这样的映射关系后,就可以采用指针的方式读写操作这一段内存,而系统会自动回写到对应的文件磁盘上。
mmap:用户空间与内核空间指的都是虚拟内存,它们都需要映射到真正的物理内存设备上。由于内核空间与物理内存本就存在映射关系,mmap 将接收进程的用户空间与内核空间进行映射,其实也是映射到了同一块物理内存上,这样内核空间的数据发生变化时,物理内存与用户空间映射的虚拟内存就都知道了数据的变化。mmap 能够让虚拟内存和指定的物理内存直接联系起来。对于 Binder 来说,接收进程与内核空间通过 mmap 共享同一块物理内存省去了一次拷贝
关于虚拟内存和物理内存的映射有一个很有趣的类比:物理内存就像地球,而虚拟内存则像地球仪,二者存在一个映射关系。应用只能操作虚拟地址,不能直接操作物理地址,但是数据最终还是存在物理地址上的;就像我们只能把玩地球仪,但把玩不了地球,可是我们的物品最终还是放在地球上,而不是放在地球仪上……
所有的系统资源管理都是在内核空间中完成的。比如读写磁盘文件,分配回收内存,从网络接口读写数据等等。用户空间通过系统调用(syscall)让内核空间完成这些功能。
对文件进行 mmap,会在进程的虚拟内存分配地址空间,创建映射关系。实现这样的映射关系后,就可以采用指针的方式读写操作这一段内存,而系统会自动回写到对应的文件磁盘上。比如说,用户空间是不可以直接操作文件的,因为用户空间用的是虚拟内存而不是物理内存,
写文件分三步:
- 调用 write,告诉内核需要写入数据的开始地址与长度
- 内核将数据拷贝到内核缓存
- 由操作系统调用,将数据拷贝到磁盘,完成写入
常规文件操作使用页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。
4、Binder JNI 启动流程
Android 系统开机过程中,init 进程通过解析 init.zygote.rc 文件创建 Zygote 进程,Zygote 进程对应的可执行程序为 app_process,对应的源文件为 app_main.cpp:
system\core\rootdir\init.zygote32.rc:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
Zygote 启动时会进入其 main(),判断是否是 Zygote 命令:
frameworks\base\core\jni\AndroidRuntime.cpp:
int main(int argc, char* const argv[]) {
// Parse runtime arguments. Stop at first unrecognized option.
bool zygote = false;
bool startSystemServer = false;
bool application = false;
String8 niceName;
String8 className;
++i; // Skip unused "parent dir" argument.
while (i < argc) {
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) {
// 判断是不是 zygote,是的话就把 zygote 变量置为 true
zygote = true;
niceName = ZYGOTE_NICE_NAME;
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName.setTo(arg + 12);
} else if (strncmp(arg, "--", 2) != 0) {
className.setTo(arg);
break;
} else {
--i;
break;
}
}
// zygote 为 true 就会启动 runtime 命令(runtime 是 AppRunTime,继承自
// AndroidRuntime),调用 AndroidRuntime 的 start()
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
return 10;
}
}
接下来会调用到 AndroidRuntime 的 start(),该函数仅需关注一个地方:
frameworks\base\core\jni\AndroidRuntime.cpp:
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
/*
* Register android functions.
*/
// 注册 JNI
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}
}
int AndroidRuntime::startReg(JNIEnv* env)
{
/*
* This hook causes all future threads created in this process to be
* attached to the JavaVM. (This needs to go away in favor of JNI
* Attach calls.)
*/
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
ALOGV("--- registering native functions ---\n");
/*
* Every "register" function calls one or more things that return
* a local reference (e.g. FindClass). Because we haven't really
* started the VM yet, they're all getting stored in the base frame
* and never released. Use Push/Pop to manage the storage.
*/
env->PushLocalFrame(200);
// 注册 JNI 这样 Java 才能调用 Native 方法
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return -1;
}
env->PopLocalFrame(NULL);
//createJavaThread("fubar", quickTest, (void*) "hello");
return 0;
}
//
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
for (size_t i = 0; i < count; i++) {
if (array[i].mProc(env) < 0) {
#ifndef NDEBUG
ALOGD("----------!!! %s failed to load\n", array[i].mName);
#endif
return -1;
}
}
return 0;
}
NDEBUG 是前面定义好的函数指针:
#ifdef NDEBUG
#define REG_JNI(name) { name }
struct RegJNIRec {
int (*mProc)(JNIEnv*);
};
#else
#define REG_JNI(name) { name, #name }
struct RegJNIRec {
int (*mProc)(JNIEnv*);
const char* mName;
};
#endif
register_jni_procs() 参数 array[] 是一个常量:
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_os_RuntimeInit),
REG_JNI(register_android_os_SystemClock),
REG_JNI(register_android_util_EventLog),
REG_JNI(register_android_util_Log),
...
// Binder JNI 注册
REG_JNI(register_android_os_Binder),
}
register_android_os_Binder() 是一个函数,在 android_util_Binder 文件中:
frameworks\base\core\jni\android_util_Binder.cpp:
int register_android_os_Binder(JNIEnv* env)
{
// 注册 Binder
if (int_register_android_os_Binder(env) < 0)
return -1;
// 注册 BinderInternal
if (int_register_android_os_BinderInternal(env) < 0)
return -1;
// 注册 BinderProxy
if (int_register_android_os_BinderProxy(env) < 0)
return -1;
jclass clazz = FindClassOrDie(env, "android/util/Log");
gLogOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
gLogOffsets.mLogE = GetStaticMethodIDOrDie(env, clazz, "e",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I");
clazz = FindClassOrDie(env, "android/os/ParcelFileDescriptor");
gParcelFileDescriptorOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
gParcelFileDescriptorOffsets.mConstructor = GetMethodIDOrDie(env, clazz, "<init>",
"(Ljava/io/FileDescriptor;)V");
clazz = FindClassOrDie(env, "android/os/StrictMode");
gStrictModeCallbackOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
gStrictModeCallbackOffsets.mCallback = GetStaticMethodIDOrDie(env, clazz,
"onBinderStrictModePolicyChange", "(I)V");
return 0;
}
三个注册的过程类似,以注册 Binder 为例:
// 为了让 Java 和 Native 可以互相调用
static int int_register_android_os_Binder(JNIEnv* env)
{
jclass clazz = FindClassOrDie(env, kBinderPathName);
// 让 Native 可以调用 Java 方法
gBinderOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z");
gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
// 让 Java 可以调用 Native,gBinderMethods 是方法映射关系。
return RegisterMethodsOrDie(
env, kBinderPathName,
gBinderMethods, NELEM(gBinderMethods));
}
类似于反射,保存 Java 方法的 mClass、mExecTransact、mObject 信息,以便 Native 调用 Java 方法。最后调用的 RegisterMethodsOrDie() 中的参数 gBinderMethods 是 Java 层方法名与对应的 Native 层的方法名:
static const JNINativeMethod gBinderMethods[] = {
/* name, signature, funcPtr */
{ "getCallingPid", "()I", (void*)android_os_Binder_getCallingPid },
{ "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid },
{ "clearCallingIdentity", "()J", (void*)android_os_Binder_clearCallingIdentity },
{ "restoreCallingIdentity", "(J)V", (void*)android_os_Binder_restoreCallingIdentity },
{ "setThreadStrictModePolicy", "(I)V", (void*)android_os_Binder_setThreadStrictModePolicy },
{ "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy },
{ "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
{ "init", "()V", (void*)android_os_Binder_init },
{ "destroy", "()V", (void*)android_os_Binder_destroy },
{ "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }
};
Binder JNI 注册的目的就是让 Java 和 native 能够互相调用。