Android A/B System OTA分析(三)主系统和bootloader的通信_ota android a/b-程序员宅基地

技术标签: bootloader  android  ota  Android A/B 系统  Android  

Android从7.0开始引入新的OTA升级方式,A/B System Updates,这里将其叫做A/B系统,涉及的内容较多,分多篇对A/B系统的各个方面进行分析。本文为第三篇,主系统和bootloader的通信。

本文为洛奇看世界(guyongqiangx)原创,转载请注明出处。
文章链接:https://blog.csdn.net/guyongqiangx/article/details/72480154

Android A/B 系统基础入门系列《Android A/B 系统》已完结,文章列表:

更多关于 Android OTA 升级相关文章,请参考《Android OTA 升级系列专栏文章导读》

本文基于AOSP 7.1.1_r23 (NMF27D)代码进行分析。

1. 传统系统的沟通机制

传统方式中,Android主系统同bootloaderrecovery系统通过存放于misc分区的bootloader_message结构进行通信。

struct bootloader_message {
	char command[32];
	char status[32];
	char recovery[768];

	/* The 'recovery' field used to be 1024 bytes.  It has only ever
	 * been used to store the recovery command line, so 768 bytes
	 * should be plenty.  We carve off the last 256 bytes to store the
	 * stage string (for multistage packages) and possible future
	 * expansion.*/
	char stage[32];
	char reserved[224];
};
  1. android系统或recovery系统根据操作更新bootloader_messagecommand成员,并写入misc分区;

  2. bootloader启动后读取misc分区并解析得到bootloader_message,根据command内容选择相应的操作,command可能的内容包括:

    • "update-radio/hboot"
      • 指示bootloader更新firmware
    • "boot-recovery"
      • 指示bootloader加载recovery系统,进入recovery mode

2. A/B系统的沟通机制

2.1 boot_control的接口定义

A/B系统中,指定了用于通信的HAL层boot_control功能的定义,但没有指定通信数据具体的存储实现,这点有别于传统方式下AOSP定义的存储于misc分区的bootloader_message结构。

HAL层的boot_control,其定义位于文件中hardware/libhardware/include/hardware/boot_control.h

/**
 * Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
 * and the fields of this data structure must begin with hw_module_t
 * followed by module specific information.
 */
typedef struct boot_control_module {
    struct hw_module_t common;

    /*
     * (*init)() perform any initialization tasks needed for the HAL.
     * This is called only once.
     */
	/* init 用于HAL初始化调用,仅启动时调用一次 */
    void (*init)(struct boot_control_module *module);

    /*
     * (*getNumberSlots)() returns the number of available slots.
     * For instance, a system with a single set of partitions would return
     * 1, a system with A/B would return 2, A/B/C -> 3...
     */
	/* 返回系统slot分区套数,1套slot包含boot, system和vendor分区 */
    unsigned (*getNumberSlots)(struct boot_control_module *module);

    /*
     * (*getCurrentSlot)() returns the value letting the system know
     * whether the current slot is A or B. The meaning of A and B is
     * left up to the implementer. It is assumed that if the current slot
     * is A, then the block devices underlying B can be accessed directly
     * without any risk of corruption.
     * The returned value is always guaranteed to be strictly less than the
     * value returned by getNumberSlots. Slots start at 0 and
     * finish at getNumberSlots() - 1
     */
	/* 返回系统当前所在的slot位置 */
    unsigned (*getCurrentSlot)(struct boot_control_module *module);

    /*
     * (*markBootSuccessful)() marks the current slot
     * as having booted successfully
     *
     * Returns 0 on success, -errno on error.
     */
	/* 标记当前slot为已经成功启动 */
    int (*markBootSuccessful)(struct boot_control_module *module);

    /*
     * (*setActiveBootSlot)() marks the slot passed in parameter as
     * the active boot slot (see getCurrentSlot for an explanation
     * of the "slot" parameter). This overrides any previous call to
     * setSlotAsUnbootable.
     * Returns 0 on success, -errno on error.
     */
	/* 标记指定slot为可启动 */
    int (*setActiveBootSlot)(struct boot_control_module *module, unsigned slot);

    /*
     * (*setSlotAsUnbootable)() marks the slot passed in parameter as
     * an unbootable. This can be used while updating the contents of the slot's
     * partitions, so that the system will not attempt to boot a known bad set up.
     * Returns 0 on success, -errno on error.
     */
	/* 标记指定slot为不可启动 */
    int (*setSlotAsUnbootable)(struct boot_control_module *module, unsigned slot);

    /*
     * (*isSlotBootable)() returns if the slot passed in parameter is
     * bootable. Note that slots can be made unbootable by both the
     * bootloader and by the OS using setSlotAsUnbootable.
     * Returns 1 if the slot is bootable, 0 if it's not, and -errno on
     * error.
     */
	/* 返回指定slot是否可启动 */
    int (*isSlotBootable)(struct boot_control_module *module, unsigned slot);

    /*
     * (*getSuffix)() returns the string suffix used by partitions that
     * correspond to the slot number passed in parameter. The returned string
     * is expected to be statically allocated and not need to be freed.
     * Returns NULL if slot does not match an existing slot.
     */
	/* 返回指定slot的系统分区后缀,例如“_a”/“_b”等 */
    const char* (*getSuffix)(struct boot_control_module *module, unsigned slot);

    /*
     * (*isSlotMarkedSucessful)() returns if the slot passed in parameter has
     * been marked as successful using markBootSuccessful.
     * Returns 1 if the slot has been marked as successful, 0 if it's
     * not the case, and -errno on error.
     */
	/* 返回指定slot是否已经标记为成功启动 */
    int (*isSlotMarkedSuccessful)(struct boot_control_module *module, unsigned slot);

    void* reserved[31];
} boot_control_module_t;

2.2 boot_control的存储和功能实现

对于boot_controlAOSP仅定义了其功能接口,并没有提供具体的代码实现,各厂家根据这个头文件,自定义其存储和功能实现。

使用grep工具搜索代码中的boot_control关键字,可以发现AOSP代码里面包含了三个平台的boot_control实现:

  • Google平台的Brillo
  • Intel平台的edison
  • QualComm
2.2.1 Google平台Brillo的实现

AOSP代码中,system\extra\boot_control_copy 定义了bootctrl.default实现:

$ ls -lh system/extras/boot_control_copy/
total 36K
-rw-r--r-- 1 ygu users  458 Mar 31 08:50 Android.mk
-rw-r--r-- 1 ygu users  11K Mar 31 08:50 NOTICE
-rw-r--r-- 1 ygu users 7.7K Mar 31 08:50 boot_control_copy.c
-rw-r--r-- 1 ygu users 5.1K Mar 31 08:50 bootinfo.c
-rw-r--r-- 1 ygu users 2.0K Mar 31 08:50 bootinfo.h

各文件的内容如下:

  • bootinfo.h定义了结构体BrilloSlotInfoBrilloBootInfo

    BrilloBootInfo包含结构体BrilloBootInfo,作为boot_control的私有数据实现,定义如下:

      typedef struct BrilloSlotInfo {
        uint8_t bootable : 1;
        uint8_t reserved[3];
      } BrilloSlotInfo;
      
      typedef struct BrilloBootInfo {
        // Used by fs_mgr. Must be NUL terminated.
        char bootctrl_suffix[4];
      
        // Magic for identification - must be 'B', 'C', 'c' (short for
        // "boot_control copy" implementation).
        uint8_t magic[3];
      
        // Version of BrilloBootInfo struct, must be 0 or larger.
        uint8_t version;
      
        // Currently active slot.
        uint8_t active_slot;
      
        // Information about each slot.
        BrilloSlotInfo slot_info[2];
      
        uint8_t reserved[15];
      } BrilloBootInfo;
    

    结构体BrilloBootInfo占用32字节,系统复用misc分区的bootloader_message结构体,将BrilloBootInfo存放在偏移量为864字节的成员slot_suffix[32]中,整个misc分区数据结构的框图如下:
    boot_control implementation on Google Brillo

  • bootinfo.c实现了对BrilloBootInfo进行存取操作的接口

    • 存取操作
      • bool boot_info_load(BrilloBootInfo *out_info)
      • bool boot_info_save(BrilloBootInfo *info)
    • 校验和复位操作
      • bool boot_info_validate(BrilloBootInfo* info)
      • void boot_info_reset(BrilloBootInfo* info)
    • 指定分区的打开操作
      • int boot_info_open_partition(const char *name, uint64_t *out_size, int flags)
  • boot_control_copy.c实现了boot_control模块的功能

      /* This boot_control HAL implementation emulates A/B by copying the
       * contents of the boot partition of the requested slot to the boot
       * partition. It hence works with bootloaders that are not yet aware
       * of A/B. This code is only intended to be used for development.
       */
      boot_control_module_t HAL_MODULE_INFO_SYM = {
        .common = {
          .tag                 = HARDWARE_MODULE_TAG,
          .module_api_version  = BOOT_CONTROL_MODULE_API_VERSION_0_1,
          .hal_api_version     = HARDWARE_HAL_API_VERSION,
          .id                  = BOOT_CONTROL_HARDWARE_MODULE_ID,
          .name                = "Copy Implementation of boot_control HAL",
          .author              = "The Android Open Source Project",
          .methods             = &module_methods,
        },
        .init                 = module_init,
        .getNumberSlots       = module_getNumberSlots,
        .getCurrentSlot       = module_getCurrentSlot,
        .markBootSuccessful   = module_markBootSuccessful,
        .setActiveBootSlot    = module_setActiveBootSlot,
        .setSlotAsUnbootable  = module_setSlotAsUnbootable,
        .isSlotBootable       = module_isSlotBootable,
        .getSuffix            = module_getSuffix,
      };
    

代码实现了boot_control_module_t模块接口的功能,这里不再对每一个函数实现进行注释,但需要特别指出的是,函数module_setActiveBootSlot内部会根据传入的slot参数将对应分区boot_X内容复制到boot分区(系统上应该存在三个分区,如bootboot_aboot_b),bootloader不需要改动代码去检查到底是从哪个分区启动,只管加载boot分区就好了,带来的问题就是,一旦启动失败(例如,kernel挂载system分区失败,根本没有进入Android环境),bootloader无法切换到另外一个slot。注释中也提到,这种方式不需要修改bootloader,其代码实现只是用于开发目的,最终产品不应该是这样的。

2.2.2 Intel平台edison的实现

AOSP代码中,hardware\bsp\intel\soc\common\bootctrl定义了bootctrl.edison的实现:

$ ls -lh hardware/bsp/intel/soc/common/bootctrl/
total 20K
-rw-r--r-- 1 ygu users  860 Mar 31 08:47 Android.mk
-rw-r--r-- 1 ygu users 9.1K Mar 31 08:47 bootctrl.c
-rw-r--r-- 1 ygu users 1.5K Mar 31 08:47 bootctrl.h

各文件的内容如下:

  • bootctrl.h定义了结构体slot_metadata_tboot_ctrl_t

    boot_ctrl_t包含结构体slot_metadata_t,作为boot_control的私有数据实现,定义如下:

      #define BOOT_CONTROL_VERSION    1
      
      typedef struct slot_metadata {
          uint8_t priority : 4;
          uint8_t tries_remaining : 3;
          uint8_t successful_boot : 1;
      } slot_metadata_t;
      
      typedef struct boot_ctrl {
          /* Magic for identification - '\0ABB' (Boot Contrl Magic) */
          uint32_t magic;
      
          /* Version of struct. */
          uint8_t version;
      
          /* Information about each slot. */
          slot_metadata_t slot_info[2];
      
          uint8_t recovery_tries_remaining;
      } boot_ctrl_t;
    

    Brillo类似,系统复用misc分区的bootloader_message结构体,将boot_ctrl_t存放在偏移量为864字节的成员slot_suffix[32]中,整个misc分区数据结构的框图如下:

在这里插入图片描述

  • bootctrl.c实现了boot_ctrl_t存取操作和boot_control的模块功能
    • boot_ctrl_t存取操作

      • int bootctrl_read_metadata(boot_ctrl_t *bctrl)
      • int bootctrl_write_metadata(boot_ctrl_t *bctrl)
    • boot_control模块功能

      	/* Boot Control Module implementation */
      	boot_control_module_t HAL_MODULE_INFO_SYM = {
      	    .common = {
      	        .tag                 = HARDWARE_MODULE_TAG,
      	        .module_api_version  = BOOT_CONTROL_MODULE_API_VERSION_0_1,
      	        .hal_api_version     = HARDWARE_HAL_API_VERSION,
      	        .id                  = BOOT_CONTROL_HARDWARE_MODULE_ID,
      	        .name                = "boot_control HAL",
      	        .author              = "Intel Corporation",
      	        .methods             = &bootctrl_methods,
      	    },
      	    .init                 = bootctrl_init,
      	    .getNumberSlots       = bootctrl_get_number_slots,
      	    .getCurrentSlot       = bootctrl_get_current_slot,
      	    .markBootSuccessful   = bootctrl_mark_boot_successful,
      	    .setActiveBootSlot    = bootctrl_set_active_boot_slot,
      	    .setSlotAsUnbootable  = bootctrl_set_slot_as_unbootable,
      	    .isSlotBootable       = bootctrl_is_slot_bootable,
      	    .getSuffix            = bootctrl_get_suffix,
      	};
      

由于没有bootloader的代码,所以对于如何通过结构体slot_metadata_t的成员prioritypriority来选择启动哪一个slot并不清楚,无法对结构体成员的作用有更详细的说明。

值得一提的是,通过读取linux命令行参数androidboot.slot_suffix=来确定当前系统在哪一个slot上运行(见bootctrl_get_active_slot函数)。

2.2.3 QualComm平台的实现

AOSP代码中,hardware\qcom\bootctrl定义了bootctrl.$(TARGET_BOARD_PLATFORM)的实现(具体名字依赖于TARGET_BOARD_PLATFORM变量设定):

$ ls -lh hardware/qcom/bootctrl/
total 28K
-rw-r--r-- 1 ygu users  944 Mar 31 08:47 Android.mk
-rw-r--r-- 1 ygu users 1.5K Mar 31 08:47 NOTICE
-rw-r--r-- 1 ygu users  19K Mar 31 08:47 boot_control.cpp

QualComm平台的实现比较特别,没有单独定义boot_control的私有数据,而是将A/B系统相关信息存放到gpt表上。
GPT内容的第3个逻辑块LBA 2开始,依次存放的是每个GPT分区的详细信息Partition Entry,单个Partition Entry占用128个字节,从其第48个字节开始存放的是分区属性(Attribute flags)。A/B系统将每个slot分区的信息,存放到分区属性的Bit 48开始的位置上。

QualComm平台详细的A/B系统分区属性如下:
QualCommA/B`系统分区属性

关于GPT分区的详细信息,可以参考另外一篇文章:<<博通机顶盒平台GPT分区和制作工具>>的第1部分,关于GPT的介绍。

在代码实现中比较特别的是:

  • 统计系统中boot开头的分区数作为slot总数(见get_number_slots函数)
  • 访问系统的属性ro.boot.slot_suffix来确定当前系统在哪一个slot上运行(见get_current_slot函数)
2.3.4 Broadcom机顶盒平台的实现

Broadcom单独提供的代码中(非AOSP代码),vendor/broadcom/bcm_platform/hals/boot_control定义了bootctrl.$(TARGET_BOARD_PLATFORM)的实现(如bootctrl.bcm7252ssffdr4):

$ ls -lh vendor/broadcom/bcm_platform/hals/boot_control/
total 20K
-rw-r--r-- 1 ygu users 1.3K Mar 30 16:09 Android.mk
-rw-r--r-- 1 ygu users  11K May  6 16:26 boot_control.cpp
-rw-r--r-- 1 ygu users 1.1K Mar 30 16:09 eio_boot.h
  • eio_boot.h定义了结构体eio_boot_sloteio_boot

    eio_boot包含结构体eio_boot_slot,作为boot_control的私有数据实现,定义如下:

      struct eio_boot_slot {
         char suffix[8];
         int  valid;
         int  boot_try;
         int  boot_ok;
         int  boot_fail;
      };
      
      struct eio_boot {
         int    magic;
         int    current;
         struct eio_boot_slot slot[2];
      };
    

    结构体eio_boot的数据存放在名为eio的分区上。
    Broadcom机顶盒平台eio_boot结构框图如下:
    Broadcomeio_boot`结构框图

  • boot_control.cpp实现了eio_boot存取操作和boot_control的模块功能

      struct boot_control_module HAL_MODULE_INFO_SYM = {
         .common = {
            .tag                = HARDWARE_MODULE_TAG,
            .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1,
            .hal_api_version    = HARDWARE_HAL_API_VERSION,
            .id                 = BOOT_CONTROL_HARDWARE_MODULE_ID,
            .name               = "boot control hal for bcm platform",
            .author             = "Broadcom",
            .methods            = &boot_control_module_methods,
            .dso                = 0,
            .reserved           = {0}
          },
          .init                    = init,
          .getNumberSlots          = getNumberSlots,
          .getCurrentSlot          = getCurrentSlot,
          .markBootSuccessful      = markBootSuccessful,
          .setActiveBootSlot       = setActiveBootSlot,
          .setSlotAsUnbootable     = setSlotAsUnbootable,
          .isSlotBootable          = isSlotBootable,
          .getSuffix               = getSuffix,
          .isSlotMarkedSuccessful  = isSlotMarkedSuccessful,
      };
    

    Broadcom平台的分区后缀名不同于常见的_a/_b,而是采用_i/_e,这里略去对函数内容的注释。

2.3 boot_control的测试工具

除了定义HAL层的接口外,AOSP也提供了boot_control模块调用的工具bootctl,位于:
system/extras/bootctl/bootctl.c

默认情况下,bootctl不会参与编译,可以在包含update_engine是将其添加到PRODUCT_PACKAGES,如下:

PRODUCT_PACKAGES += \
  update_engine \
  update_verifier \
  bootctl

bootctl工具很简单,通过命令行调用boot_control的功能接口,以下是在Broadcom参考平台上运行bootctl的例子:

bcm7252ssffdr4:/ $ su
bcm7252ssffdr4:/ # which bootctl
/system/bin/bootctl
bcm7252ssffdr4:/ # bootctl --help
bootctl - command-line wrapper for the boot_control HAL.

Usage:
  bootctl COMMAND

Commands:
  bootctl hal-info                       - Show info about boot_control HAL used.
  bootctl get-number-slots               - Prints number of slots.
  bootctl get-current-slot               - Prints currently running SLOT.
  bootctl mark-boot-successful           - Mark current slot as GOOD.
  bootctl set-active-boot-slot SLOT      - On next boot, load and execute SLOT.
  bootctl set-slot-as-unbootable SLOT    - Mark SLOT as invalid.
  bootctl is-slot-bootable SLOT          - Returns 0 only if SLOT is bootable.
  bootctl is-slot-marked-successful SLOT - Returns 0 only if SLOT is marked GOOD.
  bootctl get-suffix SLOT                - Prints suffix for SLOT.

SLOT parameter is the zero-based slot-number.
64|bcm7252ssffdr4:/ # 
64|bcm7252ssffdr4:/ # bootctl hal-info
HAL name:            boot control hal for bcm platform
HAL author:          Broadcom
HAL module version:  0.1
bcm7252ssffdr4:/ # bootctl get-number-slots
2
bcm7252ssffdr4:/ # bootctl get-current-slot
0
bcm7252ssffdr4:/ # bootctl get-suffix 0
_i
bcm7252ssffdr4:/ # bootctl get-suffix 1
_e

最后的bootctl get-suffix调用可以看到,在我的测试平台上,slot Aslot B的分区命名后缀分别为_i_e

基于bootctl的基础上,Android系统提供了两个基于Brillo平台的测试代码,分别位于以下路径:

  • system/extras/tests/bootloader
  • external/autotest/server/site_tests/brillo_BootLoader

后续打算写一篇博客来单独介绍如何在Android下运行这些测试例子进行单元测试。

2.4 boot_control的调用

2.4.1 bootloader读取boot_control私有实现的数据

设备启动后bootloader会读取boot_control私有实现的数据,来判断从哪一个slot启动,由于各家实现的私有数据结构不一样,所以无法详细说明如何解析和处理的过程。

2.4.1 boot_control_android调用boot_control

文件system/update_engine/boot_control_android.cc中,类BootControlAndroid有一个私有成员module_

// The Android implementation of the BootControlInterface. This implementation
// uses the libhardware's boot_control HAL to access the bootloader.
class BootControlAndroid : public BootControlInterface {
 ...

 private:
  // NOTE: There is no way to release/unload HAL implementations so
  // this is essentially leaked on object destruction.
  boot_control_module_t* module_;

  ...
};

BootControlAndroidInit方法内,获取boot_control_module_t模块指针并赋值给module_成员,然后调用module_->init进行boot_control的初始化,如下:

bool BootControlAndroid::Init() {
  const hw_module_t* hw_module;
  int ret;

#ifdef _UE_SIDELOAD
  // For update_engine_sideload, we simulate the hw_get_module() by accessing it
  // from the current process directly.
  # 对于update_engine_sideload应用,直接将HAL_MODULE_INFO_SYM转换为hw_module
  hw_module = &HAL_MODULE_INFO_SYM;
  ret = 0;
  if (!hw_module ||
      strcmp(BOOT_CONTROL_HARDWARE_MODULE_ID, hw_module->id) != 0) {
    ret = -EINVAL;
  }
#else  // !_UE_SIDELOAD
  # 对于update_engine应用,通过BOOT_CONTROL_HARDWARE_MODULE_ID获取hw_module
  ret = hw_get_module(BOOT_CONTROL_HARDWARE_MODULE_ID, &hw_module);
#endif  // _UE_SIDELOAD
  if (ret != 0) {
    LOG(ERROR) << "Error loading boot_control HAL implementation.";
    return false;
  }

  # 通过hw_module得到boot_control_module_t,从而后面可以愉快地调用其各种功能实现函数
  module_ = reinterpret_cast<boot_control_module_t*>(const_cast<hw_module_t*>(hw_module));
  # 调用boot_control的init函数
  module_->init(module_);

  LOG(INFO) << "Loaded boot_control HAL "
            << "'" << hw_module->name << "' "
            << "version " << (hw_module->module_api_version>>8) << "."
            << (hw_module->module_api_version&0xff) << " "
            << "authored by '" << hw_module->author << "'.";
  return true;
}

初始化完成后,就可以通过module_成员来调用各种boot_control的操作了。

2.4.2 update_verifier调用boot_control

文件bootable/recovery/update_verifier/update_verifier.cpp中,获取boot_control_module_t指针,检查当前slot分区是否已经标记为successful,如果没有,则尝试verify_image并将当前slot标记为successful,具体代码如下:

int main(int argc, char** argv) {
  ...

  # 直接根据名称"bootctrl"获取模块
  const hw_module_t* hw_module;
  if (hw_get_module("bootctrl", &hw_module) != 0) {
    SLOGE("Error getting bootctrl module.\n");
    return -1;
  }

  # 将"bootctrl"模块转化为"boot_control_module_t"结构体
  boot_control_module_t* module = reinterpret_cast<boot_control_module_t*>(
      const_cast<hw_module_t*>(hw_module));
  # 调用init
  module->init(module);

  # 获取当前slot
  unsigned current_slot = module->getCurrentSlot(module);
  # 检查当前slot是否标记为successful
  int is_successful= module->isSlotMarkedSuccessful(module, current_slot);
  SLOGI("Booting slot %u: isSlotMarkedSuccessful=%d\n", current_slot, is_successful);
  # 如果当前slot没有标记为successful,说明当前启动可能存在问题
  if (is_successful == 0) {
    // The current slot has not booted successfully.
	# 检查"ro.boot.verifymode",是否其它原因导致失败
	# 不是其它原因导致失败的情况下,重新调用verify_image验证
    ...

	# verify_image验证成功,尝试标记当前slot为successful
    int ret = module->markBootSuccessful(module);
    if (ret != 0) {
      SLOGE("Error marking booted successfully: %s\n", strerror(-ret));
      return -1;
    }
    SLOGI("Marked slot %u as booted successfully.\n", current_slot);
  }

  # 完成操作,退出update_verifier
  SLOGI("Leaving update_verifier.\n");
  return 0;
}

整个A/B系统中,基于boot_control的上层应用操作已经实现了,各家需要单独实现boot_control的底层操作,同时bootloader也需要配合解析boot_control的私有数据,从而选择相应的slot来启动Android系统。

3. 其它

到目前为止,我写过 Android OTA 升级相关的话题包括:

  • 基础入门:《Android A/B 系统》系列
  • 核心模块:《Android Update Engine 分析》 系列
  • 动态分区:《Android 动态分区》 系列
  • 虚拟 A/B:《Android 虚拟 A/B 分区》系列
  • 升级工具:《Android OTA 相关工具》系列

更多这些关于 Android OTA 升级相关文章的内容,请参考《Android OTA 升级系列专栏文章导读》

如果您已经订阅了动态分区和虚拟分区付费专栏,请务必加我微信,备注订阅账号,拉您进“动态分区 & 虚拟分区专栏 VIP 答疑群”。我会在方便的时候,回答大家关于 A/B 系统、动态分区、虚拟分区、各种 OTA 升级和签名的问题。

除此之外,我有一个 Android OTA 升级讨论群,里面现在有 400+ 朋友,主要讨论手机,车机,电视,机顶盒,平板等各种设备的 OTA 升级话题,如果您从事 OTA 升级工作,欢迎加群一起交流,请在加我微信时注明“Android OTA 讨论组”。此群仅限 Android OTA 开发者参与~

公众号“洛奇看世界”后台回复“wx”获取个人微信。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/guyongqiangx/article/details/72480154

智能推荐

稀疏编码的数学基础与理论分析-程序员宅基地

文章浏览阅读290次,点赞8次,收藏10次。1.背景介绍稀疏编码是一种用于处理稀疏数据的编码技术,其主要应用于信息传输、存储和处理等领域。稀疏数据是指数据中大部分元素为零或近似于零的数据,例如文本、图像、音频、视频等。稀疏编码的核心思想是将稀疏数据表示为非零元素和它们对应的位置信息,从而减少存储空间和计算复杂度。稀疏编码的研究起源于1990年代,随着大数据时代的到来,稀疏编码技术的应用范围和影响力不断扩大。目前,稀疏编码已经成为计算...

EasyGBS国标流媒体服务器GB28181国标方案安装使用文档-程序员宅基地

文章浏览阅读217次。EasyGBS - GB28181 国标方案安装使用文档下载安装包下载,正式使用需商业授权, 功能一致在线演示在线API架构图EasySIPCMSSIP 中心信令服务, 单节点, 自带一个 Redis Server, 随 EasySIPCMS 自启动, 不需要手动运行EasySIPSMSSIP 流媒体服务, 根..._easygbs-windows-2.6.0-23042316使用文档

【Web】记录巅峰极客2023 BabyURL题目复现——Jackson原生链_原生jackson 反序列化链子-程序员宅基地

文章浏览阅读1.2k次,点赞27次,收藏7次。2023巅峰极客 BabyURL之前AliyunCTF Bypassit I这题考查了这样一条链子:其实就是Jackson的原生反序列化利用今天复现的这题也是大同小异,一起来整一下。_原生jackson 反序列化链子

一文搞懂SpringCloud,详解干货,做好笔记_spring cloud-程序员宅基地

文章浏览阅读734次,点赞9次,收藏7次。微服务架构简单的说就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目。这么多小服务,如何管理他们?(服务治理 注册中心[服务注册 发现 剔除])这么多小服务,他们之间如何通讯?这么多小服务,客户端怎么访问他们?(网关)这么多小服务,一旦出现问题了,应该如何自处理?(容错)这么多小服务,一旦出现问题了,应该如何排错?(链路追踪)对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。_spring cloud

Js实现图片点击切换与轮播-程序员宅基地

文章浏览阅读5.9k次,点赞6次,收藏20次。Js实现图片点击切换与轮播图片点击切换<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title></title> <script type="text/ja..._点击图片进行轮播图切换

tensorflow-gpu版本安装教程(过程详细)_tensorflow gpu版本安装-程序员宅基地

文章浏览阅读10w+次,点赞245次,收藏1.5k次。在开始安装前,如果你的电脑装过tensorflow,请先把他们卸载干净,包括依赖的包(tensorflow-estimator、tensorboard、tensorflow、keras-applications、keras-preprocessing),不然后续安装了tensorflow-gpu可能会出现找不到cuda的问题。cuda、cudnn。..._tensorflow gpu版本安装

随便推点

物联网时代 权限滥用漏洞的攻击及防御-程序员宅基地

文章浏览阅读243次。0x00 简介权限滥用漏洞一般归类于逻辑问题,是指服务端功能开放过多或权限限制不严格,导致攻击者可以通过直接或间接调用的方式达到攻击效果。随着物联网时代的到来,这种漏洞已经屡见不鲜,各种漏洞组合利用也是千奇百怪、五花八门,这里总结漏洞是为了更好地应对和预防,如有不妥之处还请业内人士多多指教。0x01 背景2014年4月,在比特币飞涨的时代某网站曾经..._使用物联网漏洞的使用者

Visual Odometry and Depth Calculation--Epipolar Geometry--Direct Method--PnP_normalized plane coordinates-程序员宅基地

文章浏览阅读786次。A. Epipolar geometry and triangulationThe epipolar geometry mainly adopts the feature point method, such as SIFT, SURF and ORB, etc. to obtain the feature points corresponding to two frames of images. As shown in Figure 1, let the first image be ​ and th_normalized plane coordinates

开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先抽取关系)_语义角色增强的关系抽取-程序员宅基地

文章浏览阅读708次,点赞2次,收藏3次。开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先关系再实体)一.第二代开放信息抽取系统背景​ 第一代开放信息抽取系统(Open Information Extraction, OIE, learning-based, 自学习, 先抽取实体)通常抽取大量冗余信息,为了消除这些冗余信息,诞生了第二代开放信息抽取系统。二.第二代开放信息抽取系统历史第二代开放信息抽取系统着眼于解决第一代系统的三大问题: 大量非信息性提取(即省略关键信息的提取)、_语义角色增强的关系抽取

10个顶尖响应式HTML5网页_html欢迎页面-程序员宅基地

文章浏览阅读1.1w次,点赞6次,收藏51次。快速完成网页设计,10个顶尖响应式HTML5网页模板助你一臂之力为了寻找一个优质的网页模板,网页设计师和开发者往往可能会花上大半天的时间。不过幸运的是,现在的网页设计师和开发人员已经开始共享HTML5,Bootstrap和CSS3中的免费网页模板资源。鉴于网站模板的灵活性和强大的功能,现在广大设计师和开发者对html5网站的实际需求日益增长。为了造福大众,Mockplus的小伙伴整理了2018年最..._html欢迎页面

计算机二级 考试科目,2018全国计算机等级考试调整,一、二级都增加了考试科目...-程序员宅基地

文章浏览阅读282次。原标题:2018全国计算机等级考试调整,一、二级都增加了考试科目全国计算机等级考试将于9月15-17日举行。在备考的最后冲刺阶段,小编为大家整理了今年新公布的全国计算机等级考试调整方案,希望对备考的小伙伴有所帮助,快随小编往下看吧!从2018年3月开始,全国计算机等级考试实施2018版考试大纲,并按新体系开考各个考试级别。具体调整内容如下:一、考试级别及科目1.一级新增“网络安全素质教育”科目(代..._计算机二级增报科目什么意思

conan简单使用_apt install conan-程序员宅基地

文章浏览阅读240次。conan简单使用。_apt install conan