Android 10.0 源码阅读 | zygote 启动主函数是如何确定的 | 番外篇

572 阅读4分钟

阅读源码的文章会是一个系列,本篇主要内容是 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.mk

android编译系统makefile(Android.mk)写法

最后的最后

源码本身没有歧义,不过由于每个人基础不同,具体理解起来可能会些不同,所以有什么问题,也请大家多指点,多交流。

如果你觉得我写的还不错的话,那就通过点赞,点赞,还 tm 是点赞的方式给我反馈吧,感谢你的支持。