A/B系统是Google推的一种OTA升级方法,就是为了方便,简单说就是多了个备份区,升级时写备份区然后尝试从备份区启动,如果启动ok,就把备份区作为主区,遥想当年ROS平台bootloader就有这么个东东。

这里不关心OTA流程,我们关心的是A/B系统分区选择相关,A/B下刷机要不要带_a, _b。

ok,先让我们看下官方对分区选择说明:

A/B system updates use two sets of partitions referred to as slots (normally slot A and slot B). The system runs from the current slot while the partitions in the unused slot are not accessed by the running system during normal operation.

Each slot has a bootable attribute that states whether the slot contains a correct system from which the device can boot.

Each slot also has a successful attribute set by the user space, which is relevant only if the slot is also bootable. A successful slot should be able to boot, run, and update itself. A bootable slot that was not marked as successful (after several attempts were made to boot from it) should be marked as unbootable by the bootloader, including changing the active slot to another bootable slot (normally to the slot running immediately before the attempt to boot into the new, active one).

高通在LK下对A/B支持的文件是: ab_partition_parser.c,来看下当fastboot刷机时LK是如何解析的:

/*
* Find index of parition in array of partition entries
*/
int partition_get_index(const char *name)
{
unsigned int input_string_length = strlen(name);
unsigned n;
int curr_slot = INVALID;
const char *suffix_curr_actv_slot = NULL;
char *curr_suffix = NULL;

if( partition_count >= NUM_PARTITIONS)
{
return INVALID_PTN;
}

/* We iterate through the parition entries list,
to find the partition with active slot suffix.
*/
for (n = 0; n < partition_count; n++)
{
if (!strncmp((const char*)name, (const char *)partition_entries[n].name,
input_string_length))
{
curr_suffix = (char *)(partition_entries[n].name+input_string_length);

/* if partition_entries.name is NULL terminated return the index */
if (*curr_suffix == '\0')
return n; // tj: 完全一致名称

if (partition_multislot_is_supported())
{
curr_slot = partition_find_active_slot();

/* If suffix string matches with current active slot suffix return index */
if (curr_slot != INVALID)
{
suffix_curr_actv_slot = SUFFIX_SLOT(curr_slot);
if (!strncmp((const char *)curr_suffix, suffix_curr_actv_slot,
strlen(suffix_curr_actv_slot)))
return n;
else
continue;
}
else
{
/* No valid active slot */
return INVALID_PTN;
}
}
}
}
return INVALID_PTN;
}

首先要在分区表里找到这个name,如果完全一致那就是这个分区,对有ab分区的,找到active slot:

/*
This function returns the most priority and active slot,
also you need to update the global state seperately.

*/
int partition_find_active_slot()
{
...
for (boot_priority = MAX_PRIORITY;
boot_priority > 0; boot_priority--)
{
/* Search valid boot slot with highest priority */
for (i = 0; i < AB_SUPPORTED_SLOTS; i++)
{
current_priority = slot_priority(partition_entries, boot_slot_index[i]);
current_active_bit = slot_is_active(partition_entries, boot_slot_index[i]);
current_bootable_bit = slot_is_bootable(partition_entries, boot_slot_index[i]);

/* Count number of slots with all attributes as zero */
if ( !current_priority &&
!current_active_bit &&
current_bootable_bit)
{
count ++;
continue;
}

如上,忽略non active, non bootable, non priority的slot,对首次烧录都是non的给个default: SLOT_A

/* All slots are zeroed, this is first bootup */
/* Marking and trying SLOT 0 as default */
if (count == AB_SUPPORTED_SLOTS)
{
/* Update the priority of the boot slot */
partition_activate_slot(SLOT_A);

active_slot = SLOT_A;

/* This is required to mark all bits as active,
for fresh boot post fresh flash */
mark_all_partitions_active(active_slot);
goto out;
}

回到partition_get_index,它会返回这个active slot的partition index。

也就是说,如果我们敲入了flash system,其实烧录的就是system active slot。唯一例外就是boot分区:

int cmd_flash_mmc_img(const char *arg, void *data, unsigned sz)
{
...
index = partition_get_index(pname);
ptn = partition_get_offset(index);
if(ptn == 0) {
fastboot_fail("partition table doesn't exist");
return -1;
}

if (!strncmp(pname, "boot", strlen("boot"))
|| !strcmp(pname, "recovery"))
{
if (memcmp((void *)data, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {
fastboot_fail("image is not a boot image");
return -1;
}

/* Reset multislot_partition attributes in case of flashing boot */
if (partition_multislot_is_supported())
{
partition_reset_attributes(index);
}
}

注释写的很清楚,如果要烧boot,就要复位这个active slot分区的属性:

/*
Function: To reset partition attributes
This function reset partition_priority, retry_count
and clear successful and bootable bits.
*/
void partition_reset_attributes(unsigned index)
{
struct partition_entry *partition_entries =
partition_get_partition_entries();

partition_entries[index].attribute_flag |= (PART_ATT_PRIORITY_VAL |
PART_ATT_MAX_RETRY_COUNT_VAL);

partition_entries[index].attribute_flag &= ((~PART_ATT_SUCCESSFUL_VAL) &
(~PART_ATT_UNBOOTABLE_VAL));

if (!attributes_updated)
attributes_updated = true;

/* Make attributes persistant */
partition_mark_active_slot(active_slot);
}

可见把分区表里这个index的分区属性全部clear了,比如变成了non bootable,non successful,这样在前面的partition_find_active_slot就找不到这个active slot, 比如当前active slot是slot a, 敲入flash boot后,boot_a内容更新了但是不再是active了。此时fastboot reboot到底走不走boot_a要看boot_b是不是active,所以保险起见,还是boot_a[b]都烧入,rt?

有人会问如何判断系统用了A/B,我想有一种方法就是在fastboot看下:

D:\>fastboot getvar all
...
(bootloader) slot-count:2
(bootloader) current-slot:a
(bootloader) slot-retry-count:b:0
(bootloader) slot-success:b:No
(bootloader) slot-active:b:No
(bootloader) slot-unbootable:b:No
(bootloader) slot-retry-count:a:6
(bootloader) slot-success:a:Yes
(bootloader) slot-active:a:Yes
(bootloader) slot-unbootable:a:No
(bootloader) has-slot:userdata: No
(bootloader) has-slot:vendor: Yes
(bootloader) has-slot:system: Yes
...

Google对刷机时间的说明:

Does flashing two system partitions increase factory flashing time?
No. Pixel didn’t increase in system image size (it merely divided the space across two partitions).

so,出厂刷机没必要_a/_b都刷,rt?