之前遇到一個有關“androidboot.slot_suffix”的問題,發現該kernel cmdline作為后綴會被用來拼接位於fastab中的分區名(如vendor、oem分區等)。如果該cmdline的值傳遞不正確會使fstab中的分區掛載點出錯,導致無法正確掛載分區。通過閱讀源碼定位到更新fstab中分區后綴的代碼位於/
system/
core/
fs_mgr/
fs_mgr_boot_config.cpp中,其代碼實現為:
40 // Updates |fstab| for slot_suffix. Returns true on success, false on error. 41 bool fs_mgr_update_for_slotselect(struct fstab *fstab) { 42 int n; 43 std::string ab_suffix; 44 45 for (n = 0; n < fstab->num_entries; n++) { 46 if (fstab->recs[n].fs_mgr_flags & MF_SLOTSELECT) { 47 char *tmp; 48 if (ab_suffix.empty()) { 49 ab_suffix = fs_mgr_get_slot_suffix(); 50 // Returns false as non A/B devices should not have MF_SLOTSELECT. 51 if (ab_suffix.empty()) return false; 52 } 53 if (asprintf(&tmp, "%s%s", fstab->recs[n].blk_device, ab_suffix.c_str()) > 0) { 54 free(fstab->recs[n].blk_device); 55 fstab->recs[n].blk_device = tmp; 56 } else { 57 return false; 58 } 59 } 60 } 61 return true;
上述代碼首先通過fatab中的參數判斷分區是否需要進行分區選擇,之后通過
fs_mgr_get_slot_suffix()獲取當前所啟動的slot后綴並完成拼接。
fs_mgr_get_slot_suffix()中主要調用了
fs_mgr_get_boot_config()來完成分區后綴的獲取功能,其中
fs_mgr_get_boot_config()的代碼實現為:
24 // Tries to get the boot config value in properties, kernel cmdline and 25 // device tree (in that order). returns 'true' if successfully found, 'false' 26 // otherwise 27 bool fs_mgr_get_boot_config(const std::string& key, std::string* out_val) { 28 FS_MGR_CHECK(out_val != nullptr); 29 30 // first check if we have "ro.boot" property already 31 *out_val = android::base::GetProperty("ro.boot." + key, ""); 32 if (!out_val->empty()) { 33 return true; 34 35 } 36 37 // fallback to kernel cmdline, properties may not be ready yet 38 std::string cmdline; 39 std::string cmdline_key("androidboot." + key); 40 if (android::base::ReadFileToString("/proc/cmdline", &cmdline)) { 41 for (const auto& entry : android::base::Split(android::base::Trim(cmdline), " ")) { 42 std::vector<std::string> pieces = android::base::Split(entry, "="); 43 if (pieces.size() == 2) { 44 if (pieces[0] == cmdline_key) { 45 *out_val = pieces[1]; 46 return true; 47 } 48 } 49 } 50 } 51 52 // lastly, check the device tree 53 if (is_dt_compatible()) { 54 std::string file_name = kAndroidDtDir + "/" + key; 55 // DT entries terminate with '\0' but so do the properties 56 if (android::base::ReadFileToString(file_name, out_val)) { 57 return true; 58 } 59 60 LINFO << "Error finding '" << key << "' in device tree"; 61 } 62 63 return false;
獲取分區后綴的先后次序為:property > kernel cmdline > dt。通過測試發現系統一般是獲取ro.slot_suffix這個property值,並且ro.slot_suffix這個property也是通過androidboot.slot_suffix所轉換得到的。繼續閱讀源碼,定位到在init進程中有一處調用
process_kernel_cmdline(),繼續追蹤發現其內部調用了
import_kernel_cmdline()並傳入了
import_kernel_nv()回調函數。其中
import_kernel_cmdline()的實現為:
275 void import_kernel_cmdline(bool in_qemu, 276 const std::function<void(const std::string&, const std::string&, bool)>& fn) { 277 std::string cmdline; 278 android::base::ReadFileToString("/proc/cmdline", &cmdline); 279 280 for (const auto& entry : android::base::Split(android::base::Trim(cmdline), " ")) { 281 std::vector<std::string> pieces = android::base::Split(entry, "="); 282 if (pieces.size() == 2) { 283 fn(pieces[0], pieces[1], in_qemu); 284 } 285 } 286 }
上述代碼的功能是將傳入的cmdline進行分割,並將其傳入
import_kernel_nv()中進行轉換。
import_kernel_nv()的代碼實現為:
440 static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) { 441 if (key.empty()) return; 442 443 if (for_emulator) { 444 // In the emulator, export any kernel option with the "ro.kernel." prefix. 445 property_set(StringPrintf("ro.kernel.%s", key.c_str()).c_str(), value.c_str()); 446 return; 447 } 448 449 if (key == "qemu") { 450 strlcpy(qemu, value.c_str(), sizeof(qemu)); 451 } else if (android::base::StartsWith(key, "androidboot.")) { 452 property_set(StringPrintf("ro.boot.%s", key.c_str() + 12).c_str(), value.c_str()); 453 } 454 }
