Android13系統(tǒng)啟動階段大致分為FirstStageMain階段和SecondStageMain,此章主要講SecondStageMain階段
(若分析有誤敬請指教)
一. Android系統(tǒng)啟動基本介紹
在基于Android13的系統(tǒng)啟動流程分析(三)之FirstStageMain階段已經(jīng)講解過android系統(tǒng)啟動的基本介紹了,這里不再單獨(dú)介紹了
二. SecondStageMain源碼分析
我們先看是怎么進(jìn)入該階段的,仍然是由用戶空間層main.cpp調(diào)用,先簡單的說一下第二階段主要是干什么的:
- 設(shè)置init進(jìn)程優(yōu)先級并創(chuàng)建
/dev/.booting
設(shè)備塊代表init正在初始化執(zhí)行中 - 初始化屬性服務(wù),也就是會讀取property_contexts文件內(nèi)容以及讀取build.prop內(nèi)容通過MMAP映射到全局內(nèi)存中,也就是對所有進(jìn)程共享該資源
- 啟動屬性服務(wù)并創(chuàng)建socket_service,等待新鏈接去更新或新增屬性值
- 掛載/apex,vendor_overlay等其他分區(qū)
- 檢查設(shè)備是否被unlock解鎖
- 持續(xù)監(jiān)控/proc/mounts設(shè)備文件,解析文件中每一行數(shù)據(jù),獲取掛載點(diǎn),掛載分區(qū),文件類型,權(quán)限等。將解析內(nèi)容生成實(shí)體類追加到要掛載的mounts_中并進(jìn)行掛載
- 將根目錄下所有的目錄設(shè)置為全局共享,例如對/data設(shè)置為根目錄下的全局共享
- 解析init.rc以及其他import了的rc文件,主要解析rc中的:service,on(action),Import,而zygote進(jìn)程正是從解析rc文件中創(chuàng)建的,然后根據(jù)zygote(本質(zhì)上就是一個socket),通過JNI調(diào)用到上層代碼,再fork出systemServer.java
- 讓init進(jìn)程無限循環(huán),因?yàn)橹鬟M(jìn)程不能退出,退出即代表發(fā)生異常
- 處理sm(ServiceList)中服務(wù)超時重啟相關(guān)(init.rc中的service),若rc中啟動的服務(wù)啟動超時則會讓其服務(wù)重新啟動
...
if (argc > 1) {
...
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);//第二階段執(zhí)行
}
}
return FirstStageMain(argc, argv); //第一階段執(zhí)行
}
1. int SecondStageMain(int argc, char** argv)分析
位于/system/core/init/init.cpp
,直接上代碼,代碼注釋中分步驟來分析
int SecondStageMain(int argc, char** argv) {
if (REBOOT_BOOTLOADER_ON_PANIC) {
// 針對產(chǎn)生異常的進(jìn)程進(jìn)行信號處理,確保子進(jìn)程能重啟,如果主進(jìn)程pid=1發(fā)生異常則觸發(fā)crash
// 已經(jīng)在上個文章分析過該函數(shù)
InstallRebootSignalHandlers();
}
...
// 初始化kernel log,所有的kernel log均輸出在/dev/kmsg設(shè)備節(jié)點(diǎn)上
SetStdioToDevNull(argv);
InitKernelLogging(argv);
LOG(INFO) << "init second stage started!";
...
// Init不應(yīng)該因?yàn)橐蕾囉谌魏纹渌M(jìn)程而崩潰,因此我們忽略主進(jìn)程的信號管道信息
// 但我們不想忽略子進(jìn)程的SIGPIPE(信號管道),因此我們?yōu)樾盘柼幚沓绦蛟O(shè)置了一個no op函數(shù)
// SIGPIPE信號產(chǎn)生的場景舉例
// ① 初始時,C、S連接建立,若某一時刻,C端進(jìn)程宕機(jī)或者被KILL而終止(終止的C端進(jìn)程將會關(guān)閉打開的文件描述符,即向S端發(fā)送FIN段),S端收到FIN后,響應(yīng)ACK
// ② 假設(shè)此時,S端仍然向C端發(fā)送數(shù)據(jù):當(dāng)?shù)谝淮螌憯?shù)據(jù)后,S端將會收到RST分節(jié); 當(dāng)收到RST分節(jié)后,第二次寫數(shù)據(jù)后,S端將收到SIGPIPE信號(S端進(jìn)程被終止)
{
struct sigaction action = {.sa_flags = SA_RESTART};
action.sa_handler = [](int) {};
// sigaction是一個函數(shù),可以用來查詢或設(shè)置信號處理方式
sigaction(SIGPIPE, &action, nullptr);
}
// MIN_OOM_SCORE_ADJUST = -1000;
// MAX_OOM_SCORE_ADJUST = 1000;
// 設(shè)置進(jìn)程的優(yōu)先級,例如APK優(yōu)先級是AMS計(jì)算出來并下發(fā)到/proc/1/oom_score_adj
// 統(tǒng)一由init進(jìn)程設(shè)置/proc/**/oom_score_adj為-1000優(yōu)先級
if (auto result =
WriteFile("/proc/1/oom_score_adj", StringPrintf("%d", DEFAULT_OOM_SCORE_ADJUST));
!result.ok()) {
LOG(ERROR) << "Unable to write " << DEFAULT_OOM_SCORE_ADJUST
<< " to /proc/1/oom_score_adj: " << result.error();
}
...
// 創(chuàng)建 /dev/.booting 文件,就是個標(biāo)記,表示booting進(jìn)行中
// is_booting()函數(shù)會依靠空文件".booting"來判斷是否進(jìn)程處于初始化中,初始化結(jié)束后,這個文件會被刪除
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
// 當(dāng)設(shè)備解鎖時,允許adb root
// 如果設(shè)備unlocked(解鎖了),則會修改selinux規(guī)則,放大用戶權(quán)限,這一點(diǎn)在第一階段已經(jīng)完成了
// 并設(shè)置了INIT_FORCE_DEBUGGABLE環(huán)境變量,這里只是根據(jù)環(huán)境變量獲取第一階段的內(nèi)容
const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
bool load_debug_prop = false;
if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
load_debug_prop = "true"s == force_debuggable_env;
}
unsetenv("INIT_FORCE_DEBUGGABLE");
// 如果設(shè)備未unlock,則卸載關(guān)于debug版本的/debug_ramdisk
// 讓屬性值讀取/ramdisk而不是/debug_ramdisk,因?yàn)榉莡nlock,不需要debug ramdisk
if (!load_debug_prop) {
// setup 1
UmountDebugRamdisk();
}
// 初始化屬性服務(wù)
// 獲取system/build.prop,vendor/build.prop,/odm/build.prop,/product/build.prop,等其他build.prop并加載到properties map結(jié)構(gòu)中
// 然后會將該properties結(jié)構(gòu),通過MMAP映射到全局內(nèi)存中,供所有進(jìn)程調(diào)用,主要ro是只讀,只能夠?qū)懸淮危闯跏蓟瘯r給的一次值
// 注意build.prop有優(yōu)先級,product下的優(yōu)先級最高,因?yàn)槭莔ap結(jié)構(gòu),針對key相同的屬性值時,會覆蓋
// setup 2
PropertyInit();
// Umount second stage resources after property service has read the .prop files.
// 在屬性服務(wù)讀取.prop文件后,將卸載/second_stage_resources,因?yàn)橐呀?jīng)用不到了,已經(jīng)將屬性值加載到內(nèi)存當(dāng)中了
UmountSecondStageRes();
// Umount the debug ramdisk after property service has read the .prop files when it means to.
// 若是debug版本,已經(jīng)獲取了屬性值過后,也將卸載/debug_ramdisk
if (load_debug_prop) {
UmountDebugRamdisk();
}
// 掛載第二階段(該階段)的文件系統(tǒng),第一階段已經(jīng)掛載了很多基本的文件系統(tǒng)了以及重要的分區(qū)
// 掛載/apex:簡單點(diǎn)說apex為了解決性能而產(chǎn)生的機(jī)制,APK可以通過內(nèi)置升級,但系統(tǒng)升級可是個大問題
// setup 3
MountExtraFilesystems();
...
// 之前初始化了屬性服務(wù),這里將開始屬性服務(wù),其實(shí)它就是一個socket
// 創(chuàng)建socket,處理客戶端發(fā)來的請求,決定是更新屬性值還是新增屬性值
// setup 4
StartPropertyService(&property_fd);
...
// 根據(jù)ro.vndk.version 版本號,將/system/vendor_overlay和/product/vendor_overlay掛載在vendor上
// 也就是會覆蓋vendor分區(qū)內(nèi)容
// setup 5
fs_mgr_vendor_overlay_mount_all();
// 根據(jù)ro.oem_unlock_supported屬性值來決定是否可以對設(shè)備進(jìn)行unlock(解鎖)
// 若ro.oem_unlock_supported:「1」則代表 設(shè)備支持刷寫unlock,若不支持該值為0
// 如果設(shè)備支持刷寫解鎖,ro.boot.verifiedbootstate則會為orange,根據(jù)orange狀態(tài),把a(bǔ)ndroidboot.flash.locked設(shè)置為1
// 如果設(shè)備不支持刷新解鎖,ro.boot.verifiedbootstate則會為green,根據(jù)orange狀態(tài),把a(bǔ)ndroidboot.flash.locked設(shè)置為0
// androidboot.flash.locked在系統(tǒng)啟動完成后會形成屬性值
// (或 /firmware/android/flash.locked DT 屬性)設(shè)置為“1”(如果已鎖定)或“0”(如果已解鎖)來指示鎖定狀態(tài)。
export_oem_lock_status();
// 持續(xù)監(jiān)控/proc/mounts 節(jié)點(diǎn)(fopen("/proc/mounts", "re")),主要是解析該文件
// 解析文件中每一行數(shù)據(jù),獲取掛載點(diǎn),掛載分區(qū),文件類型,權(quán)限等(空格分割/dev/block/dm-33 /mnt/pass_through/0/emulated ext4 rw)
// 將解析內(nèi)容生成實(shí)體類追加到要掛載的mounts_中,主要就是對mounts文件解析,更新mounts_中的信息
// setup 6
MountHandler mount_handler(&epoll);
...
// 將根目錄下所有的目錄設(shè)置為全局共享
// 將根目錄/{分區(qū)}類型設(shè)置為共享,以便默認(rèn)情況下所有進(jìn)程都可以看到任何裝載事件(例如/data)
if (!SetupMountNamespaces()) {
PLOG(FATAL) << "SetupMountNamespaces failed";
}
...
// setup 7
// 創(chuàng)建ActionManager對象和ServiceList對象
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
// 加載rc文件,保存到action manager和service list中
// rc文件中:action 使用 ActionParser,而 service 使用 ServiceParser 解析
// 主要解析rc中的:service,on,Import,包含了zygote.rc,路徑:/system/bin/app_process64
// 在文件系統(tǒng)掛載的第一階段,system/vendor分區(qū)已經(jīng)成功掛載,而其它分區(qū)的掛載則通過rc來掛載
// 后面主要分析一下on early-init和on init和zygote
LoadBootScripts(am, sm);
...
// setup 8
// 構(gòu)建action和觸發(fā)器(on early-init),放到event_queue,等待執(zhí)行函數(shù)
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
am.QueueEventTrigger("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(SetMmapRndBitsAction, "SetMmapRndBits");
Keychords keychords;
am.QueueBuiltinAction(
[&epoll, &keychords](const BuiltinArguments& args) -> Result<void> {
for (const auto& svc : ServiceList::GetInstance()) {
keychords.Register(svc->keycodes());
}
keychords.Start(&epoll, HandleKeychord);
return {};
},
"KeychordInit");
// Trigger all the boot actions to get us started.
// 構(gòu)建action和觸發(fā)器(on init),放到event_queue,等待執(zhí)行函數(shù)
am.QueueEventTrigger("init");
// Don't mount filesystems or start core system services in charger mode.
// 如果是充電模式則不需要掛載文件系統(tǒng)和不要啟動核心服務(wù)
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
// Run all property triggers based on current state of the properties.
// 運(yùn)行所有屬性觸發(fā)器(action),例如 on property
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
// Restore prio before main loop
// 設(shè)置進(jìn)程優(yōu)先級,主進(jìn)程不能被銷毀和退出,循環(huán)處理rc中的服務(wù)相關(guān)
setpriority(PRIO_PROCESS, 0, 0);
while (true) {
// By default, sleep until something happens.
auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
auto shutdown_command = shutdown_state.CheckShutdown();
if (shutdown_command) {
LOG(INFO) << "Got shutdown_command '" << *shutdown_command
<< "' Calling HandlePowerctlMessage()";
HandlePowerctlMessage(*shutdown_command);
shutdown_state.set_do_shutdown(false);
}
if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
// 執(zhí)行隊(duì)列中的action
// 隊(duì)列中依次執(zhí)行每個action中攜帶command對應(yīng)的執(zhí)行函數(shù)
am.ExecuteOneCommand();
}
if (!IsShuttingDown()) {
// 處理sm(ServiceList)中服務(wù)超時重啟相關(guān)(init.rc中的service)
auto next_process_action_time = HandleProcessActions();
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;
}
}
...
return 0;
}
2. SecondStageMain(int argc, char** argv)----->setup 1步驟
先貼一下setup 1的代碼塊,主要分析UmountDebugRamdisk
函數(shù)
if (!load_debug_prop) {
// setup 1
UmountDebugRamdisk();
}
static void UmountDebugRamdisk() {
if (umount("/debug_ramdisk") != 0) {
PLOG(ERROR) << "Failed to umount /debug_ramdisk";
}
}
如果設(shè)備未unlock,則卸載關(guān)于debug版本的/debug_ramdisk,讓屬性值讀取/ramdisk而不是/debug_ramdisk
3. SecondStageMain(int argc, char** argv)----->setup 2步驟
該步驟主要作用是初始化屬性值服務(wù),這里只是一個初始化的動作
// setup 2
PropertyInit();
- 初始化屬性服務(wù),獲取
system/build.prop
,vendor/build.prop
,/odm/build.prop
,/product/build.prop
,等其他build.prop并加載到properties map結(jié)構(gòu)中,然后會將該properties結(jié)構(gòu),通過MMAP映射到全局內(nèi)存中,供所有進(jìn)程調(diào)用,主要ro是只讀,只能夠?qū)懸淮危闯跏蓟瘯r給的一次值,注意build.prop有優(yōu)先級,product下的優(yōu)先級最高,因?yàn)槭莔ap結(jié)構(gòu),針對key相同的屬性值時,會覆蓋 -
PropertyInit
位于/system/core/init/property_service.cpp
void PropertyInit() {
...
// 建立屬性服務(wù)設(shè)備文件(linux思想,萬物皆文件系統(tǒng))
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
// 創(chuàng)建序列化過后的propertyInfo實(shí)體,主要就是讀取property_contexts文件
CreateSerializedPropertyInfo();
// 這里主要步驟是:通過mmap映射,將文件(/dev/__properties__/{..})映射進(jìn)內(nèi)存(初始化屬性內(nèi)存映射文件)
// 這里將文件映射進(jìn)內(nèi)存,用于后續(xù)對__prooerties__目錄(也可以說是文件,將代碼塊映射為file類型)進(jìn)行內(nèi)存共享
if (__system_property_area_init()) {
LOG(FATAL) << "Failed to initialize property area";
}
// 加載/dev/__properties__/property_info,此文件是序列化過的,無法直接查看內(nèi)容
if (!property_info_area.LoadDefaultPath()) {
LOG(FATAL) << "Failed to load serialized property info file";
}
// 讀取/proc/device-tree/firmware/android/目錄下的文件,生成ro.boot.xxx屬性值
// 這三個函數(shù)主要就是生成ro.boot.xx屬性值,這里不詳細(xì)研究
ProcessKernelDt();
ProcessKernelCmdline();
ProcessBootconfig();
// 初始化ro.xx,將ro.boot.xx的屬性值復(fù)制給ro.xxx
// { "ro.boot.serialno", "ro.serialno", UNSET, },
// { "ro.boot.mode", "ro.bootmode", "unknown", },
// { "ro.boot.baseband", "ro.baseband", "unknown", },
// { "ro.boot.bootloader", "ro.bootloader", "unknown", },
// { "ro.boot.hardware", "ro.hardware", "unknown", },
// { "ro.boot.revision", "ro.revision", "0", },
ExportKernelBootProps();
// 讀取{system/vendor/odm/product}/build.prop等...
// 將build.prop通過MMAP映射到全局內(nèi)存中,供所有進(jìn)程訪問
PropertyLoadBootDefaults();
調(diào)用__system_property_area_init
通過mmap映射,將文件(/dev/properties/{包含了上下文和propertys_info實(shí)體:保存了property_contexts文件內(nèi)容})映射進(jìn)內(nèi)存(初始化屬性內(nèi)存映射文件), 這里將文件映射進(jìn)內(nèi)存,用于后續(xù)對__prooerties__目錄(也可以說是文件,將代碼塊映射為file類型)進(jìn)行內(nèi)存共享
-
CreateSerializedPropertyInfo:
創(chuàng)建序列化過后的屬性值信息(既然序列化了,那肯定是要跨進(jìn)程通信)
(1).讀取{system_ext,vendor,product,odm,system}_property_contexts屬性值安全上下文并賦值給:property_infos
(2).property_infos屬于容器類型,讀取不同的property_contexts將會追加到末尾,而不是覆蓋原本內(nèi)容
(3).將property_infos實(shí)體序列化,使其可以跨進(jìn)程傳遞消息
(4).將property_infos實(shí)體寫入/dev/properties/property_info驅(qū)動節(jié)點(diǎn)中 -
PropertyLoadBootDefaults
這里會將屬性值全部寫入build.prop里,分為:system/build.prop,vendor/build.prop,/odm/build.prop,/product/build.prop,注意是有優(yōu)先級順序的,按先后順序覆蓋,獲取build.prop分別是直接從指定文件里獲取和從指定分區(qū)中獲取,這兩個方式作用都一樣,只不過第二種需要區(qū)分出分區(qū)里是否存在{partition}/{etc}/build.prop
,有的分區(qū)是不存在{partition}/etc/build.prop
這個文件,而是直接存在于{partition}/build.prop
一切都寫到了注釋里,繼續(xù)分析比較重要的2個函數(shù)CreateSerializedPropertyInfo
和PropertyLoadBootDefaults
3.1 CreateSerializedPropertyInfo
void CreateSerializedPropertyInfo() {
auto property_infos = std::vector<PropertyInfoEntry>();
//判斷文件是否存在,并判斷文件是否可寫(屬性服務(wù)的安全上下文,之前有提過設(shè)備節(jié)點(diǎn),服務(wù),屬性值都要遵守selinux規(guī)則)
if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
// 加載property_contexts文件,該文件內(nèi)容都是配置的屬性值上下文,屬于selinux相關(guān)知識
// 通過ParsePropertyInfoFile解析該文件,得到property_infos
if (!LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts",
&property_infos)) {
return;
}
// 如果這里system_ext/vendor/product沒有掛載上(例如在恢復(fù)的情況下,vendor分區(qū)將不會安裝),則無法繼續(xù)加載該上下文,該分區(qū)會在第一階段掛載
// 從下面的代碼可以看出來,property_infos的是容器類型vector<PropertyInfoEntry>()
// 所以這里并沒有優(yōu)先級也沒有以哪個property_contexts為準(zhǔn),而是根據(jù)是否存在對應(yīng)的分區(qū)而append加載
// 也就是在對應(yīng)后面追加內(nèi)容,而不是覆蓋:property_infos->emplace_back(property_info_entry);
if (access("/system_ext/etc/selinux/system_ext_property_contexts", R_OK) != -1) {
LoadPropertyInfoFromFile("/system_ext/etc/selinux/system_ext_property_contexts",
&property_infos);
}
if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
&property_infos)) {
// Fallback to nonplat_* if vendor_* doesn't exist.
LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",
&property_infos);
}
if (access("/product/etc/selinux/product_property_contexts", R_OK) != -1) {
LoadPropertyInfoFromFile("/product/etc/selinux/product_property_contexts",
&property_infos);
}
if (access("/odm/etc/selinux/odm_property_contexts", R_OK) != -1) {
LoadPropertyInfoFromFile("/odm/etc/selinux/odm_property_contexts", &property_infos);
}
// 若/system/etc/selinux/plat_property_contexts無法讀取,則else
} else {
// 由于system下的安全上下文未創(chuàng)建,則可能是system出現(xiàn)異常未掛載上,或者供應(yīng)商修改過plat_property_contexts
// 一般供應(yīng)商都是復(fù)寫,而不會直接更改文件名稱
// 若找不到該文件,則加載根目錄下的這些屬性值安全上下文
if (!LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) {
return;
}
LoadPropertyInfoFromFile("/system_ext_property_contexts", &property_infos);
if (!LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
// Fallback to nonplat_* if vendor_* doesn't exist.
LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
}
LoadPropertyInfoFromFile("/product_property_contexts", &property_infos);
LoadPropertyInfoFromFile("/odm_property_contexts", &property_infos);
}
// 序列化property_infos實(shí)體,使其可以跨進(jìn)程傳遞
auto serialized_contexts = std::string();
auto error = std::string();
if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
&error)) {
LOG(ERROR) << "Unable to serialize property contexts: " << error;
return;
}
// 將property_infos寫入/dev/__properties__/property_info設(shè)備文件中
constexpr static const char kPropertyInfosPath[] = "/dev/__properties__/property_info";
if (!WriteStringToFile(serialized_contexts, kPropertyInfosPath, 0444, 0, 0, false)) {
PLOG(ERROR) << "Unable to write serialized property infos to file";
}
selinux_android_restorecon(kPropertyInfosPath, 0);
}
以上的步驟都是對property_infos變量進(jìn)行賦值,數(shù)據(jù)就是property_contexts文件內(nèi)容,而property_infos是屬于vector<PropertyInfoEntry>()容器類型,而每次加載property_infos時都是調(diào)用的property_infos->emplace_back(property_info_entry);
在容器后面追加數(shù)據(jù),不會覆蓋原有的數(shù)據(jù)
讀取property_contexts文件內(nèi)容,將內(nèi)容傳遞給property_infos實(shí)體
3.2 PropertyLoadBootDefaults
void PropertyLoadBootDefaults() {
std::map<std::string, std::string> properties;
// 如果是恢復(fù)模式則加載/prop.default
if (IsRecoveryMode()) {
load_properties_from_file("/prop.default", nullptr, &properties);
}
// 這里還沒執(zhí)行,只是一個未執(zhí)行的代碼塊,從分區(qū)里讀取build.prop文件
const auto load_properties_from_partition = [&properties](const std::string& partition,
int support_legacy_path_until) {
// 加載{system_ext,product等分區(qū)}/etc/build.prop文件
// 以后代碼上獲取的屬性值就是從該文件中獲取的
auto path = "/" + partition + "/etc/build.prop";
if (load_properties_from_file(path.c_str(), nullptr, &properties)) {
return;
}
...
}
// 獲取第一階段生成的second_stage_resources/system/etc/ramdisk/build.prop
// 并追加到properties中(這里是map結(jié)構(gòu),注意會覆蓋內(nèi)容)
LoadPropertiesFromSecondStageRes(&properties);
// 先讀取的/system/build.prop,然后賦值給properties(這里是map結(jié)構(gòu),注意會覆蓋內(nèi)容)
load_properties_from_file("/system/build.prop", nullptr, &properties);
// 獲取/system_ext分區(qū)下的build.prop/default.prop,賦值給然后賦值給propertiess(這里是map結(jié)構(gòu),注意會覆蓋內(nèi)容)
load_properties_from_partition("system_ext", /* support_legacy_path_until */ 30);
// 繼續(xù)讀取的/vendor/default.prop,然后賦值給properties(這里是map結(jié)構(gòu),注意會覆蓋內(nèi)容)
load_properties_from_file("/vendor/default.prop", nullptr, &properties);
// }
// 繼續(xù)讀取的/vendor/build.prop,然后賦值給properties(這里是map結(jié)構(gòu),注意會覆蓋內(nèi)容)
load_properties_from_file("/vendor/build.prop", nullptr, &properties);
// 繼續(xù)讀取的/vendor_dlkm/etc/build.prop,然后賦值給properties(這里是map結(jié)構(gòu),注意會覆蓋內(nèi)容)
load_properties_from_file("/vendor_dlkm/etc/build.prop", nullptr, &properties);
// 繼續(xù)讀取的/odm_dlkm/etc/build.prop,然后賦值給properties(這里是map結(jié)構(gòu),注意會覆蓋內(nèi)容)
load_properties_from_file("/odm_dlkm/etc/build.prop", nullptr, &properties);
// 獲取/{odm,product}.prop/default.prop,賦值給然后賦值給propertiess(這里是map結(jié)構(gòu),注意會覆蓋內(nèi)容)
load_properties_from_partition("odm", /* support_legacy_path_until */ 28);
load_properties_from_partition("product", /* support_legacy_path_until */ 30);
// 因?yàn)閜ropertiess是map結(jié)構(gòu),如果key一樣則會覆蓋內(nèi)容,所以以上代碼的順序不能調(diào)換,優(yōu)先級最高的是/product下的build.prop
// /system->/system_ext->/vendor->/omd->/product
// 如果"/debug_ramdisk/adb_debug.prop"存在,說明設(shè)備已經(jīng)unlock過了,則加載unlock過后的屬性值,例如ro.debugger=1,則是開啟了調(diào)試模式
if (access(kDebugRamdiskProp, R_OK) == 0) {
LOG(INFO) << "Loading " << kDebugRamdiskProp;
load_properties_from_file(kDebugRamdiskProp, nullptr, &properties);
}
// 將從build.prop,default.prop獲取的properties,循環(huán)設(shè)置屬性值
// 這里是把.prop文件里的屬性值通過mmap映射到內(nèi)存中,使得所有進(jìn)程可以訪問(全局)
for (const auto& [name, value] : properties) {
std::string error;
// 如果是ro則是只讀,只能設(shè)置一次,再次設(shè)置會無效,如果存在相同的key,則會調(diào)用update更新
if (PropertySet(name, value, &error) != PROP_SUCCESS) {
LOG(ERROR) << "Could not set '" << name << "' to '" << value
<< "' while loading .prop files" << error;
}
}
...
// 設(shè)置persist.sys.usb.config屬性值來決定是否開啟調(diào)試模式(adb或none)
update_sys_usb_config();
}
以上代碼主要做的一個動作:讀取各個分區(qū)里的build.prop或直接從指定目錄下讀取build.prop并調(diào)用PropertySet
設(shè)置到全局內(nèi)存中,讓所有進(jìn)程訪問
主要的兩個函數(shù):load_properties_from_partition
和load_properties_from_file
,分別從分區(qū)里讀取和從指定文件讀取,為什么這么做呢?因?yàn)橛械姆謪^(qū)下是沒有/etc目錄的,無法直接指定文件位置,所以通過調(diào)用 "/" + partition + "/etc/build.prop"
來讀取,若不存在該文件則直接return。
注意這里讀取了{(lán)system,system_ext,vendor,vendor_dlkm,odm_dlkm,odm,product}/build.prop,由于properties屬于map結(jié)構(gòu),如果key相同是會覆蓋原有的值,所以這里是有優(yōu)先級排序的:
- /system/build.prop
- /system_ext/{etc}/build.prop
- /vendor/default.prop
- /vendor/build.prop
- /vendor_dlkm/etc/build.prop
- /odm_dlkm/etc/build.prop
- /odm/{etc}/build.prop
- /product/{etc}/build.prop
如果在system中自定義了屬性值,又在product自定義了一樣的屬性值,那么是以product為準(zhǔn)
這里只是init初始化過程,讀取build.prop并解析出來每一行屬性值并調(diào)用PropertySet
設(shè)置到全局內(nèi)存中
繼續(xù)分析一下PropertySet
函數(shù),該函數(shù)比較簡單
static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
...
// 找到該屬性值
prop_info* pi = (prop_info*) __system_property_find(name.c_str());
if (pi != nullptr) {
// 如果是以ro開頭則代表只讀,禁止寫入,返回error
if (StartsWith(name, "ro.")) {
*error = "Read-only property was already set";
return PROP_ERROR_READ_ONLY_PROPERTY;
}
// 若存在該屬性值且非ro 則更新屬性值
__system_property_update(pi, value.c_str(), valuelen);
} else {
// 若找不到該屬性值則新增
int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
if (rc < 0) {
*error = "__system_property_add failed";
return PROP_ERROR_SET_FAILED;
}
}
// 如果是以persist.開頭,則會全部寫進(jìn)/data/property/persistent_properties
// 屬于一個緩存機(jī)制
// std::string persistent_property_filename = "/data/property/persistent_properties";
if (persistent_properties_loaded && StartsWith(name, "persist.")) {
WritePersistentProperty(name, value);
}
...
return PROP_SUCCESS;
}
以上代碼就是若存在屬性值則更新,若不存在則新增。如果是ro開頭的屬性值則代表只讀,只能初始化的時候給默認(rèn)值,后續(xù)不允許修改值,如果是persist開頭的則會全部緩存進(jìn)/data/property/persistent_properties
,persist開頭的屬性值是可改的,如果用戶修改過了persist開頭的屬性值相當(dāng)于修改了/data/property/persistent_properties
里的屬性值,那么重啟后仍然生效,并不會還原默認(rèn)值。這樣即不影響屬性值的原子性(原有的屬性值),又給了開發(fā)者/用戶操作的空間,如果是刷機(jī)或恢復(fù)出廠則會還原
如果是系統(tǒng)開發(fā)者自定義了屬性值,但是發(fā)現(xiàn)默認(rèn)定義的時候?qū)傩灾禑o法寫入,則可能是property_contexts安全上下文影響,可以直接修改這里的代碼,__system_property_add
強(qiáng)行調(diào)用該方法即可
4. SecondStageMain(int argc, char** argv)----->setup 3步驟
setup 4
MountExtraFilesystems();
static void MountExtraFilesystems() {
#define CHECKCALL(x) \
if ((x) != 0) PLOG(FATAL) << #x " failed.";
// /apex is used to mount APEXes
CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
// /linkerconfig is used to keep generated linker configuration
CHECKCALL(mount("tmpfs", "/linkerconfig", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
#undef CHECKCALL
}
可以看到MountExtraFilesystems
主要就掛載了/apex和/linkerconfig并歸屬于tmpfs文件系統(tǒng)(運(yùn)行在內(nèi)存的文件系統(tǒng),運(yùn)行速度較快),這里主要探討一下APEX.
應(yīng)用程序可以通過更新APK來升級,供應(yīng)商客制化系統(tǒng)后可以通過OTA進(jìn)行系統(tǒng)升級,而針對google 開發(fā)者來修復(fù)原生的系統(tǒng)bug該如何更新呢?那就是通過google發(fā)布的安全patch和更新manline以及apex來解決
- 簡單點(diǎn)說apex為了解決性能而產(chǎn)生的機(jī)制,APK可以通過內(nèi)置升級,但系統(tǒng)升級可是個大問題
- apex可以將系統(tǒng)內(nèi)部的各個功能打包成模塊,然后針對這些模塊單獨(dú)升級
- apk是應(yīng)用程序的載體,對應(yīng)用開發(fā)者而言,可以apk方式對應(yīng)用功能進(jìn)行升級
- apex是系統(tǒng)功能的載體,對系統(tǒng)開發(fā)者(目前看主要是谷歌)而言,可以apex方式對系統(tǒng)功能進(jìn)行升級
- 一般是google開發(fā)者通過playstore發(fā)布,然后供我們下載更新,而對應(yīng)ODM第三方供應(yīng)商,則需要通過OTA升級
- apex相當(dāng)于對系統(tǒng)功能進(jìn)行了更細(xì)粒度的劃分,可以獨(dú)立升級這些功能,可以把a(bǔ)pex看成是一個一個的系統(tǒng)升級包
5. SecondStageMain(int argc, char** argv)----->setup 4步驟
// setup 4
StartPropertyService(&property_fd);
在setup2中初始化了property,獲取了build.prop和property_contexts并設(shè)置為內(nèi)存中全局共享
void StartPropertyService(int* epoll_socket) {
// 在init階段version=1,這里已經(jīng)升級到2了
InitPropertySet("ro.property_service.version", "2");
// 創(chuàng)建sockets,套接字,可以用于網(wǎng)絡(luò)通信,也可以用于本機(jī)內(nèi)的進(jìn)程通信
// socketpair()函數(shù)用于創(chuàng)建一對無名的,相互連接的套接字
// 如果函數(shù)創(chuàng)建成功,則返回0,創(chuàng)建好的套接字分別是sv[0]和sv[1];否則返回-1
int sockets[2];
// 參數(shù)1:表示協(xié)議族AF_UNIX
// 參數(shù)2:表示協(xié)議,SOCK_SEQPACKET提供連續(xù)可靠的數(shù)據(jù)包連接
// SOCK_CLOEXEC:當(dāng)文件描述符設(shè)置了O_CLOEXEC屬性后,在調(diào)用exec函數(shù)族時,文件描述符就會自動關(guān)閉,無需手動關(guān)閉
// 而SOCK_DGRAM是基于UDP的
// 參數(shù)3:表示類型,只能為0
// 參數(shù)4:套節(jié)字柄,該兩個句柄作用相同,均能進(jìn)行讀寫雙向操作
if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) {
PLOG(FATAL) << "Failed to socketpair() between property_service and init";
}
...
// PROP_SERVICE_NAME:/dev/socket/property_service,創(chuàng)建socket
if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, {});
result.ok()) {
property_set_fd = *result;
} else {
LOG(FATAL) << "start_property_service socket creation failed: " << result.error();
}
// 監(jiān)聽socket:/dev/socket/property_service,最大連接:8
listen(property_set_fd, 8);
// 開啟線程處理socket
auto new_thread = std::thread{PropertyServiceThread};
property_service_thread.swap(new_thread);
}
這里就是創(chuàng)建了socket,而屬性值服務(wù)的本質(zhì)就是一個socket(/dev/socket/property_service),最大連接為:8,持續(xù)等待連接,連接成功后決定是更新還是新增屬性值,仍然是調(diào)用的PropertySet
,繼續(xù)分析一下PropertyServiceThread
,該函數(shù)里調(diào)用了handle_property_set_fd
函數(shù),來看看具體實(shí)現(xiàn)
static void handle_property_set_fd() {
// 設(shè)置超時:2s
static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
// 可以設(shè)置四個參數(shù),所以用的是accept4,而當(dāng)啟用了SOCK_CLOEXEC參數(shù)后,進(jìn)程在調(diào)用exec函數(shù)族時,文件描述符就會自動關(guān)閉,無需手動關(guān)閉
int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
if (s == -1) {
return;
}
...
switch (cmd) {
case PROP_MSG_SETPROP: {
char prop_name[PROP_NAME_MAX];
char prop_value[PROP_VALUE_MAX];
...
prop_name[PROP_NAME_MAX-1] = 0;
prop_value[PROP_VALUE_MAX-1] = 0;
...
// 當(dāng)收到客戶端發(fā)來的請求,去更新或新增屬性值時會調(diào)用HandlePropertySet去處理
const auto& cr = socket.cred();
std::string error;
uint32_t result =
HandlePropertySet(prop_name, prop_value, source_context, cr, nullptr, &error);
if (result != PROP_SUCCESS) {
LOG(ERROR) << "Unable to set property '" << prop_name << "' from uid:" << cr.uid
<< " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
}
break;
}
...
}
}
可以看到設(shè)置了連接超時時間為2000ms,執(zhí)行 case PROP_MSG_SETPROP
,獲取屬性值的名稱和值,調(diào)用HandlePropertySet
里的PropertySet
進(jìn)行更新屬性值或新增。PropertySet
已經(jīng)分析過了
至此屬性值相關(guān)就分析完畢了,簡單的來說就是讀取build.prop屬性值共享到全局內(nèi)存中讓所有進(jìn)程獲取,然后創(chuàng)建屬性值服務(wù)(socket),持續(xù)監(jiān)聽客戶端(哪個進(jìn)程去調(diào)用更新屬性值就是當(dāng)前客戶端)發(fā)來的請求,最大只能同時受理8個來自客戶端的請求,若有客戶端請求則去更新屬性值或新增屬性值
6. SecondStageMain(int argc, char** argv)----->setup 5步驟
// setup 5
fs_mgr_vendor_overlay_mount_all();
這里的代碼只要是針對如果有vendor_overlay分區(qū),則覆蓋/vendor分區(qū),主要看供應(yīng)商的客制化
const std::vector<const std::string> kVendorOverlaySourceDirs = {
"/system/vendor_overlay/",
"/product/vendor_overlay/",
};
bool fs_mgr_vendor_overlay_mount_all() {
...
// 獲取 "/system/vendor_overlay/","/product/vendor_overlay/"下的所有子目錄
// 將vendor_overlay掛載到vendor上,若存在該覆蓋分區(qū)則會覆蓋之前的vendor分區(qū)
const auto vendor_overlay_dirs = fs_mgr_get_vendor_overlay_dirs(vndk_version);
if (vendor_overlay_dirs.empty()) return true;
if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kNotSupported) {
LINFO << "vendor overlay: kernel does not support overlayfs";
return false;
}
// Mount each directory in /(system|product)/vendor_overlay/<ver> on /vendor
// 掛載vendor_overlay到/vendor分區(qū)上
auto ret = true;
for (const auto& vendor_overlay_dir : vendor_overlay_dirs) {
if (!fs_mgr_vendor_overlay_mount(vendor_overlay_dir)) {
ret = false;
}
}
return ret;
}
7. SecondStageMain(int argc, char** argv)----->setup 6步驟
// setup 6
MountHandler mount_handler(&epoll);
該函數(shù)主要功能:持續(xù)監(jiān)控/proc/mounts
節(jié)點(diǎn)(fopen("/proc/mounts", "re"))
,主要是解析文件中每一行數(shù)據(jù),獲取掛載點(diǎn),掛載分區(qū),文件類型,權(quán)限等, 將解析內(nèi)容生成實(shí)體類追加到要掛載的mounts_中,主要就是對mounts文件解析,更新mounts_中的信息,掛載mounts信息里的分區(qū)
位置:/system/core/init/mount_handler.cpp
MountHandler::MountHandler(Epoll* epoll) : epoll_(epoll), fp_(fopen("/proc/mounts", "re"), fclose) {
if (!fp_) PLOG(FATAL) << "Could not open /proc/mounts";
auto result = epoll->RegisterHandler(
fileno(fp_.get()), [this]() { this->MountHandlerFunction(); }, EPOLLERR | EPOLLPRI);
if (!result.ok()) LOG(FATAL) << result.error();
}
繼續(xù)看一下MountHandlerFunction
函數(shù)
void MountHandler::MountHandlerFunction() {
rewind(fp_.get());
std::vector<MountHandlerEntry> touched;
auto untouched = mounts_; //容器類型
char* buf = nullptr;
size_t len = 0;
// 循環(huán)讀取文件內(nèi)容中的每一行
while (getline(&buf, &len, fp_.get()) != -1) {
auto buf_string = std::string(buf);
// /proc/mounts文件下存在一系列代碼
// 若讀取到/0/emulated則跳過
if (buf_string.find("/emulated") != std::string::npos) {
continue;
}
// 根據(jù)讀取的文件內(nèi)容,來解析分區(qū)以及device path,type等
auto entry = ParseMount(buf_string);
auto match = untouched.find(entry);
// 若這一行解析到底了仍然沒有匹配的信息,則這一條記錄追加到touched中
// entry:舉例--->對文件內(nèi)容/dev/block/dm-33 /data_mirror/data_ce/null ext4 解析過后的實(shí)體
if (match == untouched.end()) {
touched.emplace_back(std::move(entry));
} else {
// 若找到了匹配的信息則移除
untouched.erase(match);
}
}
free(buf);
// 將匹配到的entry進(jìn)行移除,并記錄Mount屬性值
for (auto& entry : untouched) {
SetMountProperty(entry, false);
mounts_.erase(entry);
}
// 將未匹配到的entry追加到mounts_,并記錄Mount屬性值
for (auto& entry : touched) {
SetMountProperty(entry, true);
// emplace是更具有性能的 更新或追加
mounts_.emplace(std::move(entry));
}
}
- 讀取/proc/mounts,解析文件中每一行數(shù)據(jù),獲取掛載點(diǎn),掛載分區(qū),文件類型,權(quán)限等
- 若解析到/0/emulated則跳過,不處理
- 根據(jù)mounts_,當(dāng)entry(解析后的內(nèi)容)可以在其中找到則移除
- 若entry未在mounts_找到,則追加到mounts_中
- 相當(dāng)于移除舊的entry,將新的entry追加到mounts_
再看看是如何解析文件內(nèi)容的auto entry = ParseMount(buf_string)
,解析文件內(nèi)容
MountHandlerEntry ParseMount(const std::string& line) {
auto fields = android::base::Split(line, " ");
while (fields.size() < 3) fields.emplace_back("");
if (fields[0] == "/dev/root") {
auto& dm = dm::DeviceMapper::Instance();
std::string path;
// 根據(jù)名稱獲取system分區(qū)目錄路徑,若根據(jù)名稱找不到則直接獲取根目錄/
// 若找到根目錄則繼續(xù)找/system,若找到則拿到device path
// 例如/system就是掛載在/dev/block/dm-1上,那么獲取的就是這個玩意
// /dev/block/dm-3 /vendor ext4 ro,seclabel,relatime 0 0
if (dm.GetDmDevicePathByName("system", &path) || dm.GetDmDevicePathByName("vroot", &path)) {
fields[0] = path;
} else if (android::fs_mgr::Fstab fstab; android::fs_mgr::ReadDefaultFstab(&fstab)) {
auto entry = GetEntryForMountPoint(&fstab, "/");
if (entry || (entry = GetEntryForMountPoint(&fstab, "/system"))) {
fields[0] = entry->blk_device;
}
}
}
// 獲取所有/dev目錄下的device
// readlink 是Linux系統(tǒng)中的一個常用命令,主要用來找出符號鏈接所指向的位置
// 也就是找到devcie path:/dev/block/dm-33
if (android::base::StartsWith(fields[0], "/dev/")) {
if (std::string link; android::base::Readlink(fields[0], &link)) {
fields[0] = link;
}
}
// fields0:/dev/block/dm-33(blk_device)
// fields1:掛載在device上的分區(qū)/文件路徑:/data_mirror/cur_profiles(mount_point)
// fields2:該分區(qū)的type類型,例如可能是ext4(fs_type)
return MountHandlerEntry(fields[0], fields[1], fields[2]);
}
讓我們再來看看這個文件內(nèi)容可以看出來第一列是分區(qū)掛載的位置,第二列是哪個分區(qū),第三列屬于分區(qū)格式
8. SecondStageMain(int argc, char** argv)----->setup 7步驟
// setup 7
// 創(chuàng)建ActionManager對象和ServiceList對象
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
// 解析rc文件
LoadBootScripts(am, sm);
- 加載rc文件,保存到action manager和service list中
- rc文件中:action 使用 ActionParser,而 service 使用 ServiceParser 解析
- 主要解析rc中的:service,on,Import,包含了zygote.rc,路徑:/system/bin/app_process64
- 在文件系統(tǒng)掛載的第一階段,system/vendor分區(qū)已經(jīng)成功掛載,而其它分區(qū)的掛載則通過rc來掛載
- rc中action的執(zhí)行順序:
on early-init
,on init
,on late-init
,on property
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
// 創(chuàng)建解析器,只解析init.rc文件中的service,on,Import類型
// action 使用 ActionParser,而 service 使用 ServiceParser 解析
Parser parser = CreateParser(action_manager, service_list);
// 獲取ro.boot.init_rc屬性值,此時該屬性值應(yīng)該是空的
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
// 解析/system/core/rootdir/init.rc
// 這里的路徑就是將/system/core/rootdir/init.rc 拷貝到out目錄下
parser.ParseConfig("/system/etc/init/hw/init.rc");
...
} else {
parser.ParseConfig(bootscript);
}
}
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
Parser parser;
parser.AddSectionParser("service", std::make_unique<ServiceParser>(
&service_list, GetSubcontext(), std::nullopt));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, GetSubcontext()));
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
return parser;
}
可以看到只需要解析init.rc文件中的service,on,import類型
9. SecondStageMain(int argc, char** argv)----->setup 8步驟
// setup 8
// 構(gòu)建action和觸發(fā)器(on early-init),放到event_queue,等待執(zhí)行函數(shù)
...
am.QueueEventTrigger("early-init");
// 構(gòu)建action和觸發(fā)器(on init),放到event_queue,等待執(zhí)行函數(shù)
am.QueueEventTrigger("init");
// 如果是充電模式則不需要掛載文件系統(tǒng)和不要啟動核心服務(wù)
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
// 運(yùn)行所有屬性觸發(fā)器(action),例如 on property
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
把a(bǔ)ction加入隊(duì)列中按順序依次執(zhí)行,繼續(xù)一下rc文件中做了什么動作, rc中action的執(zhí)行順序:on early-init
,on init
,on late-init
,on nonencrypted(啟動zygote)
,on property
9.1. on early-init
on early-init
...
mkdir /acct/uid
# 掛載linkerconfig(動態(tài)鏈接器)
mount none /linkerconfig/bootstrap /linkerconfig bind rec
# 啟動ueventd(位于/system/bin/ueventd),ueventd是init啟動的第一個進(jìn)程
start ueventd
# 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
mkdir /dev/memcg/system 0550 system system
mkdir /dev/net 0755 root root
symlink ../tun /dev/net/tun
...
# 掛載tracefs,可以通過指定方式到處trace日志,分析CPU和內(nèi)存相關(guān)等問題
mount tracefs tracefs /sys/kernel/tracing gid=3012
# create sys dirctory
# 創(chuàng)建/sys目錄并指定權(quán)限
mkdir /dev/sys 0755 system system
mkdir /dev/sys/fs 0755 system system
mkdir /dev/sys/block 0755 system system
可以看到針對lmkd(Low Memory Killer Daemon)
以及app 創(chuàng)建用戶組,創(chuàng)建目錄,掛載tracefs:可以通過指定方式到處trace日志,分析CPU和內(nèi)存相關(guān)等問題。
第一個啟動的核心服務(wù)是:start ueventd
,位于/system/bin/ueventd,ueventd是init啟動的第一個服務(wù)進(jìn)程
9.2. on init
on init
...
chmod 0775 /dev/cpuset/system-background
chmod 0664 /dev/cpuset/foreground/tasks
chmod 0664 /dev/cpuset/background/tasks
chmod 0664 /dev/cpuset/system-background/tasks
chmod 0664 /dev/cpuset/top-app/tasks
chmod 0664 /dev/cpuset/restricted/tasks
chmod 0664 /dev/cpuset/tasks
chmod 0664 /dev/cpuset/camera-daemon/tasks
# 掛載bpf
mount bpf bpf /sys/fs/bpf nodev noexec nosuid
mkdir /dev/fscklogs 0770 root system
...
# 允許system組讀寫電源狀態(tài)
chown system system /sys/power/state
chown system system /sys/power/wakeup_count
chmod 0660 /sys/power/state
...
# 在運(yùn)行其他進(jìn)程之前需要先啟動log服務(wù),說明init中啟動的服務(wù),第一個啟動的進(jìn)程是ueventd
start logd
# 啟用 Low Memory Killer Daemon(lmkd)
# 1.基于Memory的CGroup進(jìn)行進(jìn)程的回收;2.作為frameworks與kernel的溝通橋梁傳遞參數(shù)與信息
# Start lmkd before any other services run so that it can register them
chown root system /sys/module/lowmemorykiller/parameters/adj
chmod 0664 /sys/module/lowmemorykiller/parameters/adj
chown root system /sys/module/lowmemorykiller/parameters/minfree
chmod 0664 /sys/module/lowmemorykiller/parameters/minfree
start lmkd
# Start essential services.
# 啟用ServiceManager,管理各個服務(wù),非常重要
start servicemanager
start hwservicemanager
start vndservicemanager
可以看到第二個啟動的核心服務(wù)是:start logd
,日志系統(tǒng)。
第三個核心服務(wù)是:start lmkd
,Low Memory Killer Daemon
作用:基于Memory的CGroup進(jìn)行進(jìn)程的回收,作為frameworks與kernel的溝通橋梁傳遞參數(shù)與信息。
接著啟動了:servicemanager
,hwservicemanager
,vndservicemanager
,這些都屬于核心服務(wù)
若核心服務(wù)未啟動成功,那么其他服務(wù)將無法啟動,系統(tǒng)將無法啟動,其他服務(wù)必須依賴核心服務(wù)
9.3. on late-init
# 裝載文件系統(tǒng)并啟動核心系統(tǒng)服務(wù)
on late-init
trigger early-fs
# 觸發(fā)on fs和on post-fs
trigger fs
trigger post-fs
trigger late-fs
trigger post-fs-data
trigger load_persist_props_action
trigger load_bpf_programs
trigger zygote-start
trigger firmware_mounts_complete
trigger early-boot
trigger boot
可以看到調(diào)用順序?yàn)椋簡酉到y(tǒng)on late-init會先執(zhí)行,然后繼續(xù)觸發(fā)on fs,on post-fs ,on late-fs,on zygote-start ,on boot等,在調(diào)用on zygote-start
后會解析zygote服務(wù)并指定class 名稱,然后加入服務(wù)管理隊(duì)列,后續(xù)等待調(diào)用on nonencrypted
來啟動zygote服務(wù)
9.4. on nonencrypted
on nonencrypted
class_start main
class_start late_start
目錄:/system/core/rootdir/init.zygote64.rc
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
# class : 給服務(wù)指定一個類屬
class main
priority -20
# user 在執(zhí)行此服務(wù)之前先切換用戶名。當(dāng)前默認(rèn)為root.
user root
# 切換組名
group root readproc reserved_disk
# 在/dev/socket/下創(chuàng)建一個socket,并傳遞創(chuàng)建的文件描述符fd給服務(wù)進(jìn)程
# 其中type必須為dgram或stream,seqpacket.用戶名和組名默認(rèn)為0
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
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
# oneshot : 當(dāng)此服務(wù)退出時不會自動重啟.
# disabled:服務(wù)不會自動運(yùn)行,必須顯式地通過服務(wù)器來啟動
# 據(jù)設(shè)備相關(guān)的關(guān)鍵服務(wù),如果在4分鐘內(nèi),此服務(wù)重復(fù)啟動了4次,那么設(shè)備將會重啟進(jìn)入還原模式。
critical window=${zygote.critical_window.minute:-off} target=zygote-fatal
可以看到通過class_start main來啟動主函數(shù)main,位于:frameworks/base/cmds/app_process/app_main.cpp
int main(int argc, char* const argv[])
{
...
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.");
}
}
這里啟動了zygote,并且攜帶參數(shù)啟動了systemServer,關(guān)于zygote這里就不再詳細(xì)分析了
二. 附錄
rc文件中的command以及觸發(fā)器,action等,對應(yīng)的關(guān)系如下:文章來源:http://www.zghlxwxcb.cn/news/detail-423299.html
static const BuiltinFunctionMap builtin_functions = {
{"bootchart", {1, 1, {false, do_bootchart}}},
{"chmod", {2, 2, {true, do_chmod}}},
{"chown", {2, 3, {true, do_chown}}},
{"class_reset", {1, 1, {false, do_class_reset}}},
{"class_reset_post_data", {1, 1, {false, do_class_reset_post_data}}},
{"class_restart", {1, 1, {false, do_class_restart}}},
{"class_start", {1, 1, {false, do_class_start}}},
{"class_start_post_data", {1, 1, {false, do_class_start_post_data}}},
{"class_stop", {1, 1, {false, do_class_stop}}},
{"copy", {2, 2, {true, do_copy}}},
{"copy_per_line", {2, 2, {true, do_copy_per_line}}},
{"domainname", {1, 1, {true, do_domainname}}},
{"enable", {1, 1, {false, do_enable}}},
{"exec", {1, kMax, {false, do_exec}}},
{"exec_background", {1, kMax, {false, do_exec_background}}},
{"exec_start", {1, 1, {false, do_exec_start}}},
{"export", {2, 2, {false, do_export}}},
{"hostname", {1, 1, {true, do_hostname}}},
{"ifup", {1, 1, {true, do_ifup}}},
{"init_user0", {0, 0, {false, do_init_user0}}},
{"insmod", {1, kMax, {true, do_insmod}}},
{"installkey", {1, 1, {false, do_installkey}}},
{"interface_restart", {1, 1, {false, do_interface_restart}}},
{"interface_start", {1, 1, {false, do_interface_start}}},
{"interface_stop", {1, 1, {false, do_interface_stop}}},
{"load_exports", {1, 1, {false, do_load_exports}}},
{"load_persist_props", {0, 0, {false, do_load_persist_props}}},
{"load_system_props", {0, 0, {false, do_load_system_props}}},
{"loglevel", {1, 1, {false, do_loglevel}}},
{"mark_post_data", {0, 0, {false, do_mark_post_data}}},
{"mkdir", {1, 6, {true, do_mkdir}}},
// TODO: Do mount operations in vendor_init.
// mount_all is currently too complex to run in vendor_init as it queues action triggers,
// imports rc scripts, etc. It should be simplified and run in vendor_init context.
// mount and umount are run in the same context as mount_all for symmetry.
{"mount_all", {0, kMax, {false, do_mount_all}}},
{"mount", {3, kMax, {false, do_mount}}},
{"perform_apex_config", {0, 0, {false, do_perform_apex_config}}},
{"umount", {1, 1, {false, do_umount}}},
{"umount_all", {0, 1, {false, do_umount_all}}},
{"update_linker_config", {0, 0, {false, do_update_linker_config}}},
{"readahead", {1, 2, {true, do_readahead}}},
{"remount_userdata", {0, 0, {false, do_remount_userdata}}},
{"restart", {1, 1, {false, do_restart}}},
{"restorecon", {1, kMax, {true, do_restorecon}}},
{"restorecon_recursive", {1, kMax, {true, do_restorecon_recursive}}},
{"rm", {1, 1, {true, do_rm}}},
{"rmdir", {1, 1, {true, do_rmdir}}},
{"setprop", {2, 2, {true, do_setprop}}},
{"setrlimit", {3, 3, {false, do_setrlimit}}},
{"start", {1, 1, {false, do_start}}},
{"stop", {1, 1, {false, do_stop}}},
{"swapon_all", {0, 1, {false, do_swapon_all}}},
{"enter_default_mount_ns", {0, 0, {false, do_enter_default_mount_ns}}},
{"symlink", {2, 2, {true, do_symlink}}},
{"sysclktz", {1, 1, {false, do_sysclktz}}},
{"trigger", {1, 1, {false, do_trigger}}},
{"verity_update_state", {0, 0, {false, do_verity_update_state}}},
{"wait", {1, 2, {true, do_wait}}},
{"wait_for_prop", {2, 2, {false, do_wait_for_prop}}},
{"write", {2, 2, {true, do_write}}},
};
三. 總結(jié)
至此第二階段就分析完畢了,一句話來總結(jié):優(yōu)先保證init進(jìn)程的存活率(拉高優(yōu)先級),處理設(shè)備是否unlock,決定是否卸載一些分區(qū)以及調(diào)整selinux規(guī)則和權(quán)限,創(chuàng)建并啟動屬性服務(wù)(實(shí)質(zhì)上就是把屬性值映射到全局內(nèi)存中供所有進(jìn)程訪問,然后在創(chuàng)建socket等待進(jìn)程來連接 實(shí)現(xiàn)更新和新增屬性值),繼續(xù)決定是否把vendor_overlay覆蓋到/vendor分區(qū)中,然后持續(xù)監(jiān)控/proc/mounts,如果有分區(qū)信息加入到該文件中則掛載此分區(qū),接著解析rc文件(創(chuàng)建目錄,修改權(quán)限,掛載分區(qū),啟動服務(wù)進(jìn)程等),根據(jù)調(diào)用順序啟動核心服務(wù)(adbd,ueventd等)以及主服務(wù)(zygote)和其他服務(wù)
文章來源地址http://www.zghlxwxcb.cn/news/detail-423299.html
到了這里,關(guān)于基于Android13的系統(tǒng)啟動流程分析(四)之SecondStageMain階段的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!