Android系统启动流程(二) —— Zygote进程的启动流程

接上篇: Android系统启动流程(一)init进程的启动流程

Zygote在英语中是受精卵的意思,从这个名字可以看出,zygote进程是用来孵化其他进程的,SystemServer和其他应用程序进程都是由Zygote进程所创建的。Zygote是以服务的形式存在于Android系统中的,是Android系统的一个重要的守护进程,下面我们通过源码来分析Zygote进程的启动流程。

1.解析Zygote服务的启动脚本并启动app_main

在init进程启动时,会解析Zygote服务进程的启动脚本并开启Zygote进程,针对不同位数的操作系统,Zygote也分别对应不同的启动脚本,在Android8.0系统的源码中共有4个启动脚本,分别是init.zygote32.rc(支持32位系统)、init.zygote64.rc(支持64位系统)、init.zygote32_64.rc(同时支持32位和64位,但以32位为主)、init.zygote64_32.rc(同时支持32位和64位,但以64位为主),我们以init.zygote32.rc为例来看一下Zygote服务的脚本源码:
目录位置:\system\core\rootdir\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
    socket zygote 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,参数包含-Xzygote /system/bin --zygote --start-system-server,class的名称为main。

init进程在解析完上面的代码后,会对zygote服务进行启动,启动部分的脚本代码如下:
源码位置:\system\core\rootdir\init.rc

on zygote-start && property:ro.crypto.state=unencrypted  //在.rc文件中,on表示一个触发器,zygote-start是触发器的名称
  //当该触发器被触发后,便会执行下面的命令
    exec_start update_verifier_nonencrypted
    start netd
    start zygote  //启动zygote服务
    start zygote_secondary

上面的代码是定义在init.rc中的一个触发器,当该触发器被触发后,便会执行start zygote这行命令,从而启动zygote服务,start命令对应的函数为do_start,源码如下:
源码路径:\system\core\init\builtins.cpp

static int do_start(const std::vector<std::string>& args) {
    // 1.根据service的名称找到该服务
    Service* svc = ServiceManager::GetInstance().FindServiceByName(args[1]);  
    if (!svc) {
        LOG(ERROR) << "do_start: Service " << args[1] << " not found";
        return -1;
    }
    if (!svc->Start())  //2.调用Start方法开启该服务
        return -1;
    return 0;
}

在注释1处通过service的名称来找到zygote这个服务的实例,然后再注释2出调用Service的Start方法来开启这个服务,我们来看一下Start方法的源码:
源码路径:\system\core\init\service.cpp

bool Service::Start() {

    ...

    pid_t pid = -1;
    if (namespace_flags_) {
        pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr);
    } else {
        pid = fork();  // 1.通过fork函数创建zygote子进程
    }

    if (pid == 0) {  //pid为0,说明当前在子进程中
        ...
        if (execve(strs[0], (char**) &strs[0], (char**) ENV) < 0) {  // 2.调用execve执行子进程的代码
            PLOG(ERROR) << "cannot execve('" << strs[0] << "')";
        }

        _exit(127);
    }

    ...
    return true;
}

在注释1处通过fork函数创建了一个子线程,由于fork函数是对父进程的自我复制,所以fork函数会同时在父进程和子进程中返回,并在父进程中返回子进程的id,在子进程中返回0。

在注释2处,通过调用execve函数来执行子进程的代码,从zygote的启动脚本中可以看到,该服务的执行代码位于 /system/bin/app_process中,对应的文件为app_main.cpp,这样程序即进入了app_main的main方法中。

2.通过AppRuntime启动Zygote

我们先来看一下app_main.cpp的main方法的源码:
源码路径:\frameworks\base\cmds\app_process\app_main.cpp

int main(int argc, char* const argv[])
{
    ...
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));//  1.创建AppRuntime实例

    ...

    while (i < argc) {  // 2.循环遍历参数
        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) { //当参数为“--start-system-server”时
            startSystemServer = true;  //将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;
        }
    }

    if (zygote) {
        // 3.如果zygote标志为true,则执行runtime的start方法
        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.");
    }
}

在注释1处创建了一个AppRuntime实例。

在注释2处对参数进行循环遍历,如果参数中含有 "--zygote",则将zygote置为true,如果参数中含有"--start-system-server",则将startSystemServer置为true。

在注释3处通过调用runtime的start方法来执行ZygoteInit文件中的代码,并将ZygoteInit的文件路径作为参数传入了start方法,这是一个java文件。start函数的源码位于AppRuntime的父类AndroidRuntime中,源码如下:
源码路径:\frameworks\base\core\jni\AndroidRuntime.cpp

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    ...

    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);//初始化jni
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote) != 0) {  // 1.启动java虚拟机
        return;
    }
    onVmCreated(env);

     //主要用于注册jni函数
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

    ...
    /* 2.通过jni的方式执行ZygoteInit的main方法*/
    char* slashClassName = toSlashClassName(className);  //将classname中的"."替换为“/”
    jclass startClass = env->FindClass(slashClassName);//通过jni的方式加载ZygoteInit的java类
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");  //找到ZygoteInit的main方法
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);//通过jni的方式调用ZygoteInit的mian方法
        ...
    }
    ...
}

由于ZygoteInit文件是由java代码编写的,因此我们需要用jni的方法来执行ZygoteInit的main方法。

在注释1处,通过startVm方法创建了java虚拟机。

在注释2处,通过执行一系列的jni方法,最终调用了ZygoteInit的main方法。

3.启动SystemServer并持续监听应用创建请求。

我们来看一下Zygote的main方法的源码:
源码路径:\frameworks\base\core\java\com\android\internal\os\ZygoteInit.java

public static void main(String argv[]) {
        ZygoteServer zygoteServer = new ZygoteServer();  //创建ZygoteServer实例

        ...
       
        try {
            
            ...

            zygoteServer.registerServerSocket(socketName);  // 1.注册ServerSocket
            
            ...

            if (startSystemServer) {
                startSystemServer(abiList, socketName, zygoteServer);  // 2.启动SystemServer
            }

            Log.i(TAG, "Accepting command socket connections");
            zygoteServer.runSelectLoop(abiList);  // 3.开启事件循环,不断监听新的请求

            zygoteServer.closeServerSocket();
        } catch (Zygote.MethodAndArgsCaller caller) {
            caller.run();
        } catch (Throwable ex) {
            Log.e(TAG, "System zygote died with exception", ex);
            zygoteServer.closeServerSocket();
            throw ex;
        }
    }

在main方法中,首先创建了一个ZygoteServer实例,然后在注释1处,通过调用zygoteServer的registerServerSocket方法对ServerSocket进行了注册。

在注释2处调用startSystemServer方法开启了SystemServer进程。

在注释3处通过调用zygoteServer的runSelectLoop方法开启了事件循环,这样zygote进程就可以持续监听新的应用进程创建请求。

我们先来看一下registerServerSocket方法的源码:
源码路径:\frameworks\base\core\java\com\android\internal\os\ZygoteServer.java

private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
private LocalServerSocket mServerSocket;

void registerServerSocket(String socketName) {
        if (mServerSocket == null) {
            int fileDesc;
            //通过拼接字符串得到最终的Socket名称,最后的结果为“ANDROID_SOCKET_zygote”
            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
            try {
                String env = System.getenv(fullSocketName);
                fileDesc = Integer.parseInt(env);
            } catch (RuntimeException ex) {
                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
            }

            try {
                FileDescriptor fd = new FileDescriptor();//创建文件描述符对象
                fd.setInt$(fileDesc);
                mServerSocket = new LocalServerSocket(fd);//创建ServerSocket对象
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    }

在registerServerSocket方法中,首先通过字符串拼接的方式获得了Socket的名称,然后根据这个名称创建了一个文件描述符对象。基于linux一切都是文件的思想,socket也被看作是一个文件,该文件描述符即用来表示该socket。

然后通过这个文件描述符创建了一个LocalServerSocket对象,通过名称我们便可以看出,这是一个运行于服务端的socket,它的作用便是用来监听新的应用进程创建请求。

我们再来看一下runSelectLoop方法:

void runSelectLoop(String abiList) throws Zygote.MethodAndArgsCaller {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

        fds.add(mServerSocket.getFileDescriptor());
        peers.add(null);

        /* 1.开始无限循环,不断监听新的请求 */
        while (true) {
            /* 将fds中的数据转移到pollFds数组中 */
            StructPollfd[] pollFds = new StructPollfd[fds.size()];
            for (int i = 0; i < pollFds.length; ++i) {
                pollFds[i] = new StructPollfd();
                pollFds[i].fd = fds.get(i);
                pollFds[i].events = (short) POLLIN;
            }
            try {
                Os.poll(pollFds, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
            for (int i = pollFds.length - 1; i >= 0; --i) {
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }
                if (i == 0) {// 2.当数组中没有未执行的任务时
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);//不断进行监听,当有新请求时便会返回

                    //将这个连接请求放入数组中
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    boolean done = peers.get(i).runOnce(this);// 3.从peers中去除连接请求并执行
                    if (done) {
                        //执行完成后从数组中移除
                        peers.remove(i);
                        fds.remove(i);
                    }
                }
            }
        }
    }

在runSelectLoop方法中,首先创建了一个FileDescriptor数组和一个ZygoteConnection数组,他们用来存储新接收到的请求。在注释1处开启了一个无限循环来不断监听新的请求,因此zygote进程在android系统的运行过程中会一直存在,直到系统关闭。

在这个无限循环中,先将fds数组的数据转移到了pollFds数组中,然后对pollFds数组进行了遍历,当i==0时,说明数组中所有请求任务都已经执行完了,那么调用acceptCommandPeer方法来获取新的请求,acceptCommandPeer方法是一个阻塞方法,如果没有新的连接请求,acceptCommandPeer会一直阻塞,直到有新的连接请求到来时,acceptCommandPeer才会将这个新的请求返回。获取到新的请求后便将这个请求放入peers和fds数组中。

当i不为0时,说明数组中还存在未执行的请求,则将请求取出并调用runOnce方法来执行这个请求。

我们先来看一下acceptCommandPeer方法的源码:

private ZygoteConnection acceptCommandPeer(String abiList) {
        try {
            return createNewConnection(mServerSocket.accept(), abiList);//调用accept方法等待新的请求
        } catch (IOException ex) {
            throw new RuntimeException(
                    "IOException during accept()", ex);
        }
    }

    protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
            throws IOException {
        return new ZygoteConnection(socket, abiList);  //创建ZygoteConnection实例
    }

acceptCommandPeer的源码非常简单,就是就是调用accept方法等待新的请求,该方法会一直阻塞当前线程,直到有新的请求到来。

我们再来看一下ZygoteConnection的runOnce方法:
源码路径:\frameworks\base\core\java\com\android\internal\os\ZygoteConnection.java

boolean runOnce(ZygoteServer zygoteServer) throws Zygote.MethodAndArgsCaller {

            ...          

            pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
                    parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
                    parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
                    parsedArgs.appDataDir);

       ...

    }

runOnce方法的代码很长,我们只看最关键的一句代码,即调用forkAndSpecialize方法创建新的进程,该方法最终会调用native方法来fork新的应用程序进程。

之前我们讲过,zygote进程在启动的时候会创建一个java虚拟机,而我们的应用程序进程都是由zygote进程fork得来的,而fork的本质是对父进程的自我复制,因此所有的应用程序子进程也会获得一个复制而来的java虚拟机副本,这样便无需在应用程序进程中单独启动java虚拟机了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容