<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
當我們按下電源鍵時,手機開始上電,並從地址0x00000000處開始執行,而這個地址通常是Bootloader程式的首地址。
bootloader是一段裸機程式,是直接與硬體打交道的,其最終目的是“初始化並檢測硬體裝置,準備好軟體環境,最後呼叫作業系統核心”。除此之外,bootloader還有保護功能,部分品牌的手機對bootloader做了加鎖操作,防止boot分割區和recovery分割區被寫入。
或許有人會問了,什麼是boot分割區,什麼又是recovery分割區?
我們先來認識一下Android系統的常見分割區:
/boot
這個分割區上有Android的載入程式,包括核心和記憶體操作程式。沒有這個分割區裝置就不能被引導。恢復系統的時候會擦除這個分割區,並且必須重新安裝載入程式和ROM才能重啟系統。
/recovery
recovery分割區被認為是另一個啟動分割區,你可以啟動裝置進入recovery控制檯去執行高階的系統恢復和管理操作。
/data
這個分割區儲存著使用者資料。通訊錄、簡訊、設定和你安裝的apps都在這個分割區上。擦除這個分割區相當於恢復出廠設定,當你第一次啟動裝置的時候或者在安裝了官方或者客戶的ROM之後系統會自動重建這個分割區。當你執行恢復出廠設定時,就是在擦除這個分割區。
/cache
這個分割區是Android系統儲存頻繁存取的資料和app的地方。擦除這個分割區不影響你的個人資料,當你繼續使用裝置時,被擦除的資料就會自動被建立。
/apex
Android Q新增特性,將系統功能模組化,允許系統按模組來獨立升級。此分割區用於存放apex 相關的內容。
為什麼需要bootloader去拉起linux核心,而不把bootloader這些功能直接內建在linux核心中呢?這個問題不在此做出回答,留給大家自行去思考。
bootloader完成初始化工作後,會載入 /boot 目錄下面的 kernel,此時控制權轉交給作業系統。作業系統將要完成的儲存管理、裝置管理、檔案管理、程序管理、載入驅動等任務的初始化工作,以便進入使用者態。
核心啟動完成後,將會尋找init檔案(init檔案位於/system/bin/init),啟動init程序,也就是android的第一個程序。
我們來關注一下核心的common/init/main.c中的kernel_init方法。
static int __ref kernel_init(void *unused) { ... if (execute_command) { ret = run_init_process(execute_command); if (!ret) return 0; } if (CONFIG_DEFAULT_INIT[0] != ' ') { ret = run_init_process(CONFIG_DEFAULT_INIT); if (ret) pr_err("Default init %s failed (error %d)n",CONFIG_DEFAULT_INIT, ret); else return 0; } if (!try_to_run_init_process("/sbin/init") ||!try_to_run_init_process("/etc/init") || !try_to_run_init_process("/bin/init") ||!try_to_run_init_process("/bin/sh")) return 0; }
可以看到,在init_kernel的最後,會呼叫run_init_process方法來啟動init程序。
static int run_init_process(const char *init_filename){ const char *const *p; argv_init[0] = init_filename; return kernel_execve(init_filename, argv_init, envp_init); }
kernel_execve是核心空間呼叫使用者空間的應用程式的函數。
接下來我們來重點分析init程序。
我們從system/core/init/main.cpp 這個檔案開始看起。
int main(int argc, char** argv) { #if __has_feature(address_sanitizer) __asan_set_error_report_callback(AsanReportCallback); #endif if (!strcmp(basename(argv[0]), "ueventd")) { return ueventd_main(argc, argv); } if (argc > 1) { if (!strcmp(argv[1], "subcontext")) { android::base::InitLogging(argv, &android::base::KernelLogger); const BuiltinFunctionMap function_map; return SubcontextMain(argc, argv, &function_map); } if (!strcmp(argv[1], "selinux_setup")) { return SetupSelinux(argv); } if (!strcmp(argv[1], "second_stage")) { return SecondStageMain(argc, argv); } } return FirstStageMain(argc, argv); }
第一個引數argc表示引數個數,第二個引數是參數列,也就是具體的引數。
main函數有四個引數入口:
main的執行順序如下:
我們來從FirstStageMain的原始碼看起,原始碼位於/system/core/init/first_stage_init.cpp
int FirstStageMain(int argc, char** argv) { boot_clock::time_point start_time = boot_clock::now(); #define CHECKCALL(x) if (x != 0) errors.emplace_back(#x " failed", errno); // Clear the umask. umask(0); //初始化系統環境變數 CHECKCALL(clearenv()); CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1)); // 掛載及建立基本的檔案系統,並設定合適的存取許可權 CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755")); CHECKCALL(mkdir("/dev/pts", 0755)); CHECKCALL(mkdir("/dev/socket", 0755)); CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL)); #define MAKE_STR(x) __STRING(x) CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC))); #undef MAKE_STR // 不要將原始命令列公開給非特權程序 CHECKCALL(chmod("/proc/cmdline", 0440)); gid_t groups[] = {AID_READPROC}; CHECKCALL(setgroups(arraysize(groups), groups)); CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL)); CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL)); CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11))); if constexpr (WORLD_WRITABLE_KMSG) { CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11))); } //建立linux隨機偽裝置檔案 CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8))); CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9))); //log wrapper所必須的,需要在ueventd執行之前被呼叫 CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2))); CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3))); ... //將核心的stdin/stdout/stderr 全都重定向/dev/null,關閉預設控制檯輸出 SetStdioToDevNull(argv); // tmpfs已經掛載到/dev上,同時我們也掛載了/dev/kmsg,我們能夠與外界開始溝通了 //初始化核心log InitKernelLogging(argv); //檢測上面的操作是否發生了錯誤 if (!errors.empty()) { for (const auto& [error_string, error_errno] : errors) { LOG(ERROR) << error_string << " " << strerror(error_errno); } LOG(FATAL) << "Init encountered errors starting first stage, aborting"; } LOG(INFO) << "init first stage started!"; auto old_root_dir = std::unique_ptr<DIR, decltype(&closedir)>{opendir("/"), closedir}; if (!old_root_dir) { PLOG(ERROR) << "Could not opendir("/"), not freeing ramdisk"; } struct stat old_root_info; ... //掛載 system、cache、data 等系統分割區 if (!DoFirstStageMount()) { LOG(FATAL) << "Failed to mount required partitions early ..."; } ... //進入下一步,SetupSelinux const char* path = "/system/bin/init"; const char* args[] = {path, "selinux_setup", nullptr}; execv(path, const_cast<char**>(args)); return 1; }
我們來總結一下,FirstStageMain到底做了哪些重要的事情:
這個模組主要的工作是設定SELinux安全策略,本章內容主要聚焦於android的啟動流程,selinux的內容在此不做展開。
int SetupSelinux(char** argv) { ... const char* path = "/system/bin/init"; const char* args[] = {path, "second_stage", nullptr}; execv(path, const_cast<char**>(args)); return 1; }
SetupSelinux的最後,進入了init的第二階段SecondStageMain。
不多說,先上程式碼。
int SecondStageMain(int argc, char** argv) { // 禁止OOM killer 結束該程序以及它的子程序 if (auto result = WriteFile("/proc/1/oom_score_adj", "-1000"); !result) { LOG(ERROR) << "Unable to write -1000 to /proc/1/oom_score_adj: " << result.error(); } // 啟用全域性Seccomp,Seccomp是什麼請自行查閱資料 GlobalSeccomp(); // 設定所有程序都能存取的對談金鑰 keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1); // 建立 /dev/.booting 檔案,就是個標記,表示booting進行中 close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); //初始化屬性的服務,並從指定檔案讀取屬性 property_init(); ... // 進行SELinux第二階段並恢復一些檔案安全上下文 SelinuxSetupKernelLogging(); SelabelInitialize(); SelinuxRestoreContext(); //初始化Epoll,android這裡對epoll做了一層封裝 Epoll epoll; if (auto result = epoll.Open(); !result) { PLOG(FATAL) << result.error(); } //epoll 中註冊signalfd,主要是為了建立handler處理子程序終止訊號 InstallSignalFdHandler(&epoll); ... //epoll 中註冊property_set_fd,設定其他系統屬性並開啟系統屬性的服務 StartPropertyService(&epoll); MountHandler mount_handler(&epoll); ... ActionManager& am = ActionManager::GetInstance(); ServiceList& sm = ServiceList::GetInstance(); //解析init.rc等檔案,建立rc檔案的action 、service,啟動其他程序,十分關鍵的一步 LoadBootScripts(am, sm); ... am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups"); //執行rc檔案中觸發器為 on early-init 的語句 am.QueueEventTrigger("early-init"); // 等冷插拔裝置初始化完成 am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done"); am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng"); am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits"); am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict"); // 裝置組合鍵的初始化操作 Keychords keychords; am.QueueBuiltinAction( [&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> { for (const auto& svc : ServiceList::GetInstance()) { keychords.Register(svc->keycodes()); } keychords.Start(&epoll, HandleKeychord); return Success(); }, "KeychordInit"); am.QueueBuiltinAction(console_init_action, "console_init"); // 執行rc檔案中觸發器為on init的語句 am.QueueEventTrigger("init"); // Starting the BoringSSL self test, for NIAP certification compliance. am.QueueBuiltinAction(StartBoringSslSelfTest, "StartBoringSslSelfTest"); // 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(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng"); am.QueueBuiltinAction(InitBinder, "InitBinder"); // 當裝置處於充電模式時,不需要mount檔案系統或者啟動系統服務,充電模式下,將charger設為執行佇列,否則把late-init設為執行佇列 std::string bootmode = GetProperty("ro.bootmode", ""); if (bootmode == "charger") { am.QueueEventTrigger("charger"); } else { am.QueueEventTrigger("late-init"); } // 基於屬性當前狀態 執行所有的屬性觸發器. am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers"); while (true) { //開始進入死迴圈狀態 auto epoll_timeout = std::optional<std::chrono::milliseconds>{}; //執行關機重啟流程 if (do_shutdown && !shutting_down) { do_shutdown = false; if (HandlePowerctlMessage(shutdown_command)) { shutting_down = true; } } if (!(waiting_for_prop || Service::is_exec_service_running())) { am.ExecuteOneCommand(); } if (!(waiting_for_prop || Service::is_exec_service_running())) { if (!shutting_down) { auto next_process_action_time = HandleProcessActions(); // If there's a process that needs restarting, wake up in time for that. if (next_process_action_time) { epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>( *next_process_action_time - boot_clock::now()); if (*epoll_timeout < 0ms) epoll_timeout = 0ms; } } // If there's more work to do, wake up again immediately. if (am.HasMoreCommands()) epoll_timeout = 0ms; } // 迴圈等待事件發生 if (auto result = epoll.Wait(epoll_timeout); !result) { LOG(ERROR) << result.error(); } } return 0; }
總結一下,第二階段做了以下這些比較重要的事情:
在第二階段,我們需要重點關注以下問題:
init程序是如何通過init.rc組態檔去啟動其他的程序的呢?
我們從 LoadBootScripts(am, sm)這個方法開始看起,一步一部來挖掘init.rc 的解析流程。
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) { //初始化ServiceParse、ActionParser、ImportParser三個解析器 Parser parser = CreateParser(action_manager, service_list); std::string bootscript = GetProperty("ro.boot.init_rc", ""); if (bootscript.empty()) { //bootscript為空,進入此分支 parser.ParseConfig("/init.rc"); if (!parser.ParseConfig("/system/etc/init")) { late_import_paths.emplace_back("/system/etc/init"); } if (!parser.ParseConfig("/product/etc/init")) { late_import_paths.emplace_back("/product/etc/init"); } if (!parser.ParseConfig("/product_services/etc/init")) { late_import_paths.emplace_back("/product_services/etc/init"); } if (!parser.ParseConfig("/odm/etc/init")) { late_import_paths.emplace_back("/odm/etc/init"); } if (!parser.ParseConfig("/vendor/etc/init")) { late_import_paths.emplace_back("/vendor/etc/init"); } } else { parser.ParseConfig(bootscript); } }
我們可以看到這句話,Parse開始解析init.rc檔案,在深入下去之前,讓我們先來認識一下init.rc。
parser.ParseConfig("/init.rc")
init.rc是一個可設定的初始化檔案,負責系統的初步建立。它的原始檔的路徑為 /system/core/rootdir/init.rc
。
init.rc檔案有著固定的語法,由於內容過多,限制於篇幅的原因,在此另外單獨開了一篇文章進行講解:
瞭解了init.rc的語法後,我們來看看init.rc檔案裡的內容。
import /init.environ.rc //匯入全域性環境變數 import /init.usb.rc //adb 服務、USB相關內容的定義 import /init.${ro.hardware}.rc //硬體相關的初始化,一般是廠商客製化 import /vendor/etc/init/hw/init.${ro.hardware}.rc import /init.usb.configfs.rc import /init.${ro.zygote}.rc //定義Zygote服務
我們可以看到,在/system/core/init目錄下,存在以下四個zygote相關的檔案
怎樣才能知道我們當前的手機用的是哪個組態檔呢?
答案是通過adb shell getprop | findstr ro.zygote
命令,看看${ro.zygote}
這個環境變數具體的值是什麼,筆者所使用的華為手機的ro.zygote
值如下所示:
什麼是Zygote,Zygote的啟動過程是怎樣的,它的啟動組態檔裡又做了啥,在這裡我們不再做進一步探討, 只需要知道init在一開始在這個檔案中對Zygote服務做了定義,而上述的這些問題將留到 啟動分析之Zygote篇
再去說明。
on early-init # Disable sysrq from keyboard write /proc/sys/kernel/sysrq 0 # Set the security context of /adb_keys if present. restorecon /adb_keys # Set the security context of /postinstall if present. restorecon /postinstall mkdir /acct/uid # memory.pressure_level used by lmkd chown root system /dev/memcg/memory.pressure_level chmod 0040 /dev/memcg/memory.pressure_level # app mem cgroups, used by activity manager, lmkd and zygote mkdir /dev/memcg/apps/ 0755 system system # cgroup for system_server and surfaceflinger mkdir /dev/memcg/system 0550 system system start ueventd # Run apexd-bootstrap so that APEXes that provide critical libraries # become available. Note that this is executed as exec_start to ensure that # the libraries are available to the processes started after this statement. exec_start apexd-bootstrap
緊接著是一個Action,Action的Trigger 為early-init,在這個 Action中,我們需要關注最後兩行,它啟動了ueventd服務和apex相關服務。還記得什麼是ueventd和apex嗎?不記得的讀者請往上翻越再自行回顧一下。
ueventd服務的定義也可以在init.rc檔案的結尾找到,具體程式碼及含義如下:
service ueventd //ueventd服務的可執行檔案的路徑為 /system/bin/ueventd class core //ueventd 歸屬於 core class,同樣歸屬於core class的還有adbd 、console等服務 critical //表明這個Service對裝置至關重要,如果Service在四分鐘內退出超過4次,則裝置將重啟進入恢復模式。 seclabel u:r:ueventd:s0 //selinux相關的設定 shutdown critical //ueventd服務關閉行為
然而,early-init 這個Trigger到底什麼時候觸發呢?
答案是通過init.cpp程式碼呼叫觸發。
我們可以在init.cpp 程式碼中找到如下程式碼片段:
am.QueueEventTrigger("early-init");
QueueEventTrigger這個方法的實現機制我們稍後再進行探討,目前我們只需要瞭解, ActionManager
這個類中的 QueueEventTrigger
方法,負責觸發init.rc中的Action。
我們繼續往下看init.rc的內容。
on init ... # Start logd before any other services run to ensure we capture all of their logs. start logd # Start essential services. start servicemanager ...
在Trigger 為init的Action中,我們只需要關注以上的關鍵內容。在init的action中啟動了一些核心的系統服務,這些服務具體的含義為 :
服務名 | 含義 |
---|---|
logd | Android L加入的服務,用於儲存Android執行期間的紀錄檔 |
servicemanager | android系統服務管理者,負責查詢和註冊服務 |
接下來是late-init Action:
on late-init //啟動vold服務(管理和控制Android平臺外部儲存裝置,包括SD插撥、掛載、解除安裝、格式化等) trigger early-fs trigger fs trigger post-fs trigger late-fs //掛載/data , 啟動 apexd 服務 trigger post-fs-data # 讀取持久化屬性或者從/data 中讀取並覆蓋屬性 trigger load_persist_props_action //啟動zygote服務!!在啟動zygote服務前會先啟動netd服務(專門負責網路管理和控制的後臺守護行程) trigger zygote-start //移除/dev/.booting 檔案 trigger firmware_mounts_complete trigger early-boot trigger boot //初始化網路環境,設定系統環境和守護行程的許可權
最後,我們用流程圖來總結一下上述的啟動過程:
以上就是Android 10 啟動Init程序解析的詳細內容,更多關於Android 10 啟動Init程序的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45