Android系統(tǒng)啟動系列----init進程

引言

在開發(fā)app的過程中,是不是會有疑問:

  • java程序的運行不是從main方法開始么,怎么app入口是Application的onCreate方法?
  • 那java的運行環(huán)境虛擬機Dalvik VM和ART又是什么時候創(chuàng)建的?又是由誰創(chuàng)建的?
  • 安卓是Linux內(nèi)核,那內(nèi)核創(chuàng)建后系統(tǒng)又做了什么初始化了整個安卓環(huán)境?
  • 當我們的手機或者安卓系統(tǒng)設(shè)備按下電源按鍵的時候,系統(tǒng)都做什么?

當按下電源的那一刻都發(fā)生了啥:


Android系統(tǒng)啟動流程

今天的分析都是基于Android 6.0系統(tǒng)的分析。


第一步:啟動電源

當電源按下,引導(dǎo)芯片代碼開始從預(yù)定義的地方(固化在ROM)開始執(zhí)行。加載引導(dǎo)程序到RAM,然后執(zhí)行。

第二步:執(zhí)行引導(dǎo)程序(Boot Loader)

通常在運行Android系統(tǒng)之前會先執(zhí)行Boot Loader引導(dǎo)程序,它不屬于Android系統(tǒng),常見的引導(dǎo)程序有:redboot、uboot、qi bootloader等等?;蛘咦孕虚_發(fā)引導(dǎo)程序,它是針對特定主板和芯片的,OEM制造廠商或者運營商在加鎖的時候就對這個引導(dǎo)程序做修改,比如魅族就是修改了引導(dǎo)程序,所以刷不了機。

第三步:內(nèi)核

Android內(nèi)核與桌面linux內(nèi)核啟動的方式差不多。內(nèi)核啟動時,設(shè)置緩存、被保護存儲器、計劃列表,加載驅(qū)動。當內(nèi)核完成系統(tǒng)設(shè)置,它首先在系統(tǒng)文件中尋找”init”文件,然后啟動root進程或者系統(tǒng)的第一個進程。

第四步:執(zhí)行init進程

init進程是Android系統(tǒng)啟動的第一個用戶空間進程,init進程主要做兩個事情。第一:掛載目錄,如:掛載了/sys /dev /proc 等目錄。第二:解析執(zhí)行init.rc腳本文件。

系統(tǒng)編譯,刷入手機后,init的進程保存在/system/core/bin目錄中,對應(yīng)程序的源代碼入口是/system/core/init/init.cpp。

int main(int argc, char** argv) {
  if (!is_first_stage) {
      // Indicate that booting is in progress to background fw loaders, etc.
      close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
      
      // 初始化屬性服務(wù)
      property_init();

      // If arguments are passed both on the command line and in DT,
      // properties set in DT always have priority over the command-line ones.
      process_kernel_dt();
      process_kernel_cmdline();

      // Propogate the kernel variables to internal variables
      // used by init as well as the current required properties.
      export_kernel_boot_props();
  }
  
  ........
  
  //  開始屬性服務(wù)
  start_property_service();
  
  // 初始化“init.rc”配置文件解析器
  init_parse_config_file("/init.rc");

  action_for_each_trigger("early-init", action_add_queue_tail);
  
  ........
}

主要看init.rc腳本文件的解析,在說解析前,先來了解下配置腳本的內(nèi)容,這是一個內(nèi)建的腳本語言也叫Android初始化語言,有自己的語法結(jié)構(gòu),大概介紹下:
Android初始化語言由四大類型的聲明組成,即Actions(動作)、Commands(命令)、Services(服務(wù))、以及Options(選項)。
Action(動作):動作是以命令流程命名的,有一個觸發(fā)器決定動作是否發(fā)生。

on early-init
    # Set init and its forked children's oom_adj.
    write /proc/1/oom_score_adj -1000

    # Set the security context of /adb_keys if present.
    restorecon /adb_keys

    start ueventd

on init
    sysclktz 0

    # Backward compatibility.
    symlink /system/etc /etc
    symlink /sys/kernel/debug /d

    # Link /vendor to /system/vendor for devices without a vendor partition.
    symlink /system/vendor /vendor

    # Create cgroup mount point for cpu accounting
    mkdir /acct
    mount cgroup none /acct cpuacct
    mkdir /acct/uid

以上腳本中,on early-init、on init就是 Action類型的語句,語法格式為:

on <trigger> [&& <trigger>]*     //設(shè)置觸發(fā)器  
   <command>  
   <command>      //動作觸發(fā)之后要執(zhí)行的命令

Service(服務(wù)):服務(wù)是init進程啟動的程序、當服務(wù)退出時init進程會視情況重啟服務(wù),語法格式為:

 service <name> <pathname> [ <argument> ]*   //<service的名字><執(zhí)行程序路徑><傳遞參數(shù)>  
   <option>       //option是service的修飾詞,影響什么時候、如何啟動services  
   <option>  
   ... 

下面是默認的init.rc文件,主要的事件及其服務(wù)。

Action/Service 描述
on early-init 設(shè)置init進程以及它創(chuàng)建的子進程的優(yōu)先級,設(shè)置init進程的安全環(huán)境
on init 設(shè)置全局環(huán)境,為cpu accounting創(chuàng)建cgroup(資源控制)掛載點
on fs 掛載mtd分區(qū)
on post-fs 改變系統(tǒng)目錄的訪問權(quán)限
on post-fs-data 改變/data目錄以及它的子目錄的訪問權(quán)限
on-boot 基本網(wǎng)絡(luò)的初始化,內(nèi)存管理等等
service servicemanager 啟動系統(tǒng)管理器管理所有的本地服務(wù),比如位置、音頻、Shared preference等等…
service zygote 啟動zygote進程

通常在這個階段,我們可以在屏幕上看到“Android logo”字樣或者圖標。

我們重點來看看zygote進程相關(guān)的屬性配置,它是獨立的一個rc文件在/system/core/rootdir/init.zygote32.rc

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd
    writepid /dev/cpuset/foreground/tasks

執(zhí)行zygote程序,其實是通過執(zhí)行app_process程序,然后傳入xzygote等等參數(shù)實現(xiàn)的。先找到app_process程序的源碼所在地:/frameworks/base/cmds/app_process/app_main.cpp
直接看程序的main函數(shù):

int main(int argc, char* const argv[])
{
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
        // Older kernels don't understand PR_SET_NO_NEW_PRIVS and return
        // EINVAL. Don't die on such kernels.
        if (errno != EINVAL) {
            LOG_ALWAYS_FATAL("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno));
            return 12;
        }
    }

    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    // Process command line arguments
    // ignore argv[0]
    argc--;
    argv++;

    int i;
    for (i = 0; i < argc; i++) {
        if (argv[i][0] != '-') {
            break;
        }
        if (argv[i][1] == '-' && argv[i][2] == 0) {
            ++i; // Skip --.
            break;
        }
        runtime.addOption(strdup(argv[i]));
    }

    // Parse runtime arguments.  Stop at first unrecognized option.
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    String8 className;

    ++i;  // Skip unused "parent dir" argument.
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            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;
        }
    }

    Vector<String8> args;
    if (!className.isEmpty()) {
        args.add(application ? String8("application") : String8("tool"));
        runtime.setClassNameAndArgs(className, argc - i, argv + i);
    } else {
        // We're in zygote mode.
        maybeCreateDalvikCache();

        if (startSystemServer) {
            args.add(String8("start-system-server"));
        }

        char prop[PROP_VALUE_MAX];
        if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) {
            LOG_ALWAYS_FATAL("app_process: Unable to determine ABI list from property %s.",
                ABI_LIST_PROPERTY);
            return 11;
        }

        String8 abiFlag("--abi-list=");
        abiFlag.append(prop);
        args.add(abiFlag);

        // In zygote mode, pass all remaining arguments to the zygote
        // main() method.
        for (; i < argc; ++i) {
            args.add(String8(argv[i]));
        }
    }

    if (!niceName.isEmpty()) {
        runtime.setArgv0(niceName.string());
        set_process_name(niceName.string());
    }

    // 如果參數(shù)是--zygote,那么runtime.start執(zhí)行zygote進程
    if (zygote) {
        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.");
        return 10;
    }
}

上面就是通過app_process進程,啟動zygote進程的入口,執(zhí)行啟動zygote的程序的在java層的/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java,接下來我們看看:runtime.start("com.android.internal.os.ZygoteInit", args, zygote)都干了啥。
runtime是AppRuntime類的對象,start函數(shù)在其父類AndroidRuntime中聲明和實現(xiàn)。AndroidRuntime是不是很熟悉了,Android的運行時,通常app異常的時候這玩意是不是總伴隨你左右。原來這玩意這么早就啟動在監(jiān)控系統(tǒng)的一舉一動了。

/*
 * Start the Android runtime.  This involves starting the virtual machine
 * and calling the "static void main(String[] args)" method in the class
 * named by "className".
 *
 * Passes the main function two arguments, the class name and the specified
 * options string.
 */
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{

    ......
    
    /* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote) != 0) {  // 1
        return;
    }
    onVmCreated(env);   // 2

    /*
     * Register android functions.
     */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

    /*
     * We want to call main() with a String array with arguments in it.
     * At present we have two arguments, the class name and an option string.
     * Create an array to hold them.
     */
    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    stringClass = env->FindClass("java/lang/String");
    assert(stringClass != NULL);
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
    assert(strArray != NULL);
    classNameStr = env->NewStringUTF(className);
    assert(classNameStr != NULL);
    env->SetObjectArrayElement(strArray, 0, classNameStr); 

    for (size_t i = 0; i < options.size(); ++i) {
        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
        assert(optionsStr != NULL);
        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
    }

    /*
     * Start VM.  This thread becomes the main thread of the VM, and will
     * not return until the VM exits.
     */
    char* slashClassName = toSlashClassName(className);
    jclass startClass = env->FindClass(slashClassName);  // 3
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");  // 4
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);  // 5

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
    
    ......
}
  1. 注釋1:startVm顧名思義啟動虛擬機,在此啟動java虛擬機,當然這個是運行zygote進程的虛擬機,也就回答了文章最開始引言問題,虛擬機由app_process的AndroidRuntime創(chuàng)建。
  2. 注釋2:虛擬機創(chuàng)建后的回調(diào)處理,主要是創(chuàng)建一些資源。
  3. 注釋3:className就是app_process中傳入的參數(shù)“com.android.internal.os.ZygoteInit”,因為ZygoteInit是java層的,所以需要使用jni來找到ZygoteInit.class,startClass就是ZygoteInit.class
  4. 注釋4:通過jni的GetStaticMethodID函數(shù)獲取到ZygoteInit.java的靜態(tài)main方法的類似于反射的Mehod對象引用。
  5. 注釋5:最后通過JNI的CallStaticVoidMethod函數(shù)類似于java反射的invoke方法,調(diào)用了4中獲取的main方法的Method引用。

如上就是Zygote進程的啟動方式。


總結(jié)

  1. 手機按下電源后,加載引導(dǎo)程序到內(nèi)存中。
  2. 執(zhí)行引導(dǎo)程序
  3. 啟動內(nèi)核,設(shè)置緩存、被保護存儲器、計劃列表,加載驅(qū)動,查找/system/core/bin中init程序文件。
  4. 啟動init程序,掛載/sys /dev /proc等等目錄,加載和解析init.rc腳本。
  5. 在加載init.rc腳本的時候,啟動app_process進程。
  6. 在app_process進程中,根據(jù)init.zygote32.rc腳本配置的參數(shù),啟動zygote進程。
  7. 最終zygote進程的執(zhí)行,即ZygoteInit.java文件main方法的執(zhí)行,是由AndroidRuntime通過JNI的方式調(diào)用main方法執(zhí)行的。在這之前啟動了Dalvik VM或者ART虛擬機。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容