Android P update_engine分析(三) --boot_control的操作
上篇update_engine的啟動時,有看到關于boot_control的初始化,我們知道boot_control是切換AB分區的關鍵實現者,那這篇就來專門介紹boot_control, 從boot_control的啟動與初始化,boot_control針對配置的讀寫操作,以及最后boot_control的AB切換 這三個小模塊來分析。
boot_control的初始化
我們接著上篇的boot_control的初始化開始分析, boot_control::CreateBootControl()的實現:
std::unique_ptr<BootControlInterface> CreateBootControl() {std::unique_ptr<BootControlAndroid> boot_control(new BootControlAndroid());if (!boot_control->Init()) {... } // namespace boot_controlbool BootControlAndroid::Init() {module_ = IBootControl::getService();... }首先是new了一個BootControlAndroid的實現,然后調用Init()函數,這個就是通IBootControl::getService(), 這個是標準的HAL的獲取服務句柄的寫法。那看看IBootControl的服務的啟動。
具體HAL服務路徑:hardware/interface/boot/1.0
HAL服務也是標準的HIDL流程,通過android.hardware.boot@1.0-service.rc 來啟動,但它還需要加載boot_control_module_t來實現其實際的操作。
BootControl相當于是對boot_control_module_t 加了一層封裝的服務,實現實現部分還是得看boot_control_module_t的實現。因為每個芯片平臺實現的方法不一樣。我們就找標準qcom的實現方式,路徑為:hardware/qcom/bootctrl.
看看module的接口與定義:
上面是所有boot_control 對外實現的接口,我們來看看boot_control_init:
void boot_control_init(struct boot_control_module *module) {if (!module) {ALOGE("Invalid argument passed to %s", __func__);return;}return; }boot_control_init 里沒有做什么事情,那這里boot_control 初始化就分析完了。
高通平臺boot_control針對配置的讀寫操作
boot_control 對AB分區的實際保存與讀寫,最具有代表性的函數是setActiveBootSlot, 那我們就從這個函數入手,在上面HAL_MODULE_INFO_SYM 里可以看出 setActiveBootSlot實際調用的是set_active_boot_slot:
int set_active_boot_slot(struct boot_control_module *module, unsigned slot) {map<string, vector<string>> ptn_map;vector<string> ptn_vec;const char ptn_list[][MAX_GPT_NAME_SIZE] = { AB_PTN_LIST };uint32_t i;int rc = -1;//先判斷是否是ufsint is_ufs = gpt_utils_is_ufs_device();map<string, vector<string>>::iterator map_iter;vector<string>::iterator string_iter;//檢查slot是否合法,是否屬于A/B 對應的slotif (boot_control_check_slot_sanity(module, slot)) {ALOGE("%s: Bad arguments", __func__);goto error;}//遍歷ptn_list里的分區列表,將需要更改的分區都加入到此ptn_vec列表中for (i = 0; i < ARRAY_SIZE(ptn_list); i++) {//XBL is handled differrently for ufs devices so ignore itif (is_ufs && !strncmp(ptn_list[i], PTN_XBL, strlen(PTN_XBL)))continue;//The partition list will be the list of _a partitionsstring cur_ptn = ptn_list[i];cur_ptn.append(AB_SLOT_A_SUFFIX);ptn_vec.push_back(cur_ptn);}//獲取真正存儲設備里的真實分區列表放在ptn_map中if (gpt_utils_get_partition_map(ptn_vec, ptn_map)) {ALOGE("%s: Failed to get partition map",__func__);goto error;}//遍歷ptn_map中的所有分區項,將其設置為slot項。for (map_iter = ptn_map.begin(); map_iter != ptn_map.end(); map_iter++){if (map_iter->second.size() < 1)continue;if (boot_ctl_set_active_slot_for_partitions(map_iter->second, slot)) {ALOGE("%s: Failed to set active slot for partitions ", __func__);;goto error;}}//如果是ufs,需要特殊處理,在xbl中設置為slot為啟動項。if (is_ufs) {if (!strncmp(slot_suffix_arr[slot], AB_SLOT_A_SUFFIX,strlen(AB_SLOT_A_SUFFIX))){//Set xbl_a as the boot lunrc = gpt_utils_set_xbl_boot_partition(NORMAL_BOOT);} else if (!strncmp(slot_suffix_arr[slot], AB_SLOT_B_SUFFIX,strlen(AB_SLOT_B_SUFFIX))){//Set xbl_b as the boot lunrc = gpt_utils_set_xbl_boot_partition(BACKUP_BOOT);...}return 0; error:return -1; }通過上面注釋的解釋,我們發現最重要切換分區的項是boot_ctl_set_active_slot_for_partitions 和 gpt_utils_set_xbl_boot_partition。
static int boot_ctl_set_active_slot_for_partitions(vector<string> part_list,unsigned slot) {char buf[PATH_MAX] = {0};struct gpt_disk *disk = NULL;char slotA[MAX_GPT_NAME_SIZE + 1] = {0};char slotB[MAX_GPT_NAME_SIZE + 1] = {0};char active_guid[TYPE_GUID_SIZE + 1] = {0};char inactive_guid[TYPE_GUID_SIZE + 1] = {0};//Pointer to the partition entry of current 'A' partitionuint8_t *pentryA = NULL;uint8_t *pentryA_bak = NULL;//Pointer to partition entry of current 'B' partitionuint8_t *pentryB = NULL;uint8_t *pentryB_bak = NULL;struct stat st;vector<string>::iterator partition_iterator;for (partition_iterator = part_list.begin();partition_iterator != part_list.end();partition_iterator++) {//Chop off the slot suffix from the partition name to//make the string easier to work with.string prefix = *partition_iterator;if (prefix.size() < (strlen(AB_SLOT_A_SUFFIX) + 1)) {ALOGE("Invalid partition name: %s", prefix.c_str());goto error;}prefix.resize(prefix.size() - strlen(AB_SLOT_A_SUFFIX));//檢查AB 分區對應的塊設備是否存在snprintf(buf, sizeof(buf) - 1, "%s/%s%s", BOOT_DEV_DIR,prefix.c_str(),AB_SLOT_A_SUFFIX);if (stat(buf, &st))continue;memset(buf, '\0', sizeof(buf));snprintf(buf, sizeof(buf) - 1, "%s/%s%s", BOOT_DEV_DIR,prefix.c_str(),AB_SLOT_B_SUFFIX);if (stat(buf, &st))continue;//設置slotA slotB的全名 ,類似boot_a, boot_bmemset(slotA, 0, sizeof(slotA));memset(slotB, 0, sizeof(slotA));snprintf(slotA, sizeof(slotA) - 1, "%s%s", prefix.c_str(),AB_SLOT_A_SUFFIX);snprintf(slotB, sizeof(slotB) - 1,"%s%s", prefix.c_str(),AB_SLOT_B_SUFFIX);//獲取磁盤的分區表信息if (!disk) {disk = boot_ctl_get_disk_info(slotA);if (!disk)goto error;}//qcom里分區表里有兩塊分區表信息,在磁盤頭尾分別一塊,為primary_GPT和SECONDARY_GPT 兩個分區表信息,分別保存在pentryA/B 和 pentryA/B_bak中pentryA = gpt_disk_get_pentry(disk, slotA, PRIMARY_GPT);pentryA_bak = gpt_disk_get_pentry(disk, slotA, SECONDARY_GPT);pentryB = gpt_disk_get_pentry(disk, slotB, PRIMARY_GPT);pentryB_bak = gpt_disk_get_pentry(disk, slotB, SECONDARY_GPT);if ( !pentryA || !pentryA_bak || !pentryB || !pentryB_bak) {//None of these should be NULL since we have already//checked for A & B versions earlier.ALOGE("Slot pentries for %s not found.",prefix.c_str());goto error;}//將當前激活的guid 和 非激活的guid 分別存儲在active_guid和 inactive_guid中。memset(active_guid, '\0', sizeof(active_guid));memset(inactive_guid, '\0', sizeof(inactive_guid));if (get_partition_attribute(slotA, ATTR_SLOT_ACTIVE) == 1) {//A is the current active slotmemcpy((void*)active_guid, (const void*)pentryA,TYPE_GUID_SIZE);memcpy((void*)inactive_guid,(const void*)pentryB,TYPE_GUID_SIZE);} else if (get_partition_attribute(slotB,ATTR_SLOT_ACTIVE) == 1) {//B is the current active slotmemcpy((void*)active_guid, (const void*)pentryB,TYPE_GUID_SIZE);memcpy((void*)inactive_guid, (const void*)pentryA,TYPE_GUID_SIZE);} else {ALOGE("Both A & B are inactive..Aborting");goto error;}//更新slot為最新的激活slotif (!strncmp(slot_suffix_arr[slot], AB_SLOT_A_SUFFIX,strlen(AB_SLOT_A_SUFFIX))){//Mark A as active in primary tableUPDATE_SLOT(pentryA, active_guid, SLOT_ACTIVE);//Mark A as active in backup tableUPDATE_SLOT(pentryA_bak, active_guid, SLOT_ACTIVE);//Mark B as inactive in primary tableUPDATE_SLOT(pentryB, inactive_guid, SLOT_INACTIVE);//Mark B as inactive in backup tableUPDATE_SLOT(pentryB_bak, inactive_guid, SLOT_INACTIVE);} else if (!strncmp(slot_suffix_arr[slot], AB_SLOT_B_SUFFIX,strlen(AB_SLOT_B_SUFFIX))){//Mark B as active in primary tableUPDATE_SLOT(pentryB, active_guid, SLOT_ACTIVE);//Mark B as active in backup tableUPDATE_SLOT(pentryB_bak, active_guid, SLOT_ACTIVE);//Mark A as inavtive in primary tableUPDATE_SLOT(pentryA, inactive_guid, SLOT_INACTIVE);//Mark A as inactive in backup tableUPDATE_SLOT(pentryA_bak, inactive_guid, SLOT_INACTIVE);} else {//Something has gone terribly terribly wrongALOGE("%s: Unknown slot suffix!", __func__);goto error;}//更新分區表信息的CRC信息if (disk) {if (gpt_disk_update_crc(disk) != 0) {ALOGE("%s: Failed to update gpt_disk crc",__func__);goto error;}}}//將信息寫入磁盤信息中if (disk) {if (gpt_disk_commit(disk)) {ALOGE("Failed to commit disk entry");goto error;}gpt_disk_free(disk);}return 0;error:if (disk)gpt_disk_free(disk);return -1; }從上面的注釋中可以看到,首先根據分區名如boot_a, 獲取到對應分區表信息,高通的分區表信息是有兩份的,一份在磁盤的第二塊上,另一份在磁盤的最后一塊上,做為備份分區表信息。通過更新分區表信息中的FLAG 為 SLOT_ACTIVE 或 SLOT_INACTIVE 來設置分區為激活狀態,還是非激活狀態。最后將disk分區表信息寫回到磁盤上。這樣就完成了分區表的更新。
標準google平臺boot_control針對配置的讀寫操作
從上面高通平臺來看,主要是基于GPT分區表去修改,設置flag的方式,讀取GPT表信息來來配置從boot_control信息。那我們來看看從標準平臺是如果配置boot_control信息的。還是從setActiveBootSlot來分析:
int BootControl_setActiveBootSlot(boot_control_module_t* module, unsigned int slot) {//獲取bootctl_module的接口boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) {// Invalid slot number.return -1;}bootloader_control bootctrl;//從misc分區中讀取當前bootctrl的信息if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;// 如果是激活分區,就將Priorty設置為15,如果是非激活分區,就將優先級設置為14.const unsigned int kActivePriority = 15;const unsigned int kActiveTries = 6;for (unsigned int i = 0; i < bootctrl_module->num_slots; ++i) {if (i != slot) {if (bootctrl.slot_info[i].priority >= kActivePriority)bootctrl.slot_info[i].priority = kActivePriority - 1;}}// Note that setting a slot as active doesn't change the successful bit.// The successful bit will only be changed by setSlotAsUnbootable().bootctrl.slot_info[slot].priority = kActivePriority;bootctrl.slot_info[slot].tries_remaining = kActiveTries;// Setting the current slot as active is a way to revert the operation that// set *another* slot as active at the end of an updater. This is commonly// used to cancel the pending update. We should only reset the verity_corrpted// bit when attempting a new slot, otherwise the verity bit on the current// slot would be flip.if (slot != bootctrl_module->current_slot) bootctrl.slot_info[slot].verity_corrupted = 0;//然后將更新后的bootctrl信息更新回misc分區中if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;return 0; }struct slot_metadata {// Slot priority with 15 meaning highest priority, 1 lowest// priority and 0 the slot is unbootable.uint8_t priority : 4;// Number of times left attempting to boot this slot.uint8_t tries_remaining : 3;// 1 if this slot has booted successfully, 0 otherwise.uint8_t successful_boot : 1;// 1 if this slot is corrupted from a dm-verity corruption, 0// otherwise.uint8_t verity_corrupted : 1;// Reserved for further use.uint8_t reserved : 7; } __attribute__((packed));struct bootloader_control {// NUL terminated active slot suffix.char slot_suffix[4];// Bootloader Control AB magic number (see BOOT_CTRL_MAGIC).uint32_t magic;// Version of struct being used (see BOOT_CTRL_VERSION).uint8_t version;// Number of slots being managed.uint8_t nb_slot : 3;// Number of times left attempting to boot recovery.uint8_t recovery_tries_remaining : 3;// Ensure 4-bytes alignment for slot_info field.uint8_t reserved0[2];// Per-slot information. Up to 4 slots.struct slot_metadata slot_info[4];// Reserved for further use.uint8_t reserved1[8];// CRC32 of all 28 bytes preceding this field (little endian// format).uint32_t crc32_le; } __attribute__((packed));從上面的可以看到,google標準平臺就是將bootloader_control 的結構體信息保存在misc分區中,通過讀寫這塊信息,來配置當前slot信息。
總結來說,bootcontrol 每家芯片實現的方式有些許差異。但都是適配boot_control的HAL層接口。
總結
以上是生活随笔為你收集整理的Android P update_engine分析(三) --boot_control的操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 语句摘抄——第18周
- 下一篇: 详谈Activity生命周期函数调用时机