阅读源码的文章会是一个系列,本篇主要内容是 zygote 启动主函数是如何确定的。
zygote 启动主函数是如何确定的
上篇文章中提到了属性服务,我觉得它更像关于这台手机的各种系统信息,并通过 key / value 的形式供所有程序使用。在手机上进入 adb shell 后输入 getprop 可以获取到各种属性值,这里只关注 zygote 的值,拿到它有什么用呢?其实是为了确定这台手机上的 zygote 的 rc 配置文件具体是使用的那个。
generic_x86:/ $ getprop
...
// zygote 启动该启动哪个
// ro 表示 read only
[ro.zygote]: [zygote32]
...
因为其实在 AOSP 的 /system/core/rootdir/ 下面其实现在有四个关于 zygote 的 rc 文件。
而 init.rc (init 进程中会解析这个文件)中是这样导入的:
...
// 这里就是通过属性值确认
import /init.${ro.zygote}.rc
...
# Mount filesystems and start core system services.
on late-init
...
// 在这个阶段触发 zygote 的启动
# Now we can start zygote for devices with file based encryption
trigger zygote-start
...
...
这时就会根据属性服务中 ro.zygote 取到的值,来到对应的 zygote rc 文件中了,而对于我的虚拟就是找到了 init.zygote32.rc 文件中,下面就是它的原文内容:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc reserved_disk
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
看上面内容,可以知道 zygote 对应的可执行程序是 /system/bin/app_process,不过我看到了不少文章都直接说出对应的代码文件其实是 /frameworks/base/cmds/app_process/app_main.cpp,我对这个结论十分困扰,虽然我也看了确实在 app_main.cpp 指定了 com.android.internal.os.ZygoteInit,但还是不太理解这样是一个怎么关系。
其实在没有摸索到答案之前,我也全局搜索过 app_process,也确实找到了 /frameworks/base/cmds/app_process/ 目录,下面 app_main.cpp 也确实指定了 ZygoteInit 是 zygote 的启动入口,但容易让人忽略的其实是同目录下的 Android.mk,这里面才是解决迷惑的关键:
// 指定了依赖的库
app_process_common_shared_libs := \
libandroid_runtime \
libbinder \
libcutils \
libdl \
libhidlbase \
liblog \
libnativeloader \
libutils \
// ...
// 指定了要编译的源文件
app_process_src_files := \
app_main.cpp \
LOCAL_SRC_FILES:= $(app_process_src_files)
// ...
// 指定了编译后的模块名
LOCAL_MODULE:= app_process
// ...
// 这里竟然写的是 BUILD_EXECUTABLE 而不是 BUILD_SHARED_LIBRARY 或 BUILD_STATIC_LIBRARY
include $(BUILD_EXECUTABLE)
// ...
如果你在 Android 中开发过 NDK,那你应该对 Android.mk 比较熟悉(虽然现在 CMake 多一些),我之前用它来做 so 文件的编译配置,所以满脑子都在想这个应该是用来编译动态库或静态库的(我还真的全局搜了 libapp_process),没想到这个 mk 下面直接写的 BUILD_EXECUTABLE,也就是源文件编译成可执行程序(如果写的是 BUILD_SHARED_LIBRARY 表示编译成动态库,BUILD_STATIC_LIBRARY 编译成静态库),并且还指定了名字,就叫 app_process,这下我终于明白了为什么启动 zygote 会来到这里。
或许在别人眼里这并不是一个问题,不过对我而言,本身我不太懂 c/c++、编译这些东西,确实研究了好久才解决了困扰我很久的这个问题,但我依然还是有一个问题,就是它编译之后怎么就跑到 /system/bin/ 下面去了,或许应该是就在这个目录下面编译的?
当然如果确定了 /frameworks/base/cmds/app_process/app_main.cpp,再去找 zygote 入口就容易多了。
int main(int argc, char* const argv[])
{
// ...
if (zygote) {
// 这里就给出了 zygote 的启动入口,而为什么调用了 main 方法,就是要看这个 start 函数
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
}
// ...
}
// 因为在 runtime.start 中通过 jni 的方式调用到了 ZygoteInit 的 main 方法
// 对应的文件是 /frameworks/base/core/jni/AndroidRuntime.cpp
// 这里的 className 就是上面传进来的 com.android.internal.os.ZygoteInit
// 这样写法也就是 JNI 中调用 Java 的写法,先找到 class,然后找到方法,进行调用
char* slashClassName = toSlashClassName(className != NULL ? className : "");
jclass startClass = env->FindClass(slashClassName);
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);
}
这个小问题解开了之后,那么其实 Android init 进程的启动入口问题也就一同解决了(对我来说),这个也只需要去看对应目录下的 Android.mk 即可,最终会编译为了 /init 这个可执行程序,在 Linux 启动 1 号进程时就会找到这个二进制文件。
关于 zygote 的源码阅读部分,会后续读完相关源码后进行整理输出。
感谢与参考
android编译系统makefile(Android.mk)写法
最后的最后
源码本身没有歧义,不过由于每个人基础不同,具体理解起来可能会些不同,所以有什么问题,也请大家多指点,多交流。
如果你觉得我写的还不错的话,那就通过点赞,点赞,还 tm 是点赞的方式给我反馈吧,感谢你的支持。