Android系统启动过程

参考链接:

《深入理解Android 卷I》 《Linux 的启动流程》 《Android系统架构开篇》

引言

Google官方提供的经典分层架构图,从下往上依次分为Linux内核、HAL、系统Native库和Android运行时环境、Java框架层以及应用层这5层架构,其中每一层都包含大量的子模块或子系统。

Android系统架构

这幅图很清晰的展现了Android系统从下往上的层次关系,这种关系恰好也是整个系统启动大过程的缩影。

init是Linux系统中用户空间的第一个进程,要搞清楚Android的启动过程,我们先来看一下Linux系统的启动过程。

Linux启动过程

Linux启动过程

第一步:加载内核

操作系统接管硬件以后,首先读入 /boot 目录下的内核文件

第二步:启动初始化进程

内核文件加载以后,就开始运行第一个程序 /sbin/init,它的作用是初始化系统环境。

由于init是第一个运行的程序,它的进程编号(pid)就是 1。其他所有进程都从它衍生,都是它的子进程。

第三步:确定运行级别

许多程序需要开机启动。它们在Windows叫做”服务”(service),在Linux就叫做”守护进程”(daemon)。

init进程的一大任务,就是去运行这些开机启动的程序。但是,不同的场合需要启动不同的程序,比如用作服务器时,需要启动Apache,用作桌面就不需要。Linux允许为不同的场合,分配不同的开机启动程序,这就叫做”运行级别”(runlevel)。也就是说,启动时根据”运行级别”,确定要运行哪些程序。

第四步:加载开机启动程序

init进程逐一加载开机启动程序,其实就是运行/etc/init.d目录里的启动脚本。

第五步、用户登录

开机启动程序加载完毕以后,就要让用户登录了。一般来说,用户的登录方式有三种:

  1. 命令行登录
  2. ssh登录
  3. 图形界面登录

第六步、进入 login shell

所谓shell,简单说就是命令行界面,让用户可以直接与操作系统对话。用户登录时打开的shell,就叫做login shell。

第七步,打开 non-login shell

上一步完成以后,Linux的启动过程就算结束了,用户已经可以看到命令行提示符或者图形界面了。用户进入操作系统以后,常常会再手动开启一个shell。这个shell就叫做 non-login shell,意思是它不同于登录时出现的那个shell,不读取/etc/profile和.profile等配置文件。它会读入用户自己的bash配置文件 ~/.bashrc。大多数时候,我们对于bash的定制,都是写在这个文件里面的。

init过程分析

上面我们看到了linux系统的启动过程,Android系统是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程。

init的入口函数是main,源码中main方法代码很长,下面简要来分析一下做了什么操作。

/system/core/init/init.c

int main(int argc, char **argv)
{
    int device_fd = -1;
    int property_set_fd = -1;
    int signal_recv_fd = -1;
    int keychord_fd = -1;
    int fd_count;
    int s[2];
    int fd;
    struct sigaction act;
    char tmp[PROP_VALUE_MAX];
    struct pollfd ufds[4];
    char *tmpdev;
    char* debuggable;

    //设置子进程退出的信号处理函数,该函数为sigchld_handler
    act.sa_handler = sigchld_handler;
    act.sa_flags = SA_NOCLDSTOP;
    act.sa_mask = 0;
    act.sa_restorer = NULL;
    sigaction(SIGCHLD, &act, 0);

    //umask设置用户创建文件的默认权限
    umask(0);

    //创建一些文件夹并挂载设备
    mkdir("/dev", 0755);
    mkdir("/proc", 0755);
    mkdir("/sys", 0755);
    mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755");
    mkdir("/dev/pts", 0755);
    mkdir("/dev/socket", 0755);
    mount("devpts", "/dev/pts", "devpts", 0, NULL);
    mount("proc", "/proc", "proc", 0, NULL);
    mount("sysfs", "/sys", "sysfs", 0, NULL);

    //重定向标注输入输出到/dev/_null_
    open_devnull_stdio();

    //设置日志的输出设备为/dev/__ksmg__
    log_init();

    //解析init.rc配置文件
    INFO("reading config file\n");
    parse_config_file("/init.rc");

    //qemu可以虚拟主机实现对系统的引导
    qemu_init();

    //读取机器的Hardware名
    get_hardware_name();
    snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);

    //解析和这个机器相关的配置文件,手机对应的配置文件为init.bravo.rc
    parse_config_file(tmp);

    //执行配置文件中的action动作
    action_for_each_trigger("early-init", action_add_queue_tail);
    drain_action_queue();

    //创建利用Uevent与Linux内核交互的socket
    INFO("device init\n");
    device_fd = device_init();

    //初始化和属性相关的资源
    property_init();

    //.....more.....

    //从这里init进入一个死循环...
    for(;;) {
     int nr, i, timeout = -1;

     for (i = 0; i < fd_count; i++)
         ufds[i].revents = 0;

     drain_action_queue();
     restart_processes();

     if (process_needs_restart) {
         timeout = (process_needs_restart - gettime()) * 1000;
         if (timeout < 0)
             timeout = 0;
     }

}

结合上面分析,可以将init的过程重点总结为以下四点:

  1. 解析两个配置文件(init.rc和init.bravo.rc)
  2. 执行各个阶段的动作,创建zygote的工作就是在某个阶段完成的。
  3. 调用property_init初始化属性相关资源。
  4. init进入无限循环。

解析init.rc

在init中会解析init.rc和init.bravo.rc两个配置文件,其中bravo是平台名称,这两个配置文件都是通过调用parse_config_file函数来解析的,解析函数如下:

int parse_config_file(const char *fn)
{
    char *data;
    data = read_file(fn, 0);
    if (!data) return -1;

    parse_config(fn, data);
    DUMP();
    return 0;
}

可以看到parse_config_file方法实际上只是一个读取文件的方法,真正的解析是在parse_config函数中:

static void parse_config(const char *fn, char *s)
{
    struct parse_state state;
    char *args[SVC_MAXARGS];
    int nargs;

    nargs = 0;
    state.filename = fn;
    state.line = 1;
    state.ptr = s;
    state.nexttoken = 0;
    //设置解析函数,不同的内容用不同的函数
    state.parse_line = parse_line_no_op;
    for (;;) {
        switch (next_token(&state)) {
        case T_EOF:
            state.parse_line(&state, 0, 0);
            return;
        case T_NEWLINE:
            if (nargs) {
                //得到关键字的类型
                int kw = lookup_keyword(args[0]);
                //判断关键字是不是SECTION
                if (kw_is(kw, SECTION)) {
                    state.parse_line(&state, 0, 0);
                    //解析这个SECTION
                    parse_new_section(&state, kw, nargs, args);
                } else {
                    state.parse_line(&state, nargs, args);
                }
                nargs = 0;
            }
            break;
        case T_TEXT:
            if (nargs < SVC_MAXARGS) {
                args[nargs++] = state.text;
            }
            break;
        }
    }
}

上面的next_token方法会拆解配置文件,parse_config首先会找到配置文件的一个section然后针对不同的section使用不同的解析函数来解析。

keywords.h中定义了init过程中使用的关键字。

/system/core/init/keywords.h


#define KEYWORD(symbol, flags, nargs, func) K_##symbol,

//....

KEYWORD(on,          SECTION, 0, 0)
KEYWORD(oneshot,     OPTION,  0, 0)
KEYWORD(onrestart,   OPTION,  0, 0)
KEYWORD(restart,     COMMAND, 1, do_restart)
KEYWORD(service,     SECTION, 0, 0)

//....

可以看到symbol为on或者service的时候标识section。

打开配置文件init.rc

on init //on关键字标示一个section,对应名字是init,直到下一个section开始下面的代码都属于这个section

//.....

on boot

//.....

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    socket zygote stream 666
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media

//.....

一个section的内容从这个标识section的关键字开始到下一个标识section的地方结束。init.rc中定义了boot和init动作执行,另外发现zygote被放在了一个service section中。

理解zygote

zygote本身是一个Native的应用程序,与驱动、内核等无关,上面我们知道zygote是有init进程通过fork而来。

zygote的main函数中的重要功能是通过AppRuntime的start来完成的。

/frameworks/base/core/jni/AndroidRuntime.cpp

void AndroidRuntime::start(const char* className, const bool startSystemServer)
{
    LOGD("\n>>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<\n");

    char* slashClassName = NULL;
    char* cp;
    JNIEnv* env;

    blockSigpipe();

    /*
     * 'startSystemServer == true' means runtime is obslete and not run from
     * init.rc anymore, so we print out the boot start event here.
     */
    if (startSystemServer) {
        /* track our progress through the boot sequence */
        const int LOG_BOOT_PROGRESS_START = 3000;
        LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,
                       ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
    }

    const char* rootDir = getenv("ANDROID_ROOT");
    if (rootDir == NULL) {
        rootDir = "/system";
        if (!hasDir("/system")) {
            LOG_FATAL("No root directory specified, and /android does not exist.");
            goto bail;
        }
        setenv("ANDROID_ROOT", rootDir, 1);
    }

    //const char* kernelHack = getenv("LD_ASSUME_KERNEL");
    //LOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack);

    /* [1]创建虚拟机 */
    if (startVm(&mJavaVM, &env) != 0)
        goto bail;

    /*
     * [2]注册JNI函数
     */
    if (startReg(env) < 0) {
        LOGE("Unable to register all android natives\n");
        goto bail;
    }

    /*
     * We want to call main() with a String array with arguments in it.
     * At present we only have one argument, the class name.  Create an
     * array to hold it.
     */
    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;
    jstring startSystemServerStr;

    stringClass = env->FindClass("java/lang/String");
    assert(stringClass != NULL);
    strArray = env->NewObjectArray(2, stringClass, NULL);
    assert(strArray != NULL);
    classNameStr = env->NewStringUTF(className);
    assert(classNameStr != NULL);
    env->SetObjectArrayElement(strArray, 0, classNameStr);
    startSystemServerStr = env->NewStringUTF(startSystemServer ?
                                                 "true" : "false");
    env->SetObjectArrayElement(strArray, 1, startSystemServerStr);

    /*
     * Start VM.  This thread becomes the main thread of the VM, and will
     * not return until the VM exits.
     */
    jclass startClass;
    jmethodID startMeth;

    slashClassName = strdup(className);
    for (cp = slashClassName; *cp != '\0'; cp++)
        if (*cp == '.')
            *cp = '/';

    startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        LOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
         //[3]通过JNI调用Java函数,这里调用的main函数
        startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            LOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }

    LOGD("Shutting down VM\n");
    if (mJavaVM->DetachCurrentThread() != JNI_OK)
        LOGW("Warning: unable to detach main thread\n");
    if (mJavaVM->DestroyJavaVM() != 0)
        LOGW("Warning: VM did not shut down cleanly\n");

bail:
    free(slashClassName);
}

通过上面代码,我们找到了三个关键点([1][2][3])

  1. 创建虚拟机
  2. 注册JNI函数
  3. 通过JNI调用Java函数,这里调用的main函数

它们共同组成了开创 Android 系统中Java世界的三部曲。zygote 是Android系统中Java世界的盘古,它创建了第一个Java虚拟机并创建了framework的核心system_service 进程。

在Java世界的入口里面,最终CallStaticVoidMethod调用了com.android.internal.os.ZygoteInit.java的main函数。至此进入了Java的世界。

public static void main(String argv[]) {
    try {
        // Start profiling the zygote initialization.
        SamplingProfilerIntegration.start();

        //注册zygote使用的socket
        registerZygoteSocket();
        EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
            SystemClock.uptimeMillis());

        //预加载java类和资源
        preloadClasses();
        //cacheRegisterMaps();
        preloadResources();
        EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
            SystemClock.uptimeMillis());

        if (SamplingProfilerIntegration.isEnabled()) {
            SamplingProfiler sp = SamplingProfiler.getInstance();
            sp.pause();
            SamplingProfilerIntegration.writeZygoteSnapshot();
            sp.shutDown();
        }

        // 强制执行一次垃圾回收
        gc();

        // If requested, start system server directly from Zygote
        if (argv.length != 2) {
            throw new RuntimeException(argv[0] + USAGE_STRING);
        }

        if (argv[1].equals("true")) {

            //最终进入这里了。。。启动system_service进程
            startSystemServer();
        } else if (!argv[1].equals("false")) {
            throw new RuntimeException(argv[0] + USAGE_STRING);
        }

        Log.i(TAG, "Accepting command socket connections");

        if (ZYGOTE_FORK_MODE) {
            runForkMode();
        } else {
            runSelectLoopMode();
        }

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

上面大致完成了下面几件事情:

  1. 在ZygoteInit的main函数中,建立了IPC通信服务端(registerZygoteSocket),zygote及其系统中其他程序的通信没有使用Binder而是采用了基于AF_UNIX类型的socket.
  2. 预加载类和资源
  3. 启动了system_service
  4. zygote进入runSelectLoopMode

SystemService的进程名叫system_service,它是Zygote创建的第一个进程,SystemService调用zygoteInitNative后与Binder通讯系统建立联系。

整个过程总结
[1] ZygoteInit调用startSystemService创建system_service进程
[2] system_service调用handleSystemServiceProcess完成自己的使命
[3] handleSystemServiceProcess抛出异常,最终调用com.android.service.SystemService的main函数
[4] main函数加载libandroid_service.so并调用native的init1函数
[5] init1函数通过JNI调用com.android.service.SystemService类的init2函数,init2函数创建一个线程,用于加载各种service
[6] init1函数最终加入Binder通信系统

关于各个进程之间通信方式关系图如下,在后面继续看相关通信过程:

Android系统启动过程