Android小知識-深刻淺出Android系統啓動流程(上)

本篇文章已受權微信公衆號 顧林海 獨家發佈java

init進程啓動過程

Android系統基於Linux,init進程是Android系統中用戶空間的第一個進程,進程號爲1,init源代碼在system/core/init目錄下。既然init進程是Android系統用戶空間的第一個進程,所以擔負着很是重要的責任,主要負責如下兩件事:linux

  1. 解析配置init.rc,而後啓動系統各類native進程,好比Zygote進程、SurfaceFlinger進程以及media進程等。android

  2. 初始化並啓動屬性服務。ios

init進程的入口函數

init的入口函數是main,代碼以下所示:web

//路徑:/system/core/init/init.cpp
int main(int argc, char** argv) {
    //註釋1
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }
    //註釋2
    if (!strcmp(basename(argv[0]), "watchdogd")) {
        return watchdogd_main(argc, argv);
    }
    //註釋3
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        install_reboot_signal_handlers();
    }

    ...
}
複製代碼

註釋1處判斷當前進程是否是ueventd。init進程建立子進程ueventd,並將建立設備節點文件的工做託付給ueventd。ueventd主要是負責設備節點的建立、權限設定等一系列工做。服務經過使用uevent,監控驅動發送的消息,作進一步處理。安全

ueventd經過兩種方式建立設備節點文件。bash

  1. 「冷插拔」(Cold Plug),即以預先定義的設備信息爲基礎,當ueventd啓動後,統一建立設備節點文件。這一類設備節點文件也被稱爲靜態節點文件。微信

  2. 「熱插拔」(Hot Plug),即在系統運行中,當有設備插入USB端口時,ueventd就會接收到這一事件,爲插入的設備動態建立設備節點文件。這一類設備節點文件也被稱爲動態節點文件。數據結構

註釋2處判斷當前進程是否是watchdogd。Android系統在長時間的運行下會面臨各類軟硬件的問題,爲了解決這個問題,Android開發了WatchDog類做爲軟件看門狗來監控SystemServer中的線程,一旦發現問題,WatchDog會殺死SystemServer進程,SystemServer的父進程Zygote接收到SystemServer的死亡信號後,會殺死本身。Zygote進程死亡的信號傳遞到init進程後,init進程會殺死Zygote進程全部的子進程並重啓Zygote。app

註釋3處判斷是否緊急重啓,若是是緊急重啓,就安裝對應的消息處理器。

//路徑:/system/core/init/init.cpp
int main(int argc, char** argv) {
    ...
    //註釋1
    add_environment("PATH", _PATH_DEFPATH);
    //註釋2
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    if (is_first_stage) {
        // 用於記錄啓動時間
        boot_clock::time_point start_time = boot_clock::now();
        // 清除屏蔽字(file mode creation mask),保證新建的目錄的訪問權限不受屏蔽字影響
        umask(0);
        // 掛載tmpfs文件系統
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        // 掛載devpts文件系統
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        #define MAKE_STR(x) __STRING(x)
        // 掛載proc文件系統
        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
        //8.0新增, 收緊了cmdline目錄的權限
        chmod("/proc/cmdline", 0440);
        // 8.0新增,增長了個用戶組
        gid_t groups[] = { AID_READPROC };
        setgroups(arraysize(groups), groups);
        // 掛載sysfs文件系統
        mount("sysfs", "/sys", "sysfs", 0, NULL);
        // 8.0新增
        mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL)
        // 提早建立了kmsg設備節點文件,用於輸出log信息
        mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
        mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
        mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
        ...
    }
    ...
}
複製代碼

註釋1處添加環境變量。註釋2處獲取本次啓動是不是系統啓動的第一階段,若是是第一階段,進入下面的if語句中,建立並掛載相關的文件系統。

以上建立並掛載的五類文件系統分別以下所示:

  • 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內核引入的,它把鏈接在系統上的設備和總線組織成爲一個分級的文件,使得它們能夠在用戶空間存取。

  • selinuxfs:用於支持SELinux的文件系統,SELinux提供了一套規則來編寫安全策略文件,這套規則被稱之爲 SELinux Policy 語言。

//路徑:/system/core/init/init.cpp
int main(int argc, char** argv) {
    ...
    if (is_first_stage) {
        ...
        //重定向輸入輸出/內核Log系統
        InitKernelLogging(argv);
        LOG(INFO) << "init first stage started!";
        //掛在一些分區設備
        if (!DoFirstStageMount()) {
            LOG(ERROR) << "Failed to mount required partitions early ...";
            panic();
        }
        //註釋1
        SetInitAvbVersionInRecovery();
        //註釋2
        selinux_initialize(true);
        ...
    }
    ...
}
複製代碼

註釋1處初始化安全框架AVB(Android Verified Boot),AVB主要用於防止系統文件自己被篡改,還包含了防止系統回滾的功能,以避免有人試圖回滾系統並利用之前的漏洞。註釋2處調用selinux_initialize啓動SELinux。

//路徑:/system/core/init/init.cpp
int main(int argc, char** argv) {
    ...
    if (is_first_stage) {
        ...
    }
    ...
    //註釋1
    property_init();  
    ...
    //註釋2
    signal_handler_init();
    ...
    //註釋3
    start_property_service();
    ...
}
複製代碼

註釋1處經過property_init函數對屬性服務進行初始化,註釋3經過start_property_service函數啓動屬性服務。註釋2處signal_handler_init設置子進程退出的信號處理函數,當子進程異常退出的時候,init進程會去捕獲異常信息,當它捕獲到這些異常信息以後,就會調用該函數設置的相應的捕獲函數來處理。好比init進程的子進程Zygote死以後,init進程捕獲到這些異常信息,就會調用handle_signal()函數去重啓Zygote進程。

//路徑:/system/core/init/init.cpp
int main(int argc, char** argv) {
    ...
    if (is_first_stage) {
        ...
    }
    ...
     if (bootscript.empty()) {
        //註釋1
        parser.ParseConfig("/init.rc");
        ...
    } else {
        ...
    }
    ...
    while (true) {
        ...
        ServiceManager::GetInstance().IsWaitingForExec())) {
            //註釋2
            am.ExecuteOneCommand();
        }
        if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
            //註釋3
            restart_processes();
            ...
        }
        ...
    }    
    return 0;
}
複製代碼

註釋1處解析init.rc配置文件,在註釋2處執行子進程對應的命令,也就是執行init.rc文件裏配置的命令。在註釋3處重啓死掉的service。

解析init.rc

在init.rc中使用的語言稱爲Android Init Language,翻譯過來就是「Android初始化語言」,init語言共有五種類型的表達式,分別以下所示:

  • Action:Action中包含了一系列的Command。

  • Command:init語言中的命令。

  • Service:由init進程啓動的服務。

  • Option:對服務的配置選項。

  • Import:引入其餘配置文件。

Action表達式的語法以下所示:

on <trigger> [&& <trigger>]*
    <command>
    <command>
    <command>
複製代碼

這裏的trigger是Action執行的觸發器,當觸發器條件知足時,command會被執行。觸發器有以下兩類:

  • 事件觸發器:當指定的事件發生時觸發。事件可能由「trigger」命令發出,也多是init進程經過QueueEventTrigger()函數發出。

  • 屬性觸發器:當指定的屬性知足某個值時觸發。

Action中的Command是init語言定義的命令,全部支持的命令以下表:

命令 參數格式 說明
bootchart_init - 啓動bootchart
chmod octal-mode path 改變文件的訪問權限
chown owner group path 改變文件的擁有者和組
class_start serviceclass 啓動指定類別的服務
class_stop serviceclass 中止並「disable」指定類別的服務
class_reset serviceclass 中止指定類別的服務,可是不「disable」它們
copy src dst 複製文件
domainname name 設置域名
enable servicename enable一個被disable的服務
exec [seclabel[user[group]]] -- command [argument]* fork一個子進程來執行指定的命令
export name value 導出環境變量
hostname name 設置host名稱
ifup iterface 使網卡在線
insmod path 安裝指定路徑的模塊
load_all_props - 從/system、/vendor等路徑載入屬性
load_persist_props - 載入持久化的屬性
loglevel level 設置內核的日誌級別
mkdir path[mode][owner][group] 建立目錄
mount_all fstab[path]*[--option] 掛載文件系統而且導入指定的.rc文件
mount typedevicedir[flag]*[options] 掛載一個文件系統
powerctl - 內部實現使用
restart service 重啓服務
restorecon path[path]* 設定文件的安全上下文
restorecon_recursive path[path]* restorecon的遞歸版本
rm path 對於指定路徑調用unlink(2)
rmdir path 刪除文件夾
setprop namevalue 設置屬性值
setrlimit resourcecurmax 指定資源的rlimit
start service 啓動服務
stop service 中止服務
swapon_all fstab 在指定文件上調用fs_mgr_swapon_all
symlink targetpath 建立符合連接
sysclktz mins_west_of_gmt 指定系統時鐘基準
trigger event 觸發一個事件
umount path ummount指定的文件系統
verity_load_state - 內部實現使用
verity_update_state mount_point 內部實現使用
wait path[timeout] 等待某個文件存在直到超時,若存在則直接返回
write pathcontent 寫入內容到指定文件

Service是init進程啓動的可執行程序,Service表達式的語法以下所示:

service <name> <pathname> [ <argument> ]*    <option>
    <option>
複製代碼

Option是對服務的修飾,它們影響着init進程如何以及什麼時候啓動服務。全部支持的Option入下所示:

Option 參數格式 說明
critical - 標識爲系統關鍵服務,該服務若退出屢次將致使系統重啓到recovery模式
disabled - 不會隨着類別自動啓動,必須明確start
setenv name value 爲啓動的進程設置環境變量
socket nametypeperm[user[group[seclabel]]] 建立UNIX Domain Socket
user username 在執行服務以前切換用戶
group groupname[groupname]* 在執行服務以前切換組
seclabel seclabel 在執行服務以前切換seclabel
oneshot - 一次性服務,死亡後不用重啓
class name 指定服務的類別
onrestart - 當服務重啓時執行命令
writepid file... 寫入子進程的pid到指定文件

import是一個關鍵字,而不是一個命令,能夠在.rc文件中經過這個關鍵字來加載其餘的.rc文件,它的語法以下:

import path
複製代碼

path能夠是另外一個.rc文件,也能夠是一個文件夾。若是是文件夾,那麼這個文件夾下面的全部文件都會被導入,可是它不會循環加載子目錄中的文件。

啓動Zygote

init.rc文件有以下配置代碼:

...import /init.${ro.zygote}.rc...on nonencrypted
    class_start main
    class_start late_start...    
複製代碼

在init.rc文件的開頭使用了import類型語句來引入Zygote啓動腳本,其中ro.zygote根據不一樣的內容引入不一樣的文件,從Android 5.0開始,Android開始支持64位程序,Zygote就有了32位和64位之分,以下圖所示:

image

查看init.zygote64.rc的代碼以下所示:

service zygote /system/bin/app_process64 -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
複製代碼

Service用於通知init進程建立名爲zygote的進程,這個進程執行程序的路徑爲/system/bin/app_process64,後面的代碼是傳遞給app_process64的參數,class main指的是Zygote的classname爲main。在解析Service類型語句時會將Service對象加入Service鏈表中。

再回過頭看init.rc配置文件:

...import /init.${ro.zygote}.rc...on nonencrypted
    class_start main
    class_start late_start...    
複製代碼

class_start是一個command,對應的函數是do_class_start,用於啓動classname爲main的Service,也就是前面的Zygote,所以class_start main是用來啓動Zygote的,do_class_start函數在builtins.cpp中定義,代碼以下所示:

//路徑:/system/core/init/builtins.cpp
static int do_class_start(const std::vector<std::string>& args) {
        /* Starting a class does not start services
         * which are explicitly disabled.  They must
         * be started individually.
         */
    ServiceManager::GetInstance().
        ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });
    return 0;
}
複製代碼

ForEachServiceInClass函數會遍歷Service鏈表,找到classname爲main的Zygote,並執行StartIfNotDisabled函數,代碼以下所示:

//路徑:/system/core/init/service.cpp
bool Service::StartIfNotDisabled() {
    if (!(flags_ & SVC_DISABLED)) {
        return Start();
    } else {
        flags_ |= SVC_DISABLED_START;
    }
    return true;
}
複製代碼

若是Service沒有在其對應的rc文件中設置disabled選項,就會調用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 {
        //註釋1
        pid = fork();
    }

    if (pid == 0) {
        ...
        //註釋2
        if (execve(strs[0], (char**) &strs[0], (char**) ENV) < 0) {
            PLOG(ERROR) << "cannot execve('" << strs[0] << "')";
        }
        ...
    }
    ...
}
複製代碼

在註釋1處經過fork函數建立子進程,並返回pid,若是pid爲0說明當前代碼邏輯在子線程中運行,接着執行註釋2處的execve函數,來啓動Service子進程,進入Service的main函數中,若是Service是Zygote,執行程序的路徑是/system/bin/app_process64,對應的文件是app_main.cpp,也就是會進入app_main.cpp的main函數中。代碼以下所示:

int main(int argc, char* const argv[])
{
    ...
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            //註釋1
            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;
        }
    }
    ...
    if (zygote) {
        //註釋2
        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處判斷執行命令時是否帶了--zygote,若是攜帶了,zygote賦值爲true,接在註釋2處判斷若是zygote爲true,就會經過runtime.start啓動com.android.internal.os.ZygoteInit。


838794-506ddad529df4cd4.webp.jpg
相關文章
相關標籤/搜索