前言
在上一篇讲rc脚本的例子中,已经出现了zygote的身影,在Gityuan的系统启动架构图中也能看到zygote处在C++ Framework和Java Framework之间,由此可以猜测此进程必定是承上启下的开创性进程。首先让我们感性的认识一下这个进程。adb shell "ps -A | grep zygote"
user pid ppid
root 1570 1 9069288 110472 0 0 S zygote64
root 1630 1 534487112 50836 0 0 S zygote
父进程id为1,就是上一篇的init进程,但有点奇怪,为啥有两个zygote?等会儿我们带着问题去源码中找答案。adb shell "ps -A | grep 1570"
user pid ppid
root 1570 1 9069288 110472 0 0 S zygote64
system 3236 1570 17905656 760888 0 0 S system_server
u0_a206 4872 1570 12824556 477164 0 0 S com.android.systemui
u0_a248 14681 1570 10596876 120760 0 0 S com.android.mms
u0_a250 15976 1570 9754228 121768 0 0 S com.android.contacts
... ...
非常多且大部分是APP,他们的父进程都是pid 1570(zygote),其中包含一个叫system_server的进程。我们在第三篇曾单步调试过这个进程,当时它在UI中显示为system_process。到这里我们几乎可以笃定的相信所有应用进程都是zygote孵化而来,因此,接下来我们就看看zygote怎么启动,它又如何启动(孵化)别的进程。
上一篇讲到init进程通过rc脚本启动后续进程,那么我们找一下zygote在哪里。注:编译之前源码状态下,系统启动相关的rc脚本都在system\core\rootdir目录下,其他服务或进程的rc脚本一般都放在该模块代码根目录,比如:surfaceflinger.rc就在frameworks\native\services\surfaceflinger中。// system\core\rootdir\init.rc
// 引入了zygote的rc脚本,adb shell getprop ro.zygote,查询到其值为zygote64_32
import /system/etc/init/hw/init.${ro.zygote}.rc
// system\core\rootdir\init.zygote64_32.rc
// 引入了init.zygote64.rc,同时定义了一个进程zygote_secondary
import /system/etc/init/hw/init.zygote64.rc
service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload
... ...
onrestart restart zygote
... ...
// system\core\rootdir\init.zygote64.rc
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
class main //入口
... ...
socket zygote stream 660 root system //此进程需要创建socket
... ...
//zygote重启时,需要同时重启以下服务
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart media.tuner
onrestart restart netd
onrestart restart wificond
zygote的定义找到了,在哪里启动的呢?init.rc中一番搜索之后,得出结论。// 1. late-init中触发zygote-start
on late-init
... ...
trigger zygote-start
... ...
// 2. zygote-start中直接启动zygote
on zygote-start
... ...
start zygote
start zygote_secondary
... ...
这里我们就可以回答开头的疑问了,init确实启动了两个进程:zygote和zygote_secondary,从rc脚本的关系来看,zygote_secondary有点像zygote的守护进程,因为zygote_secondary重启时也会重启zygote。因此我们的主要目标还是zygote,zygote_secondary就不去深究了。
zygote的入口函数在frameworks/base/cmds/app_process/app_main.cpp中。等等,这里我们需要稍微停一下,你咋知道入口在这里?难不成自学一个模块,要在网上先搜索从哪里开始?如果没人输出过这个模块的文章,还学不成了?其实不然,从开头rc文件的分析我们已经知道,zygote进程代码被存放在了一个二进制文件中/system/bin/app_process64,这个文件名一定在编译脚本(.bp文件)中出现过,所以我们需要在bp文件中找到它。这里又需要大家有一点AOSP编译方面的基础知识,但不要害怕,真的只需要一点,甚至直接打开bp文件都能猜个大概。bp文件的语法及详细解释可以参考官方文档(https://source.android.com/docs/setup/reference)。我们直接在cs.android.com上搜索app_process64,出来的结果不是很满意,没有bp文件。仔细看看,后缀64?标识系统平台?去掉后缀再搜。cc_binary {
name: "app_process",
srcs: ["app_main.cpp"],
multilib: {
lib32: {
suffix: "32",
},
lib64: {
suffix: "64",
},
}
... ...
}
太明显了,官方文档基本不需要看。盲猜一下,它说的是:编译一个二进制文件,名称为app_process,源码在当前目录的app_main.cpp中,multilib指定多平台后缀。真真儿的大白话啊。好了,这下我们可以非常安心的说出开头那句话:zygote的入口函数在frameworks/base/cmds/app_process/app_main.cpp中。
// frameworks/base/cmds/app_process/app_main.cpp
int main(int argc, char* const argv[]) {
//很多与参数相关的操作,读取、解析、设置等
... ...
if (strcmp(arg, "--zygote") == 0) {
// rc文件中配置的启动参数有--zygote,因此变量zygote为true
zygote = true;
// ZYGOTE_NICE_NAME为宏定义,值为zygote64,即进程名
niceName = ZYGOTE_NICE_NAME;
}
... ...
if (zygote) {
// runtime的start函数参数:一个类名、args字符数组(参数)以及zygote布尔变量
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (!className.isEmpty()) {
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.");
}
}
简单回溯一下runtime实例,可以得知,对应的类为AppRuntime,其父类为AndroidRuntime,start函数是在父类中实现的。// frameworks\base\core\jni\AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, const Vector& options, bool zygote) {
... ...
static const String8 startSystemServer("start-system-server");
// Whether this is the primary zygote, meaning the zygote which will fork system server.
// 是否是主zygote就看是否有start-system-server参数,即是否负责启动system server进程。
// 这就是zygote和zygote_secondary的区别之一
bool primary_zygote = false;
... ...
/* start the virtual machine */
// 初始化JNI环境,这里我们只需要理解JNI就是连接C++和Java的桥梁即可
JniInvocation jni_invocation;
// Init函数中会动态加载libart.so库,这个在后面会用到
jni_invocation.Init(NULL);
JNIEnv* env;
// 创建虚拟机,如何创建?后面介绍
if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
return;
}
onVmCreated(env);
// startReg中注册当前阶段需要使用的JNI函数
if (startReg(env) 0) {
ALOGE("Unable to register all android natives\n");
return;
}
... ...
char* slashClassName = toSlashClassName(className != NULL ? className : "");
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
} else {
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
} else {
// 最终调用com.android.internal.os.ZygoteInit的main函数
// 这句调用是阻塞的,也就是说如果Java中的函数不执行完
// CallStaticVoidMethod就不会返回
env->CallStaticVoidMethod(startClass, startMeth, strArray);
}
}
... ...
}
以上代码中env(JNI环境)的各种调用就是C层调用Java函数的用法,看起来是不是跟Java反射调用的代码很相似。关于JNI的介绍,可以参考这一篇(https://juejin.cn/post/7139843955457261605),快速过一下,用到时再查阅细节。// frameworks\base\core\jni\AndroidRuntime.cpp
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool primary_zygote) {
... ...
/*
* Initialize the VM.
* The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
* If this call succeeds, the VM is ready, and we can start issuing
* JNI calls.
*/
// JNI_CreateJavaVM最终会调用libart.so中的函数JNI_CreateJavaVM创建虚拟机
// 需要研究虚拟机的伙伴,可以从这个so开始
if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) 0) {
ALOGE("JNI_CreateJavaVM failed\n");
return -1;
}
return 0;
}
到这里,我们已经进入Java Framework。// frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
public static void main(String[] argv) {
// 我们的zygote在rc文件中并没有配置懒加载,因此这里肯定会执行if分支
if (!enableLazyPreload) {
... ...
// 预加载类和资源
// 比如:/system/etc/preloaded-classes中的Java类,我们熟悉的四大组件赫然在列
preload(bootTimingsTraceLog);
... ...
}
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
// {@code r == null} in the parent (zygote) process,
// and {@code r != null} in the child (system_server) process.
// 这两行注释的意思是:
// 在zygote进程中会返回null,接下来执行runSelectLoop
// 在SystemServer进程中会返回一个runnable,接下来执行r.run()
if (r != null) {
r.run();
return;
}
}
Log.i(TAG, "Accepting command socket connections");
// The select loop returns early in the child process after a fork and
// loops forever in the zygote.
caller = zygoteServer.runSelectLoop(abiList);
... ...
}
系统启动流程到forkSystemServer函数这里就开始“分叉”了,因为一个新的进程SystemServer诞生了。zygote和SystemServer将分别执行各自的后续逻辑。我们在下一篇讲SystemServer的文章中会以forkSystemServer函数作为开始。这里我们继续跟踪zygote的逻辑。// frameworks/base/core/java/com/android/internal/os/ZygoteServer.java
Runnable runSelectLoop(String abiList) {
... ...
while (true) {
... ...
// poll函数等待pollFDs文件描述符状态变化的消息
//
// pollTimeoutMs参数:
// 为正:在指定的毫秒数内等待文件描述符的状态变化,超时或有消息到来后poll就返回
// 为0:表示立即检测文件描述符状态,立即返回
// 为-1:表示一直等待,直到有文件描述符的状态变化
//
// pollReturnValue返回值:
// 为0:没有文件描述符可用,即没有需要处理的消息
// 为正:有文件描述符可用,即需要处理消息
// 为负:出现错误
pollReturnValue = Os.poll(pollFDs, pollTimeoutMs);
... ...
}
}
正常情况下,Zygote进程就会在这个循环里一直执行下去,响应新建进程的消息或继续循环等待。注:while循环中会有USAP(Unspecialized App Process)的逻辑,这是Android10之后,新增的提升进程创建效率的机制,简单来说,就是zygote会预创建一些进程放入池中,当有新建需求时,就从池中取出一个。为了突出主线,我们剥离了这部分逻辑,若想深入了解,可以参考芦大佬的这篇文章(https://juejin.cn/post/6922704248195153927)。
流程来到zygote这里,逻辑变得复杂,Zygote自身的初始化、虚拟机的创建、特殊Zygote进程(webview_zygote)的创建、SystemServer的创建、普通APP进程的创建、USAP机制,代码分支众多,判断条件一层叠一层,我们紧紧抓住Zygote主线进行探索,后续再逐步完善其他分支的理解。让我们用一张图概括一下这段历程。C/C++下的日志开关
面对复杂的逻辑,仅仅看代码已经不能满足需求,因此我们需要观察日志。对于不熟悉C/C++的伙伴,简单介绍一下相关内容。以app_main.cpp为例,其中main函数一进来就会看到:if (!LOG_NDEBUG) {
String8 argv_String;
for (int i = 0; i argv_String.append("\"");
argv_String.append(argv[i]);
argv_String.append("\" ");
}
ALOGV("app_process main with argv: %s", argv_String.string());
}
LOG_NDEBUG是一个宏定义变量,是v级日志的输出开关,为0输出,为1不输出。默认情况下,只要你编译的是release版本,那么LOG_NDEBUG都为1。如果我们想要看到ALOGV的输出,就需要在当前cpp文件最开始加入"#define LOG_NDEBUG 0"。不要被if语句的判断条件迷惑,如果不在文件开头定义LOG_NDEBUG为0,即使把if分支里面的代码提出来,一样得不到日志输出。这是为什么?因为ALOGV也是一个宏定义,且它的定义与LOG_NDEBUG的取值相关。具体原因看看以下宏定义就明白了。另外,除了在IDE中看日志,我们也经常使用命令导出日志到本地查看:adb logcat -d > log.txt。// system/logging/liblog/include/log/log_main.h
/*
* Normally we strip the effects of ALOGV (VERBOSE messages),
* LOG_FATAL and LOG_FATAL_IF (FATAL assert messages) from the
* release builds by defining NDEBUG. You can modify this (for
* example with "#define LOG_NDEBUG 0" at the top of your source
* file) to change that behavior.
*/
// 其实上面的注释已经说了打开日志的方法,有时看注释确实能走很多捷径
// NDEBUG也是一个宏定义变量,它是编译器在编译期间定义的,我这里在代码中打印它是为1
// 我猜测如果编译的是debug版本,NDEBUG应该打印出来就是0
#ifndef LOG_NDEBUG
#ifdef NDEBUG
#define LOG_NDEBUG 1
#else
#define LOG_NDEBUG 0
#endif
#endif
... ...
// ALOGV的宏定义
#ifndef ALOGV
#define __ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
#if LOG_NDEBUG
#define ALOGV(...) \
do { \
__FAKE_USE_VA_ARGS(__VA_ARGS__); \
if (false) { \
__ALOGV(__VA_ARGS__); \
} \
} while (false)
#else
#define ALOGV(...) __ALOGV(__VA_ARGS__)
#endif
#endif
通过上面的头文件内容,还可以知道,我们可以通过LOG_TAG这个宏定义变量设置日志Tag,也是在cpp文件开头定义即可,就像我们经常在Java类第一行写的“public static final String TAG = xxx”。
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!