Android 8.1 啓動篇(一) -- 深入研究 init

前言

init進程,它是一個由內核啓動的用戶級進程,當Linux內核啓動之後,運行的第一個進程是init,這個進程是一個守護進程,確切的說,它是Linux系統中用戶控件的第一個進程,所以它的進程號是1。它的生命週期貫穿整個linux 內核運行的始終, linux中所有其它的進程的共同始祖均爲init進程。

開篇

核心源碼

Android 版本 關鍵類 路徑
8.1 init.rc system/core/rootdir/init.rc
8.1 init.cpp system/core/init/init.cpp
8.1 property_service.cpp system/core/init/property_service.cpp
8.1 init_parser.h system/core/init/init_parser.h
8.1 init_parser.cpp system/core/init/init_parser.cpp
8.1 log.cpp system/core/init/log.cpp
8.1 logging.cpp system/core/base/logging.cpp
8.1 property_service.cpp system/core/init/property_service.cpp
8.1 signal_handler.cpp system/core/init/signal_handler.cpp
8.1 service.cpp system/core/init/service.cpp
8.1 Action.cpp system/core/init/Action.cpp
8.1 builtins.cpp system/core/init/builtins.cpp

Android系統啓動過程

1. 按下電源系統啓動
    當電源按下時引導芯片代碼開始從預定義的地方(固化在ROM)開始執行,加載引導程序Bootloader到RAM,然後執行。
2. 引導程序Bootloader
    引導程序是在Android操作系統開始運行前的一個小程序,它的主要作用是把系統OS拉起來並運行。
3. linux內核啓動
    內核啓動時,設置緩存、被保護存儲器、計劃列表,加載驅動。當內核完成系統設置,它首先在系統文件中尋找”init”文件,然後啓動root進程或者系統的第一個進程。
4. init進程啓動
    ✨ 這就是我們接下來要討論的內容 ✨

Read The Fucking Code

Android init進程的入口文件在system/core/init/init.cpp中,由於init是命令行程序,所以分析init.cpp首先應從main函數開始:

第一階段(內核態)

判斷及增加環境變量

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    //根據參數,判斷是否需要啓動ueventd和watchdogd
    if (!strcmp(basename(argv[0]), "ueventd")) {                                         // 啓動ueventd
        return ueventd_main(argc, argv);
    }

    if (!strcmp(basename(argv[0]), "watchdogd")) {                                       // 啓動watchdogd
        return watchdogd_main(argc, argv);
    }

    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();                                                   // 若緊急重啓,則安裝對應的消息處理器
    }

    add_environment("PATH", _PATH_DEFPATH);                                              // 添加環境變量
    ... ...
}

創建並掛載相關的文件系統

int main(int argc, char** argv) {
    /* 01. 判斷及增加環境變量 */
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);                      

    if (is_first_stage) {                                                                // 判斷是否是系統啓動的第一階段(第一次進入:true)    
        boot_clock::time_point start_time = boot_clock::now();                           // 用於記錄啓動時間
        
        // Clear the umask.
        umask(0);                                                                        // 清除屏蔽字(file mode creation mask),保證新建的目錄的訪問權限不受屏蔽字影響

        // Get the basic filesystem setup we need put together in the initramdisk
        // on / and then we'll let the rc file figure out the rest.
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");                         // 掛載tmpfs文件系統 
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        mount("devpts", "/dev/pts", "devpts", 0, NULL);                                  // 掛載devpts文件系統 
        #define MAKE_STR(x) __STRING(x)
        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));      // 掛載proc文件系統
		
        // Don't expose the raw commandline to unprivileged processes.
        chmod("/proc/cmdline", 0440);                                                    // 8.0新增, 收緊了cmdline目錄的權限

        gid_t groups[] = { AID_READPROC };                                               // 8.0新增,增加了個用戶組
        setgroups(arraysize(groups), groups);		

        mount("sysfs", "/sys", "sysfs", 0, NULL);                                        // 掛載sysfs文件系統 
		
        mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);                     // 8.0新增

        mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));                              // 提前創建了kmsg設備節點文件,用於輸出log信息
        mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
        mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
        
        ... ...
    }

如上所示,該部分主要用於創建和掛載啓動所需的文件目錄。需要注意的是,在編譯Android系統源碼時,在生成的根文件系統中, 並不存在這些目錄,它們是系統運行時的目錄,即當系統終止時,就會消失。

四類文件系統:

tmpfs:一種虛擬內存文件系統,它會將所有的文件存儲在虛擬內存中,如果你將tmpfs文件系統卸載後,那麼其下的所有的內容將不復存在。tmpfs既可以使用RAM,也可以使用交換分區,會根據你的實際需要而改變大小。tmpfs的速度非常驚人,畢竟它是駐留在RAM中的,即使用了交換分區,性能仍然非常卓越。由於tmpfs是駐留在RAM的,因此它的內容是不持久的。斷電後,tmpfs的內容就消失了,這也是被稱作tmpfs的根本原因。

devpts:爲僞終端提供了一個標準接口,它的標準掛接點是/dev/ pts。只要pty的主複合設備/dev/ptmx被打開,就會在/dev/pts下動態的創建一個新的pty設備文件。

proc:一個非常重要的虛擬文件系統,它可以看作是內核內部數據結構的接口,通過它我們可以獲得系統的信息,同時也能夠在運行時修改特定的內核參數。

sysfs:與proc文件系統類似,也是一個不佔有任何磁盤空間的虛擬文件系統。它通常被掛接在/sys目錄下。sysfs文件系統是Linux2.6內核引入的,它把連接在系統上的設備和總線組織成爲一個分級的文件,使得它們可以在用戶空間存取。

重定向輸入輸出/內核Log系統

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建並掛載相關的文件系統 */
    if (is_first_stage) {
        ... ...
        // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
        // talk to the outside world...
        InitKernelLogging(argv);
        ... ...
    }
    ... ...

屏蔽標準的輸入輸出

跟蹤InitKernelLogging(): system/core/init/log.cpp

void InitKernelLogging(char* argv[]) {
    // Make stdin/stdout/stderr all point to /dev/null.
    int fd = open("/sys/fs/selinux/null", O_RDWR);
    if (fd == -1) {                                                  // 若開啓失敗,則記錄log
        int saved_errno = errno;
        android::base::InitLogging(argv, &android::base::KernelLogger);
        errno = saved_errno;
        PLOG(FATAL) << "Couldn't open /sys/fs/selinux/null";             
    }
    dup2(fd, 0);                                                     // dup2函數的作用是用來複制一個文件的描述符, 通常用來重定向進程的stdin、stdout和stderr
    dup2(fd, 1);                                                     // 它的函數原形是:int dup2(int oldfd, int targetfd),該函數執行後,targetfd將變成oldfd的複製品
    dup2(fd, 2);                                                     // 因此這邊的過程其實就是:創建出__null__設備後,將0、1、2綁定到__null__設備上
    if (fd > 2) close(fd);                                           // 所以init進程調用InitKernelLogging函數後,通過標準的輸入輸出無法輸出信息

    android::base::InitLogging(argv, &android::base::KernelLogger);
}

設置kernel logger

跟蹤InitLogging():system/core/base/logging.cpp

// 設置KernelLogger
void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter) {
  //設置logger
  SetLogger(std::forward<LogFunction>(logger));
  SetAborter(std::forward<AbortFunction>(aborter));

  if (gInitialized) {
    return;
  }

  gInitialized = true;

  // Stash the command line for later use. We can use /proc/self/cmdline on
  // Linux to recover this, but we don't have that luxury on the Mac/Windows,
  // and there are a couple of argv[0] variants that are commonly used.
  if (argv != nullptr) {
    std::lock_guard<std::mutex> lock(LoggingLock());
    ProgramInvocationName() = basename(argv[0]);
  }

  const char* tags = getenv("ANDROID_LOG_TAGS");
  if (tags == nullptr) {
    return;
  }

  // 根據TAG決定最小記錄等級
  std::vector<std::string> specs = Split(tags, " ");
  for (size_t i = 0; i < specs.size(); ++i) {
    // "tag-pattern:[vdiwefs]"
    std::string spec(specs[i]);
    if (spec.size() == 3 && StartsWith(spec, "*:")) {
      switch (spec[2]) {
        case 'v':
          gMinimumLogSeverity = VERBOSE;
          continue;
        ... ...
      }
    }
    LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags
               << ")";
  }
}

當需要輸出日誌時,KernelLogger函數就會被調用:

#if defined(__linux__)
void KernelLogger(android::base::LogId, android::base::LogSeverity severity,
                  const char* tag, const char*, unsigned int, const char* msg) {
  ... ...

  // 打開log節點
  static int klog_fd = TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC));
  if (klog_fd == -1) return;

  // 決定log等級
  int level = kLogSeverityToKernelLogLevel[severity];

  // The kernel's printk buffer is only 1024 bytes.
  // TODO: should we automatically break up long lines into multiple lines?
  // Or we could log but with something like "..." at the end?
  char buf[1024];
  size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %s\n", level, tag, msg);
  if (size > sizeof(buf)) {
    size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printk\n",
                    level, tag, size);
  }

  iovec iov[1];
  iov[0].iov_base = buf;
  iov[0].iov_len = size;
  // 通過iovec將log發送到dev/kmsg
  TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1));
}
#endif

掛在一些分區設備

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建文件系統目錄並掛載相關的文件系統 */
    /* 03. 重定向輸入輸出/內核Log系統 */
    if (is_first_stage) {                                                                
        ... ...
        // 掛載特定的分區設備
        if (!DoFirstStageMount()) {
            LOG(ERROR) << "Failed to mount required partitions early ...";
            panic();        // panic會嘗試reboot
        }
    }
    ... ...

跟蹤DoFirstStageMount():system/core/init/init_first_stage.cpp

// Mounts partitions specified by fstab in device tree.
bool DoFirstStageMount() {
    // Skips first stage mount if we're in recovery mode.
    if (IsRecoveryMode()) {
        LOG(INFO) << "First stage mount skipped (recovery mode)";
        return true;
    }

    // Firstly checks if device tree fstab entries are compatible.
    if (!is_android_dt_value_expected("fstab/compatible", "android,fstab")) {
        LOG(INFO) << "First stage mount skipped (missing/incompatible fstab in device tree)";
        return true;
    }

    // 滿足上述條件時,就會調用FirstStageMount的DoFirstStageMount函數
    std::unique_ptr<FirstStageMount> handle = FirstStageMount::Create();
    if (!handle) {
        LOG(ERROR) << "Failed to create FirstStageMount";
        return false;
    }
	
    // 主要是初始化特定設備並掛載
    return handle->DoFirstStageMount();
}

完成SELinux相關工作

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建文件系統目錄並掛載相關的文件系統 */
    /* 03. 重定向輸入輸出/內核Log系統 */
    /* 04. 掛在一些分區設備 */
    if (is_first_stage) {                                                                
        ... ...

        // 此處應該是初始化安全框架:Android Verified Boot
        // AVB主要用於防止系統文件本身被篡改,還包含了防止系統回滾的功能,
        // 以免有人試圖回滾系統並利用以前的漏洞
        SetInitAvbVersionInRecovery();

        // Set up SELinux, loading the SELinux policy.                                   
        selinux_initialize(true);                                                        // 調用selinux_initialize啓動SELinux
        ... ...
    }
    ... ...

跟蹤selinux_initialize():

static void selinux_initialize(bool in_kernel_domain) {
    Timer t;

    selinux_callback cb;
    cb.func_log = selinux_klog_callback;               // 用於打印log的回調函數
    selinux_set_callback(SELINUX_CB_LOG, cb);
    cb.func_audit = audit_callback;                    // 用於檢查權限的回調函數
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    // init進程的運行是區分用戶態和內核態的,first_stage運行在內核態
    if (in_kernel_domain) {
        LOG(INFO) << "Loading SELinux policy";
        // 用於加載sepolicy文件
        // 該函數最終將sepolicy文件傳遞給kernel,這樣kernel就有了安全策略配置文件,後續的MAC才能開展起來。
        if (!selinux_load_policy()) {
            panic();
        }

        bool kernel_enforcing = (security_getenforce() == 1);         // 內核中讀取的信息
        bool is_enforcing = selinux_is_enforcing();                   // 命令行中得到的數據
        // 用於設置selinux的工作模式。selinux有兩種工作模式:
        // 1、”permissive”,所有的操作都被允許(即沒有MAC),但是如果違反權限的話,會記錄日誌
        // 2、”enforcing”,所有操作都會進行權限檢查。在一般的終端中,應該工作於enforing模式
        if (kernel_enforcing != is_enforcing) {
            if (security_setenforce(is_enforcing)) {
                PLOG(ERROR) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
                security_failure();                                   // 將重啓進入recovery mode
            }
        }

        std::string err;
        if (!WriteFile("/sys/fs/selinux/checkreqprot", "0", &err)) {
            LOG(ERROR) << err;
            security_failure();
        }

        // init's first stage can't set properties, so pass the time to the second stage.
        setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
    } else {
        selinux_init_all_handles();                                   // 在second stage調用時,初始化所有的handle
    }
}

is_first_stage 收尾

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建文件系統目錄並掛載相關的文件系統 */
    /* 03. 重定向輸入輸出/內核Log系統 */
    /* 04. 掛在一些分區設備 */
    /* 05. 完成SELinux相關工作 */
    if (is_first_stage) {                                                                
        ... ...

        // We're in the kernel domain, so re-exec init to transition to the init domain now
        // that the SELinux policy has been loaded.
        if (selinux_android_restorecon("/init", 0) == -1) {                              // 按selinux policy要求,重新設置init文件屬性
            PLOG(ERROR) << "restorecon failed";
            security_failure();                                                          // 失敗的話會reboot
        }
		
        static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
        uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
        setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);                  // 記錄初始化時的時間

        char* path = argv[0];
        char* args[] = { path, nullptr };
        execv(path, args);                                                               // 再次調用init的main函數,啓動用戶態的init進程

        // execv() only returns if an error happened, in which case we
        // panic and never fall through this conditional.
        PLOG(ERROR) << "execv(\"" << path << "\") failed";
        security_failure();                                                              // 內核態的進程不應該退出,若退出則會重啓
    }
    /* 06. is_first_stage 收尾 */
    /* ------------ 第一階段 ------------- END ------------ */
    ... ...
}

上面所有的源碼我們都是圍繞第一階段分析(is_first_stage),自此第一階段結束,會復位一些信息,並設置一些環境變量,最後啓動用戶態的init進程,進入init第二階段。

第二階段(用戶態)

init進程的第二階段仍然從main函數開始入手(繼續分析main函數剩餘源碼)

初始化屬性域

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建文件系統目錄並掛載相關的文件系統 */
    /* 03. 重定向輸入輸出/內核Log系統 */
    /* 04. 掛在一些分區設備 */
    /* 05. 完成SELinux相關工作 */
    /* 06. is_first_stage 收尾 */
    /* ------------ 第一階段 ------------- END ------------ */
	
    /* ------------ 第二階段 ------------ BEGIN------------ */

    // 同樣進行一些判斷及環境變量設置的工作
	
    ... ...

    // 現在 is_first_stage 爲 false 了
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    // 這部分工作不再執行了
    if (is_first_stage) {
        ...........
    }

    // At this point we're in the second stage of init.
    InitKernelLogging(argv);                       // 同樣屏蔽標準輸入輸出及定義Kernel logger
    LOG(INFO) << "init second stage started!";

    // Set up a session keyring that all processes will have access to. It
    // will hold things like FBE encryption keys. No process should override
    // its session keyring.
    keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);      // 最後調用syscall,設置安全相關的值
	
    // Indicate that booting is in progress to background fw loaders, etc.
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));         // 這裏的功能類似於“鎖”
	
    ... ...
    property_init();                               // 初始化屬性域 --> 定義於system/core/init/property_service.cpp
	
    // 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();                      // 處理內核命令

    // Propagate the kernel variables to internal variables
    // used by init as well as the current required properties.
    export_kernel_boot_props();

    // Make the time that init started available for bootstat to log.
    property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
    property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));

    // Set libavb version for Framework-only OTA match in Treble build.
    const char* avb_version = getenv("INIT_AVB_VERSION");
    if (avb_version) property_set("ro.boot.avb_version", avb_version);
	
    ... ...
}

這部分代碼主要的工作應該就是調用 property_init 初始化屬性域,然後設置各種屬性。

在Android平臺中,爲了讓運行中的所有進程共享系統運行時所需要的各種設置值,系統開闢了屬性存儲區域,並提供了訪問該區域的API。

跟蹤property_init():system/core/init/property_service.cpp

void property_init() {
    if (__system_property_area_init()) {                   // 最終調用_system_property_area_init函數初始化屬性域
        LOG(ERROR) << "Failed to initialize property area";
        exit(1);
    }
}

清空環境變量

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建文件系統目錄並掛載相關的文件系統 */
    /* 03. 重定向輸入輸出/內核Log系統 */
    /* 04. 掛在一些分區設備 */
    /* 05. 完成SELinux相關工作 */
    /* 06. is_first_stage 收尾 */
    /* ------------ 第一階段 ------------- END ------------ */
	
    /* ------------ 第二階段 ------------ BEGIN------------ */
    /* 01. 初始化屬性域 */
    // Clean up our environment.
    unsetenv("INIT_SECOND_STAGE");
    unsetenv("INIT_STARTED_AT");                 // 清除掉之前使用過的環境變量
    unsetenv("INIT_SELINUX_TOOK");
    unsetenv("INIT_AVB_VERSION");
    ... ...
}

完成SELinux相關工作

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建文件系統目錄並掛載相關的文件系統 */
    /* 03. 重定向輸入輸出/內核Log系統 */
    /* 04. 掛在一些分區設備 */
    /* 05. 完成SELinux相關工作 */
    /* 06. is_first_stage 收尾 */
    /* ------------ 第一階段 ------------- END ------------ */
	
    /* ------------ 第二階段 ------------ BEGIN------------ */
    /* 01. 初始化屬性域 */
    /* 02. 清空環境變量 */
    // Now set up SELinux for second stage.
    selinux_initialize(false);
    selinux_restore_context();                   // 再次完成selinux相關的工作

我們發現在init進程的第一階段,也調用了selinux_initialize函數,那麼兩者有什麼區別?
init進程第一階段主要加載selinux相關的策略,而第二階段調用selinux_initialize僅僅註冊一些處理器。

我們跟下selinux_initialize():

static void selinux_initialize(bool in_kernel_domain) {
    Timer t;

    selinux_callback cb;
    cb.func_log = selinux_klog_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);
    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    if (in_kernel_domain) {
        ... ...                              // 這邊就是第一階段的工作
    } else {
        selinux_init_all_handles();          // 這邊就是第二階段的工作:註冊處理器
    }
}

再來看一下selinux_restore_context():主要是按 selinux policy 要求,重新設置一些文件的屬性。

// The files and directories that were created before initial sepolicy load or
// files on ramdisk need to have their security context restored to the proper
// value. This must happen before /dev is populated by ueventd.
// 如註釋所述,以下文件在selinux被加載前就創建了
// 於是,在selinux啓動後,需要重新設置一些屬性
static void selinux_restore_context() {
    LOG(INFO) << "Running restorecon...";
    selinux_android_restorecon("/dev", 0);
    selinux_android_restorecon("/dev/kmsg", 0);
    selinux_android_restorecon("/dev/socket", 0);
    selinux_android_restorecon("/dev/random", 0);
    selinux_android_restorecon("/dev/urandom", 0);
    selinux_android_restorecon("/dev/__properties__", 0);

    selinux_android_restorecon("/plat_file_contexts", 0);
    selinux_android_restorecon("/nonplat_file_contexts", 0);
    selinux_android_restorecon("/plat_property_contexts", 0);
    selinux_android_restorecon("/nonplat_property_contexts", 0);
    selinux_android_restorecon("/plat_seapp_contexts", 0);
    selinux_android_restorecon("/nonplat_seapp_contexts", 0);
    selinux_android_restorecon("/plat_service_contexts", 0);
    selinux_android_restorecon("/nonplat_service_contexts", 0);
    selinux_android_restorecon("/plat_hwservice_contexts", 0);
    selinux_android_restorecon("/nonplat_hwservice_contexts", 0);
    selinux_android_restorecon("/sepolicy", 0);
    selinux_android_restorecon("/vndservice_contexts", 0);

    selinux_android_restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
    selinux_android_restorecon("/dev/device-mapper", 0);

    selinux_android_restorecon("/sbin/mke2fs_static", 0);
    selinux_android_restorecon("/sbin/e2fsdroid_static", 0);
}

創建epoll句柄

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建文件系統目錄並掛載相關的文件系統 */
    /* 03. 重定向輸入輸出/內核Log系統 */
    /* 04. 掛在一些分區設備 */
    /* 05. 完成SELinux相關工作 */
    /* 06. is_first_stage 收尾 */
    /* ------------ 第一階段 ------------- END ------------ */
	
    /* ------------ 第二階段 ------------ BEGIN------------ */
    /* 01. 初始化屬性域 */
    /* 02. 清空環境變量 */
    /* 03. 完成SELinux相關工作 */
    epoll_fd = epoll_create1(EPOLL_CLOEXEC);       // 調用epoll_create1創建epoll句柄
    if (epoll_fd == -1) {
        PLOG(ERROR) << "epoll_create1 failed";
        exit(1);
    }
    ... ...
}

在linux的網絡編程中,很長的時間都在使用 select 來做事件觸發。在linux新的內核中,有了一種替換它的機制,就是 epoll。
相比於select,epoll最大的好處在於它不會隨着監聽fd數目的增長而降低效率。因爲在內核中的 select 實現中,它是採用輪詢來處理的,輪詢的fd數目越多,自然耗時越多。

epoll機制一般使用epoll_create(int size)函數創建epoll句柄,size用來告訴內核這個句柄可監聽的fd的數目。
注意這個參數不同於select()中的第一個參數,在select中需給出最大監聽數加1的值。

此外,當創建好epoll句柄後,它就會佔用一個fd值,在linux下如果查看/proc/進程id/fd/,能夠看到創建出的fd,因此在使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡。
上述代碼使用的epoll_create1(EPOLL_CLOEXEC)來創建epoll句柄,該標誌位表示生成的epoll fd具有“執行後關閉”特性。

裝載子進程信號處理器

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建文件系統目錄並掛載相關的文件系統 */
    /* 03. 重定向輸入輸出/內核Log系統 */
    /* 04. 掛在一些分區設備 */
    /* 05. 完成SELinux相關工作 */
    /* 06. is_first_stage 收尾 */
    /* ------------ 第一階段 ------------- END ------------ */
	
    /* ------------ 第二階段 ------------ BEGIN------------ */
    /* 01. 初始化屬性域 */
    /* 02. 清空環境變量 */
    /* 03. 完成SELinux相關工作 */
    /* 04. 創建epoll句柄 */
    signal_handler_init();                         // 裝載子進程信號處理器
}

init是一個守護進程,爲了防止init的子進程成爲殭屍進程(zombie process),需要init在子進程在結束時獲取子進程的結束碼,通過結束碼將程序表中的子進程移除,防止成爲殭屍進程的子進程佔用程序表的空間(程序表的空間達到上限時,系統就不能再啓動新的進程了,會引起嚴重的系統問題)。

在linux當中,父進程是通過捕捉 SIGCHLD 信號來得知子進程運行結束的情況,此處init進程調用 signal_handler_init 的目的就是捕獲子進程結束的信號。

我們跟蹤下signal_handler_init():

void signal_handler_init() {
    // Create a signalling mechanism for SIGCHLD.
    int s[2];
    // 利用socketpair創建出已經連接的兩個socket,分別作爲信號的讀、寫端
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
        PLOG(ERROR) << "socketpair failed";
        exit(1);
    }

    signal_write_fd = s[0];
    signal_read_fd = s[1];

    // Write to signal_write_fd if we catch SIGCHLD.
    struct sigaction act;
    memset(&act, 0, sizeof(act));

    // 信號處理器對應的執行函數爲SIGCHLD_handler
    // 被存在sigaction結構體中,負責處理SIGCHLD消息
    act.sa_handler = SIGCHLD_handler;
    act.sa_flags = SA_NOCLDSTOP;
	
    // 調用信號安裝函數sigaction,將監聽的信號及對應的信號處理器註冊到內核中
    sigaction(SIGCHLD, &act, 0);

    // 用於終止出現問題的子進程
    ServiceManager::GetInstance().ReapAnyOutstandingChildren();

    // 註冊信號處理函數handle_signal
    register_epoll_handler(signal_read_fd, handle_signal);
}

在深入分析代碼前,我們需要了解一些基本概念:Linux進程通過互相發送消息來實現進程間的通信,這些消息被稱爲“信號”。每個進程在處理其它進程發送的信號時都要註冊處理者,處理者被稱爲信號處理器。

注意到sigaction結構體的sa_flags爲SA_NOCLDSTOP。由於系統默認在子進程暫停時也會發送信號SIGCHLD,init需要忽略子進程在暫停時發出的SIGCHLD信號,因此將act.sa_flags 置爲SA_NOCLDSTOP,該標誌位表示僅當進程終止時才接受SIGCHLD信號。

接下來,我們分步驟詳細瞭解一下signal_handler_init具體的工作流程。

SIGCHLD_handler

// system/core/init/signal_handler.cpp
static void SIGCHLD_handler(int) {
    if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
        PLOG(ERROR) << "write(signal_write_fd) failed";
    }
}

從上面代碼我們知道,init進程是所有進程的父進程,當其子進程終止產生SIGCHLD信號時,SIGCHLD_handler將對signal_write_fd執行寫操作。由於socketpair的綁定關係,這將觸發信號對應的signal_read_fd收到數據。

register_epoll_handler

根據前文的代碼我們知道,在裝載信號監聽器的最後,signal_handler_init調用了register_epoll_handler,其代碼如下所示,注意傳入的參數分別爲signal_read_fd和handle_signal:

// system/core/init/init.cpp
void register_epoll_handler(int fd, void (*fn)()) {
    epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.ptr = reinterpret_cast<void*>(fn);
    // epoll_fd增加一個監聽對象fd,fd上有數據到來時,調用fn處理
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        PLOG(ERROR) << "epoll_ctl failed";
    }
}

根據代碼不難看出:當epoll句柄監聽到signal_read_fd中有數據可讀時,將調用handle_signal進行處理。

至此,結合上文我們知道:當init進程調用signal_handler_init後,一旦收到子進程終止帶來的SIGCHLD消息後,將利用信號處理者SIGCHLD_handler向signal_write_fd寫入信息;由於綁定的關係,epoll句柄將監聽到signal_read_fd收到消息,於是將調用handle_signal進行處理。

handle_signal

// system/core/init/signal_handler.cpp
static void handle_signal() {
    // Clear outstanding requests.
    char buf[32];
    read(signal_read_fd, buf, sizeof(buf));

    ServiceManager::GetInstance().ReapAnyOutstandingChildren();
}

從代碼中可以看出,handle_signal只是清空signal_read_fd中的數據,然後調用ServiceManager::GetInstance().ReapAnyOutstandingChildren()。

ServiceManager定義於 system/core/init/service.cpp 中,是一個單例對象:

... ...

ServiceManager::ServiceManager() {
}

ServiceManager& ServiceManager::GetInstance() {
    static ServiceManager instance;
    return instance;
}

... ...

void ServiceManager::ReapAnyOutstandingChildren() {
    while (ReapOneProcess()) {
    }
}

... ...

如上所示,ReapAnyOutstandingChildren函數實際上調用了ReapOneProcess。我們結合代碼,看看ReapOneProcess的具體工作。

bool ServiceManager::ReapOneProcess() {
    siginfo_t siginfo = {};
    // This returns a zombie pid or informs us that there are no zombies left to be reaped.
    // It does NOT reap the pid; that is done below.
    //用waitid函數獲取狀態發生變化的子進程pid
    //waitid的標記爲WNOHANG,即非阻塞,返回爲正值就說明有進程掛掉了
    if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) {
        PLOG(ERROR) << "waitid failed";
        return false;
    }

    auto pid = siginfo.si_pid;
    if (pid == 0) return false;

    // At this point we know we have a zombie pid, so we use this scopeguard to reap the pid
    // whenever the function returns from this point forward.
    // We do NOT want to reap the zombie earlier as in Service::Reap(), we kill(-pid, ...) and we
    // want the pid to remain valid throughout that (and potentially future) usages.
    auto reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); });

    if (PropertyChildReap(pid)) {
        return true;
    }

    // 利用FindServiceByPid函數,找到pid對應的服務
    // FindServiceByPid主要通過輪詢解析init.rc生成的service_list,找到pid與參數一致的srvc
    Service* svc = FindServiceByPid(pid);

    ... ...                             // 輸出服務結束的原因
	
    if (!svc) {                         // 沒有找到,說明已經結束了
        return true;
    }

    svc->Reap();

    // 根據svc的類型,決定後續的處理方式
    if (svc->flags() & SVC_EXEC) {
        exec_waiter_.reset();           // 可執行服務則重置對應的waiter
    }
    if (svc->flags() & SVC_TEMPORARY) {
        RemoveService(*svc);            // 移除臨時服務
    }

    return true;
}

Reap

void Service::Reap() {
    // 清理未攜帶SVC_ONESHOT 或 攜帶了SVC_RESTART標誌的srvc的進程組
    if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
        KillProcessGroup(SIGKILL);
    }

    // Remove any descriptor resources we may have created.
    // 清除srvc中創建出的任意描述符
    std::for_each(descriptors_.begin(), descriptors_.end(),
                  std::bind(&DescriptorInfo::Clean, std::placeholders::_1));

    // 清理工作完畢後,後面決定是否重啓機器或重啓服務
    // TEMP服務不用參與這種判斷
    if (flags_ & SVC_TEMPORARY) {
        return;
    }

    pid_ = 0;
    flags_ &= (~SVC_RUNNING);

    // Oneshot processes go into the disabled state on exit,
    // except when manually restarted.
    // 對於攜帶了SVC_ONESHOT並且未攜帶SVC_RESTART的srvc,將這類服務的標誌置爲SVC_DISABLED,不再自啓動
    if ((flags_ & SVC_ONESHOT) && !(flags_ & SVC_RESTART)) {
        flags_ |= SVC_DISABLED;
    }

    // Disabled and reset processes do not get restarted automatically.
    if (flags_ & (SVC_DISABLED | SVC_RESET))  {
        NotifyStateChange("stopped");
        return;
    }

    // If we crash > 4 times in 4 minutes, reboot into recovery.
    boot_clock::time_point now = boot_clock::now();
	
    // 未攜帶SVC_RESTART的關鍵服務,在規定的間隔內,crash字數過多時,會導致整機重啓;
    if ((flags_ & SVC_CRITICAL) && !(flags_ & SVC_RESTART)) {
        if (now < time_crashed_ + 4min) {
            if (++crash_count_ > 4) {
                LOG(ERROR) << "critical process '" << name_ << "' exited 4 times in 4 minutes";
                panic();
            }
        } else {
            time_crashed_ = now;
            crash_count_ = 1;
        }
    }

    // 將待重啓srvc的標誌位置爲SVC_RESTARTING(init進程將根據該標誌位,重啓服務)
    flags_ &= (~SVC_RESTART);
    flags_ |= SVC_RESTARTING;

    // Execute all onrestart commands for this service.
    // 重啓在init.rc文件中帶有onrestart選項的服務
    onrestart_.ExecuteAllCommands();

    NotifyStateChange("restarting");
    return;
}

不難看出,Reap函數的主要作用就是清除問題進程相關的資源,然後根據進程對應的類型,決定是否重啓機器或重啓進程。

ExecuteAllCommands

我們在這一部分的最後,看看定義於system/core/init/Action.cpp中的ExecuteAllCommands函數:

void Action::ExecuteAllCommands() const {
    for (const auto& c : commands_) {
        ExecuteCommand(c);
    }
}

void Action::ExecuteCommand(const Command& command) const {
    android::base::Timer t;
    // 進程重啓時,將執行對應的函數
    int result = command.InvokeFunc();

    // 打印log
    auto duration = t.duration();
    // Any action longer than 50ms will be warned to user as slow operation
    if (duration > 50ms || android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
        std::string trigger_name = BuildTriggersString();
        std::string cmd_str = command.BuildCommandString();

        LOG(INFO) << "Command '" << cmd_str << "' action=" << trigger_name << " (" << filename_
                  << ":" << command.line() << ") returned " << result << " took "
                  << duration.count() << "ms.";
    }
}

整個signal_handler_init的內容比較多,在此總結一下:signal_handler_init的本質就是監聽子進程死亡的信息,然後進行對應的清理工作,並根據死亡進程的類型,決定是否需要重啓進程或機器。

啓動屬性服務

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建文件系統目錄並掛載相關的文件系統 */
    /* 03. 重定向輸入輸出/內核Log系統 */
    /* 04. 掛在一些分區設備 */
    /* 05. 完成SELinux相關工作 */
    /* 06. is_first_stage 收尾 */
    /* ------------ 第一階段 ------------- END ------------ */
	
    /* ------------ 第二階段 ------------ BEGIN------------ */
    /* 01. 初始化屬性域 */
    /* 02. 清空環境變量 */
    /* 03. 完成SELinux相關工作 */
    /* 04. 創建epoll句柄 */
    /* 05. 裝載子進程信號處理器 */
    property_load_boot_defaults();                 // 進程調用property_load_boot_defaults進行默認屬性配置相關的工作
    export_oem_lock_status();                      // 最終就是決定"ro.boot.flash.locked"的值
    start_property_service();                      // 啓動屬性服務
    set_usb_controller();
    ... ...
}

老樣子,這邊我們跟蹤幾個重要的函數。

property_load_boot_defaults

void property_load_boot_defaults() {
    // 就是從各種路徑讀取默認配置
    // load_properties_from_file的基本操作就是read_file,然後解析並設置
    if (!load_properties_from_file("/system/etc/prop.default", NULL)) {
        // Try recovery path
        if (!load_properties_from_file("/prop.default", NULL)) {
            // Try legacy path
            load_properties_from_file("/default.prop", NULL);
        }
    }
    load_properties_from_file("/odm/default.prop", NULL);
    load_properties_from_file("/vendor/default.prop", NULL);

    update_sys_usb_config();          // 就是設置"persist.sys.usb.config"相關的配置
}

如代碼所示,property_load_boot_defaults 實際上就是調用 load_properties_from_file 解析配置文件,然後根據解析的結果,設置系統屬性。

start_property_service

void start_property_service() {
    property_set("ro.property_service.version", "2");

    // 創建了一個非阻塞socket
    property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                   false, 0666, 0, 0, nullptr, sehandle);
    if (property_set_fd == -1) {
        PLOG(ERROR) << "start_property_service socket creation failed";
        exit(1);
    }

    // 調用listen函數監聽property_set_fd, 於是該socket變成一個server
    listen(property_set_fd, 8);

    // 監聽server socket上是否有數據到來
    register_epoll_handler(property_set_fd, handle_property_set_fd);
}

init進程在共享內存區域中,創建並初始化屬性域。其它進程可以訪問屬性域中的值,但更改屬性值僅能在init進程中進行。這就是init進程調用start_property_service的原因。
其它進程修改屬性值時,要預先向init進程提交值變更申請,然後init進程處理該申請,並修改屬性值。在訪問和修改屬性時,init進程都可以進行權限控制。

匹配命令和函數之間對應關係

    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建文件系統目錄並掛載相關的文件系統 */
    /* 03. 重定向輸入輸出/內核Log系統 */
    /* 04. 掛在一些分區設備 */
    /* 05. 完成SELinux相關工作 */
    /* 06. is_first_stage 收尾 */
    /* ------------ 第一階段 ------------- END ------------ */
	
    /* ------------ 第二階段 ------------ BEGIN------------ */
    /* 01. 初始化屬性域 */
    /* 02. 清空環境變量 */
    /* 03. 完成SELinux相關工作 */
    /* 04. 創建epoll句柄 */
    /* 05. 裝載子進程信號處理器 */
    /* 06. 啓動屬性服務*/
    const BuiltinFunctionMap function_map;         // system/core/init/builtins.cpp,定義Action中的function_map_爲BuiltinFuntionMap
    Action::set_function_map(&function_map);       // 在Action中保存function_map對象,記錄了命令與函數之間的對應關係
    /* 07. 匹配命令和函數之間對應關係 */
    /* ------------ 第二階段 ------------ END ------------ */
    ... ...
}

至此,init進程的準備工作執行完畢, 接下來就要開始解析init.rc文件了。

第三階段(init.rc)

解析init.rc

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建文件系統目錄並掛載相關的文件系統 */
    /* 03. 重定向輸入輸出/內核Log系統 */
    /* 04. 掛在一些分區設備 */
    /* 05. 完成SELinux相關工作 */
    /* 06. is_first_stage 收尾 */
    /* ------------ 第一階段 ------------- END ------------ */
	
    /* ------------ 第二階段 ------------ BEGIN------------ */
    /* 01. 初始化屬性域 */
    /* 02. 清空環境變量 */
    /* 03. 完成SELinux相關工作 */
    /* 04. 創建epoll句柄 */
    /* 05. 裝載子進程信號處理器 */
    /* 06. 啓動屬性服務*/
    /* 07. 匹配命令和函數之間對應關係 */
    /* ------------ 第二階段 ------------ END ------------ */
	
    /* ------------ 第三階段 ----------- BEGIN------------ */
    ActionManager& am = ActionManager::GetInstance();
    ServiceManager& sm = ServiceManager::GetInstance();
    Parser& parser = Parser::GetInstance();                                          // 構造解析文件用的parser對象

    // 爲一些類型的關鍵字,創建特定的parser
    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm));        // 增加ServiceParser爲一個section,對應name爲service
    parser.AddSectionParser("on", std::make_unique<ActionParser>(&am));              // 增加ActionParser爲一個section,對應name爲action
    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));      // 增加ActionParser爲一個section,對應name爲import
	
    std::string bootscript = GetProperty("ro.boot.init_rc", "");                     // 判斷是否存在bootscript

    // 如果沒有bootscript,則解析init.rc文件
    if (bootscript.empty()) {                                                        // 8.0引入
        parser.ParseConfig("/init.rc");                                              // 開始實際的解析過程
        parser.set_is_system_etc_init_loaded(
                parser.ParseConfig("/system/etc/init"));
        parser.set_is_vendor_etc_init_loaded(
                parser.ParseConfig("/vendor/etc/init"));
        parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
    } else {
        // 若存在bootscript, 則解析bootscript
        parser.ParseConfig(bootscript);
        parser.set_is_system_etc_init_loaded(true);
        parser.set_is_vendor_etc_init_loaded(true);
        parser.set_is_odm_etc_init_loaded(true);
    }
    ... ...
}

如果沒有定義bootScript,那麼init進程還是會解析init.rc文件。init.rc文件是在init進程啓動後執行的啓動腳本,文件中記錄着init進程需執行的操作。
此處解析函數傳入的參數爲“/init.rc”,解析的是運行時與init進程同在根目錄下的init.rc文件。該文件在編譯前,定義於system/core/rootdir/init.rc中。

✨ 繼續往下分析main函數之前;
✨ 我們先了解一下init.rc是什麼,然後分析下parser解析init.rc過程;
✨ 最後我們再繼續跟源碼!

init.rc配置文件

init.rc是一個配置文件,內部由Android初始化語言編寫(Android Init Language)編寫的腳本,主要包含五種類型語句:Action、Command、Service、Option 和 Import,在分析代碼的過程中我們會詳細介紹。

init.rc的配置代碼在:system/core/rootdir/init.rc 中。

init.rc文件是在init進程啓動後執行的啓動腳本,文件中記錄着init進程需執行的操作。

init.rc文件大致分爲兩大部分:

一部分是以“on”關鍵字開頭的 動作列表(action list):

on early-init                    // Action類型語句
    # Set init and its forked children's oom_adj.     // #:註釋符號
    write /proc/1/oom_score_adj -1000
    ... ...
    # Shouldn't be necessary, but sdcard won't start without it. http://b/22568628.
    mkdir /mnt 0775 root system
    ... ...
    start ueventd

Action類型語句格式:

on <trigger> [&& <trigger>]*     // 設置觸發器  
   <command>  
   <command>                     // 動作觸發之後要執行的命令

另一部分是以“service”關鍵字開頭的 服務列表(service list): 如 Zygote

service ueventd /sbin/ueventd    // Service類型語句
    class core
    critical
    seclabel u:r:ueventd:s0

Service類型語句格式:

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

藉助系統環境變量或Linux命令,
            🏹 動作列表用於創建所需目錄,以及爲某些特定文件指定權限
            🏹 服務列表用來記錄init進程需要啓動的一些子進程,如上面代碼所示,service關鍵字後的第一個字符串表示服務(子進程)的名稱,第二個字符串表示服務的執行路徑。

值得一提的是從Android 7.0後的源碼,對init.rc文件進行了拆分,每個服務一個rc文件。我們要分析的zygote服務的啓動腳本則在init.zygoteXX.rc中定義。

在init.rc的import段我們看到如下代碼:

import /init.${ro.zygote}.rc     // 可以看出init.rc不再直接引入一個固定的文件,而是根據屬性ro.zygote的內容來引入不同的文件

       從android5.0開始,android開始支持64位的編譯,zygote本身也就有了32位和64位的區別,所以在這裏用ro.zygote屬性來控制啓動不同版本的zygote進程。

       init.rc位於/system/core/rootdir下。在這個路徑下還包括四個關於zygote的rc文件。分別是init.zygote32.rc,init.zygote32_64.rc,init.zygote64.rc,init.zygote64_32.rc,由硬件決定調用哪個文件。

       這裏拿32位處理器爲例,init.zygote32.rc的代碼如下所示:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main                                              # class是一個option,指定zygote服務的類型爲main
    priority -20
    user root
    group root readproc
    socket zygote stream 660 root system                    # socket關鍵字表示一個option,創建一個名爲dev/socket/zygote,類型爲stream,權限爲660的socket
    onrestart write /sys/android_power/request_state wake   # onrestart是一個option,說明在zygote重啓時需要執行的command
    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

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server 語句解讀:

在Init.zygote32.rc中,定義了一個zygote服務:zygote,由關鍵字service告訴init進程創建一個名爲zygote的進程,這個進程要執行的程序是:/system/bin/app_process,給這個進程四個參數:

            🏹 -Xzygote:該參數將作爲虛擬機啓動時所需的參數

            🏹 /system/bin:代表虛擬機程序所在目錄

            🏹 --zygote:指明以ZygoteInit.java類中的main函數作爲虛擬機執行入口

            🏹 --start-system-server:告訴Zygote進程啓動systemServer進程


init.rc解析過程

回顧解析init.rc的代碼:

int main(int argc, char** argv) {
    ... ...
    ActionManager& am = ActionManager::GetInstance();
    ServiceManager& sm = ServiceManager::GetInstance();
    Parser& parser = Parser::GetInstance();                                          // 構造解析文件用的parser對象

    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm));        // 增加ServiceParser爲一個section,對應name爲service
    parser.AddSectionParser("on", std::make_unique<ActionParser>(&am));              // 增加ActionParser爲一個section,對應name爲action
    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));      // 增加ActionParser爲一個section,對應name爲import
    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        parser.ParseConfig("/init.rc");                                              // 開始實際的解析過程
        parser.set_is_system_etc_init_loaded(
                parser.ParseConfig("/system/etc/init"));
        parser.set_is_vendor_etc_init_loaded(
                parser.ParseConfig("/vendor/etc/init"));
        parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
    } else {
        parser.ParseConfig(bootscript);
        parser.set_is_system_etc_init_loaded(true);
        parser.set_is_vendor_etc_init_loaded(true);
        parser.set_is_odm_etc_init_loaded(true);
    }
    ... ...
}

Parse

我們發現在解析前,使用了Parser類(在init目錄下的 init_parser.h 中定義),構造了parser對象:

    Parser& parser = Parser::GetInstance();                // 構造解析文件用的parser對象

初始化ServiceParser用來解析 “service”塊,ActionParser用來解析"on"塊,ImportParser用來解析“import”塊,“import”是用來引入一個init配置文件,來擴展當前配置的。

    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm));        // 增加ServiceParser爲一個section,對應name爲service
    parser.AddSectionParser("on", std::make_unique<ActionParser>(&am));              // 增加ActionParser爲一個section,對應name爲action
    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));      // 增加ActionParser爲一個section,對應name爲import

/system/core/init/readme.txt中對init文件中的所有關鍵字做了介紹,主要包含了Actions, Commands, Services, Options, and Imports等,可自行學習解讀。

ParseConfig

下面就是分析解析過程了:parser.ParseConfig("/init.rc") (函數定義於 system/core/init/init_parser.cpp 中)

bool Parser::ParseConfig(const std::string& path) {
    if (is_dir(path.c_str())) {              // 判斷傳入參數是否爲目錄地址
        return ParseConfigDir(path);         // 遞歸目錄,最終還是靠ParseConfigFile來解析實際的文件
    }
    return ParseConfigFile(path);            // 傳入參數爲文件地址
}

先來看看ParseConfigDir函數:

bool Parser::ParseConfigDir(const std::string& path) {
    LOG(INFO) << "Parsing directory " << path << "...";
    std::unique_ptr<DIR, int(*)(DIR*)> config_dir(opendir(path.c_str()), closedir);
    if (!config_dir) {
        PLOG(ERROR) << "Could not import directory '" << path << "'";
        return false;
    }
    // 遞歸目錄,得到需要處理的文件
    dirent* current_file;
    std::vector<std::string> files;
    while ((current_file = readdir(config_dir.get()))) {
        // Ignore directories and only process regular files.
        if (current_file->d_type == DT_REG) {
            std::string current_path =
                android::base::StringPrintf("%s/%s", path.c_str(), current_file->d_name);
            files.emplace_back(current_path);
        }
    }
    // Sort first so we load files in a consistent order (bug 31996208)
    std::sort(files.begin(), files.end());
    for (const auto& file : files) {
        // 容易看出,最終仍是調用ParseConfigFile
        if (!ParseConfigFile(file)) {
            LOG(ERROR) << "could not import file '" << file << "'";
        }
    }
    return true;
}

接下來就重點分析ParseConfigFile():

bool Parser::ParseConfigFile(const std::string& path) {
    ... ...
    android::base::Timer t;
    std::string data;
    std::string err;
    if (!ReadFile(path, &data, &err)) {     // 讀取路徑指定文件中的內容,保存爲字符串形式
        LOG(ERROR) << err;
        return false;
    }
    ... ...
    ParseData(path, data);                  // 解析獲取的字符串
    ... ...
    return true;
}

ParseConfigFile只是讀取文件的內容並轉換爲字符串,實際的解析工作被交付給ParseData。

ParseData

ParseData函數定義於system/core/init/init_parser.cpp中,負責根據關鍵字解析出服務和動作。動作與服務會以鏈表節點的形式註冊到service_list與action_list中,service_list與action_list是init進程中聲明的全局結構體。

跟蹤ParseData():

void Parser::ParseData(const std::string& filename, const std::string& data) {
    //TODO: Use a parser with const input and remove this copy
    // copy一波數據
    std::vector<char> data_copy(data.begin(), data.end());
    data_copy.push_back('\0');

    // 解析用的結構體
    parse_state state;
    state.line = 0;
    state.ptr = &data_copy[0];
    state.nexttoken = 0;

    SectionParser* section_parser = nullptr;
    std::vector<std::string> args;

    for (;;) {
        switch (next_token(&state)) {                // next_token以行爲單位分割參數傳遞過來的字符串,初始沒有分割符時,最先走到T_TEXT分支
        case T_EOF:
            if (section_parser) {
                section_parser->EndSection();        // 解析結束
            }
            return;
        case T_NEWLINE:
            state.line++;
            if (args.empty()) {
                break;
            }

            ... ...

            // 在前文創建parser時,我們爲service,on,import定義了對應的parser
            // 這裏就是根據第一個參數,判斷是否有對應的parser
            if (section_parsers_.count(args[0])) {           
                if (section_parser) {
                    // 結束上一個parser的工作,將構造出的對象加入到對應的service_list與action_list中
                    section_parser->EndSection();            
                }
                // 獲取參數對應的parser
                section_parser = section_parsers_[args[0]].get();
                std::string ret_err;
                // 調用實際parser的ParseSection函數
                if (!section_parser->ParseSection(std::move(args), filename, state.line, &ret_err)) {
                    LOG(ERROR) << filename << ": " << state.line << ": " << ret_err;
                    section_parser = nullptr;
                }
            } else if (section_parser) {
                std::string ret_err;
                /* 
                 * 如果第一個參數不是service,on,import
                 * 則調用前一個parser的ParseLineSection函數
                 * 這裏相當於解析一個參數塊的子項
                 */
                if (!section_parser->ParseLineSection(std::move(args), state.line, &ret_err)) {
                    LOG(ERROR) << filename << ": " << state.line << ": " << ret_err;
                }
            }
            // 清空本次解析的數據
            args.clear();      
            break;
        case T_TEXT:
            // 將本次解析的內容寫入到args中
            args.emplace_back(state.text);    
            break;
        }
    }
}

上面的代碼看起來比較複雜,但實際上就是面向對象,根據不同的關鍵字,使用不同的parser對象進行解析。

至此,init.rc解析完!Ok,別忘了,main函數還沒有分析完,繼續往下看。

第四階段

向執行隊列中添加其他action

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建文件系統目錄並掛載相關的文件系統 */
    /* 03. 重定向輸入輸出/內核Log系統 */
    /* 04. 掛在一些分區設備 */
    /* 05. 完成SELinux相關工作 */
    /* 06. is_first_stage 收尾 */
    /* ------------ 第一階段 ------------- END ------------ */
	
    /* ------------ 第二階段 ------------ BEGIN------------ */
    /* 01. 初始化屬性域 */
    /* 02. 清空環境變量 */
    /* 03. 完成SELinux相關工作 */
    /* 04. 創建epoll句柄 */
    /* 05. 裝載子進程信號處理器 */
    /* 06. 啓動屬性服務*/
    /* 07. 匹配命令和函數之間對應關係 */
    /* ------------ 第二階段 ------------ END ------------ */
	
    /* ------------ 第三階段 ----------- BEGIN------------ */
	/* init解析 */
    /* ------------ 第三階段 -----------  END ------------ */
	
    /* ------------ 第四階段 ----------- BEGIN------------ */	
    // 通過am對命令執行順序進行控制
    // ActionManager& am = ActionManager::GetInstance();
	
    // init執行命令觸發器主要分爲early-init,init,late-init,boot等
    am.QueueEventTrigger("early-init");          // 添加觸發器early-init,執行on early-init內容

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
    am.QueueBuiltinAction(set_kptr_restrict_action, "set_kptr_restrict");
    am.QueueBuiltinAction(keychord_init_action, "keychord_init");
    am.QueueBuiltinAction(console_init_action, "console_init");

    // Trigger all the boot actions to get us started.
    am.QueueEventTrigger("init");                // 添加觸發器init,執行on init內容,主要包括創建/掛在一些目錄,以及symlink等

    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

    // Don't mount filesystems or start core system services in charger mode.
    std::string bootmode = GetProperty("ro.bootmode", "");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");         // on charger階段
    } else {
        am.QueueEventTrigger("late-init");       // 非充電模式添加觸發器last-init
    }

    // Run all property triggers based on current state of the properties.
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
}

其餘工作

繼續分析main函數:

int main(int argc, char** argv) {
    /* ------------ 第一階段 ------------ BEGIN------------ */
    /* 01. 判斷及增加環境變量 */
    /* 02. 創建文件系統目錄並掛載相關的文件系統 */
    /* 03. 重定向輸入輸出/內核Log系統 */
    /* 04. 掛在一些分區設備 */
    /* 05. 完成SELinux相關工作 */
    /* 06. is_first_stage 收尾 */
    /* ------------ 第一階段 ------------- END ------------ */
	
    /* ------------ 第二階段 ------------ BEGIN------------ */
    /* 01. 初始化屬性域 */
    /* 02. 清空環境變量 */
    /* 03. 完成SELinux相關工作 */
    /* 04. 創建epoll句柄 */
    /* 05. 裝載子進程信號處理器 */
    /* 06. 啓動屬性服務*/
    /* 07. 匹配命令和函數之間對應關係 */
    /* ------------ 第二階段 ------------ END ------------ */
	
    /* ------------ 第三階段 ----------- BEGIN------------ */
	/* init解析 */
    /* ------------ 第三階段 -----------  END ------------ */
	
    /* ------------ 第四階段 ----------- BEGIN------------ */
	/* 01. 向執行隊列中添加其他action */
	/* 02. 其餘工作 */
    while (true) {
    // 判斷是否有事件需要處理
        // By default, sleep until something happens.
        int epoll_timeout_ms = -1;

        if (do_shutdown && !shutting_down) {
            do_shutdown = false;
            if (HandlePowerctlMessage(shutdown_command)) {
                shutting_down = true;
            }
        }

        if (!(waiting_for_prop || sm.IsWaitingForExec())) {
            am.ExecuteOneCommand();                    // 依次執行每個action中攜帶command對應的執行函數
        }
        if (!(waiting_for_prop || sm.IsWaitingForExec())) {
            if (!shutting_down) restart_processes();   // 重啓一些掛掉的進程

            // If there's a process that needs restarting, wake up in time for that.
            if (process_needs_restart_at != 0) {       // 進程重啓相關邏輯
                epoll_timeout_ms = (process_needs_restart_at - time(nullptr)) * 1000;
                if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
            }

            // If there's more work to do, wake up again immediately.
            if (am.HasMoreCommands()) epoll_timeout_ms = 0;      // 有action待處理,不等待
        }

        epoll_event ev;
        // 沒有事件到來的話,最多阻塞timeout時間
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
        if (nr == -1) {
            PLOG(ERROR) << "epoll_wait failed";
        } else if (nr == 1) {
            //有事件到來,執行對應處理函數
            //根據上文知道,epoll句柄(即epoll_fd)主要監聽子進程結束,及其它進程設置系統屬性的請求
            ((void (*)()) ev.data.ptr)();
        }
    } 
} // end main

至此,Init.cpp的main函數分析完畢!init進程已經啓動完成,一些重要的服務如core服務和main服務也都啓動起來,並啓動了zygote(/system/bin/app_process64)進程,zygote初始化時會創建虛擬機,啓動systemserver等。


參考Blog

                  01. https://www.cnblogs.com/pepsimaxin/p/6702945.html
                  02. https://blog.csdn.net/gaugamela/article/details/79280385
                  03. http://qiangbo.space/2016-10-10/AndroidAnatomy_Process_Creation/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章