之前遇到一个有关“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 ' ' 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 }