ota 原理流程

本文主要介绍基本原理,源码,制作ota

1 ota 基本原理

OTA(Over-the-Air Technology)空中下载技术
现在ota 基本是AB 分区的 ,在系统正常运行的时候进行升级,升级之后启动对应的升级之后的分区。 之前最早的时候只有一个分区, 是recovery 升级,就本质而言,都类似,因此下面介绍recovery source code

终端ota 升级流程

--->1.1 系统运行时获取升级包,可以从服务端下载,也可以直接拷贝到SD卡中
    ----> 1.2 获取升级包路径,验证签名,通过installPackage接口升级
--->1.3 系统重启进入Recovery模式
    ---->1.4 在install.cpp进行升级操作
         ----> 1.5 try_update_binary执行升级脚本
              --->1.6 finish_recovery,重启

1.1 获取升级包
device 可以设计如何从服务器获取update.zip 升级包,因为每一个公司的服务器不同, 升级的apk 设计也不一样,目的就是获取update.zip 差分升级包。

举例: 服务器可以搭建go lang 服务,升级的apk 直接调用RecoverySystem 类进入升级流程

1.2 installPackage 接口升级
下面verifyPackage 校验文件,签名,如果不对抛出异常,实际在recovery阶段还是会进行校验升级文件
frameworks/base/core/java/android/os/ RecoverySystem.java
verifyPackage

 public static void verifyPackage(File packageFile,---》升级文件名字
                                     ProgressListener listener,
                                     File deviceCertsZipFile)
        throws IOException, GeneralSecurityException {
        final long fileLen = packageFile.length();

        final RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
        try {
            final long startTimeMillis = System.currentTimeMillis();
            if (listener != null) {
                listener.onProgress(0);---->进度监听
            }

            // Parse the signature------》解析签名
            PKCS7 block =
                new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));

            // Take the first certificate from the signature (packages
            // should contain only one).
            X509Certificate[] certificates = block.getCertificates();
            if (certificates == null || certificates.length == 0) {
                throw new SignatureException("signature contains no certificates");
            }
            X509Certificate cert = certificates[0];
            PublicKey signatureKey = cert.getPublicKey();

            // Check that the public key of the certificate contained
            // in the package equals one of our trusted public keys.
            boolean verified = false;
            //deviceCertsZipFile  ----->签名文件
            HashSet trusted = getTrustedCerts(
                deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
            for (X509Certificate c : trusted) {
                if (c.getPublicKey().equals(signatureKey)) {
                    verified = true;
                    break;
                }
            }
            if (!verified) {
                throw new SignatureException("signature doesn't match any trusted key");
            }

            // The signature cert matches a trusted key.  Now verify that
            // the digest in the cert matches the actual file data.
            raf.seek(0);
   
........
            final boolean interrupted = Thread.interrupted();
            if (listener != null) {
                listener.onProgress(100);
            }

            if (interrupted) {
                throw new SignatureException("verification was interrupted");
            }

            if (verifyResult == null) {
                throw new SignatureException("signature digest verification failed");
            }
        } finally {
            raf.close();
        }

        // Additionally verify the package compatibility.
        if (!readAndVerifyPackageCompatibilityEntry(packageFile)) {
            throw new SignatureException("package compatibility verification failed");---->抛出异常
        }
    }

主要功能清空上一次 UNCRYPT_PACKAGE_FILE, 校验过后,进入系统的recovery 引导模式
frameworks/base/core/java/android/os/ RecoverySystem.java
installPackage

    /**
     * If the package hasn't been processed (i.e. uncrypt'd), set up
     * UNCRYPT_PACKAGE_FILE and delete BLOCK_MAP_FILE to trigger uncrypt during the
     * reboot.
     *
     * @param context      the Context to use
     * @param packageFile  the update package to install.  Must be on a
     * partition mountable by recovery.
     * @param processed    if the package has been processed (uncrypt'd).
     *
     * @throws IOException if writing the recovery command file fails, or if
     * the reboot itself fails.
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.RECOVERY)
    public static void installPackage(Context context, File packageFile, boolean processed)
            throws IOException {
        synchronized (sRequestLock) {
            LOG_FILE.delete();
            // Must delete the file in case it was created by system server.
            UNCRYPT_PACKAGE_FILE.delete();  ----》 清空UNCRYPT_PACKAGE_FILE

            String filename = packageFile.getCanonicalPath();---》 获取升级文件名字
            Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");

            // If the package name ends with "_s.zip", it's a security update.
            boolean securityUpdate = filename.endsWith("_s.zip");
            // If the package is on the /data partition, the package needs to
            // be processed (i.e. uncrypt'd). The caller specifies if that has
            // been done in 'processed' parameter.
            if (filename.startsWith("/data/")) {
                if (processed) {
                    if (!BLOCK_MAP_FILE.exists()) {
                        Log.e(TAG, "Package claimed to have been processed but failed to find "
                                + "the block map file.");
                        throw new IOException("Failed to find block map file");
                    }
                } else {
                    FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
                    try {
                        uncryptFile.write(filename + "\n");
                    } finally {
                        uncryptFile.close();
                    }
                    // UNCRYPT_PACKAGE_FILE needs to be readable and writable
                    // by system server.
                    if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false)
                            || !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) {
                        Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE);
                    }

                    BLOCK_MAP_FILE.delete();
                }

                // If the package is on the /data partition, use the block map
                // file as the package name instead.
                filename = "@/cache/recovery/block.map";
            }
            final String filenameArg = "--update_package=" + filename + "\n";
            final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
            final String securityArg = "--security\n";

            String command = filenameArg + localeArg;
            if (securityUpdate) {
                command += securityArg;
            }

            RecoverySystem rs = (RecoverySystem) context.getSystemService(
                    Context.RECOVERY_SERVICE);
            if (!rs.setupBcb(command)) { ----》设定重启参数 --->bootCommand
                throw new IOException("Setup BCB failed");
            }
            // Having set up the BCB (bootloader control block), go ahead and reboot
            PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
            String reason = PowerManager.REBOOT_RECOVERY_UPDATE;

            // On TV, reboot quiescently if the screen is off
            if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
                WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
                if (wm.getDefaultDisplay().getState() != Display.STATE_ON) {
                    reason += ",quiescent";
                }
            }
            pm.reboot(reason);----> 引导系统进入recovery模式
            throw new IOException("Reboot failed (no permissions?)");
        }
    }

frameworks/base/core/java/android/os/ RecoverySystem.java
bootCommand

     * Reboot into the recovery system with the supplied argument.
     * @param args to pass to the recovery utility.
     * @throws IOException if something goes wrong.
     */
    private static void bootCommand(Context context, String... args) throws IOException {
        LOG_FILE.delete();

        StringBuilder command = new StringBuilder();
        for (String arg : args) {
            if (!TextUtils.isEmpty(arg)) {
                command.append(arg);
                command.append("\n");
            }
        }

        // Write the command into BCB (bootloader control block) and boot from
        // there. Will not return unless failed.
        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
        rs.rebootRecoveryWithCommand(command.toString());

        throw new IOException("Reboot failed (no permissions?)");
    }

1.3系统重启进入Recovery模式
这个时候手机进入了重启,进入了recovery
bootable/recovery/recovery.cpp

int main(int argc, char **argv) {
    android::base::InitLogging(argv, &UiLogger);---->log 打印 终端
    .......
    case 'u': update_package = optarg; break;----》读取recovery 之前的参数,进入update 升级, 参考OPTIONS

    locale = load_locale_from_cache();---》从cache 读取update.zip 

    ui = new StubRecoveryUI();  ---->new 升级进度显示

    ui->SetBackground(RecoveryUI::NONE);------>升级进度条显示

    selinux_android_set_sehandle(sehandle);---->selinux

    device->StartRecovery();  ---》开始升级

    if(do_sdcard_mount_for_ufs() != 0) {----->sdcard mount

    if (!is_battery_ok()) {-----》检测电量

    status = install_package(update_package, &should_wipe_cache,------->进入了install_package

    finish_recovery();-----》升级完成,重启
}

bootable/recovery/recovery.cpp
OPTIONS

static const struct option OPTIONS[] = {
  { "update_package", required_argument, NULL, 'u' },
  { "retry_count", required_argument, NULL, 'n' },
  { "wipe_data", no_argument, NULL, 'w' },
  { "wipe_cache", no_argument, NULL, 'c' },
  { "show_text", no_argument, NULL, 't' },
  { "sideload", no_argument, NULL, 's' },
  { "sideload_auto_reboot", no_argument, NULL, 'a' },
  { "just_exit", no_argument, NULL, 'x' },
  { "locale", required_argument, NULL, 'l' },
  { "shutdown_after", no_argument, NULL, 'p' },
  { "reason", required_argument, NULL, 'r' },
  { "security", no_argument, NULL, 'e'},
  { "wipe_ab", no_argument, NULL, 0 },
  { "wipe_package_size", required_argument, NULL, 0 },
  { "prompt_and_wipe_data", no_argument, NULL, 0 },
  { NULL, 0, NULL, 0 },
};

1.4在install.cpp进行升级操作
bootable/recovery/ install.cpp
install_package

int install_package(const std::string& path, bool* wipe_cache, const std::string& install_file,
                    bool needs_mount, int retry_count) {
..........
    if (result != 0) {
    LOG(ERROR) << "failed to set up expected mounts for install; aborting";
    result = INSTALL_ERROR;
  } else {
    result = really_install_package(path, wipe_cache, needs_mount, &log_buffer, retry_count,
                                    &max_temperature);------>进入下一步的升级过程
  } 
......
  return result;
}

真正的升级在下面的函数中实现
bootable/recovery/ install.cpp
really_install_package

static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount,
                                  std::vector* log_buffer, int retry_count,
                                  int* max_temperature) {
  ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
  ui->Print("Finding update package...\n");
  // Give verification half the progress bar...
  ui->SetProgressType(RecoveryUI::DETERMINATE);
  ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
  LOG(INFO) << "Update location: " << path;

  // Map the update package into memory.
  ui->Print("Opening update package...\n");

  // Verify package.
  if (!verify_package(map.addr, map.length)) {----》校验update .zip 
    log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
    return INSTALL_CORRUPT;
  }

  // Try to open the package.
  ZipArchiveHandle zip;
  int err = OpenArchiveFromMemory(map.addr, map.length, path.c_str(), &zip);
  if (err != 0) {
    LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err);
    log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));

    CloseArchive(zip);
    return INSTALL_CORRUPT;
  }

  // Additionally verify the compatibility of the package.
  if (!verify_package_compatibility(zip)) {
    log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
    CloseArchive(zip);
    return INSTALL_CORRUPT;
  }

  // Verify and install the contents of the package.
  ui->Print("Installing update...\n");----->开始实际升级
  if (retry_count > 0) {
    ui->Print("Retry attempt: %d\n", retry_count);
  }
  ui->SetEnableReboot(false);
  int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, ----》 升级中max_temperature);
  ui->SetEnableReboot(true);---》 升级完成reboot 
  ui->Print("\n");

  CloseArchive(zip);
  return result;
}

1.5、try_update_binary执行升级脚本
bootable/recovery/install.cpp
try_update_binary

// If the package contains an update binary, extract it and run it.
static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache,
                             std::vector* log_buffer, int retry_count,
                             int* max_temperature) {
  read_source_target_build(zip, log_buffer);

  int pipefd[2];
  pipe(pipefd);

  std::vector args;
#ifdef AB_OTA_UPDATER
  int ret = update_binary_command(package, zip, "/sbin/update_engine_sideload", retry_count,
                                  pipefd[1], &args);
#else
  int ret = update_binary_command(package, zip, "/tmp/update-binary", retry_count, pipefd[1],
                                  &args);
#endif
  if (ret) {
    close(pipefd[0]);
    close(pipefd[1]);
    return ret;
  }

  // When executing the update binary contained in the package, the
  // arguments passed are:
  //
  //   - the version number for this interface
  //
  //   - an FD to which the program can write in order to update the
  //     progress bar.  The program can write single-line commands:
  //
  //        progress  
  //            fill up the next  part of of the progress bar
  //            over  seconds.  If  is zero, use
  //            set_progress commands to manually control the
  //            progress of this segment of the bar.
  //
  //        set_progress 
  //             should be between 0.0 and 1.0; sets the
  //            progress bar within the segment defined by the most
  //            recent progress command.
  //
  //        ui_print 
  //            display  on the screen.
  //
  //        wipe_cache
  //            a wipe of cache will be performed following a successful
  //            installation.
  //
  //        clear_display
  //            turn off the text display.
  //
  //        enable_reboot
  //            packages can explicitly request that they want the user
  //            to be able to reboot during installation (useful for
  //            debugging packages that don't exit).
  //
  //        retry_update
  //            updater encounters some issue during the update. It requests
  //            a reboot to retry the same package automatically.
  //
  //        log 
  //            updater requests logging the string (e.g. cause of the
  //            failure).
  //
  //   - the name of the package zip file.
  //
  //   - an optional argument "retry" if this update is a retry of a failed
  //   update attempt.
  //

  // Convert the vector to a NULL-terminated char* array suitable for execv.
  const char* chr_args[args.size() + 1];
  chr_args[args.size()] = nullptr;
  for (size_t i = 0; i < args.size(); i++) {
    chr_args[i] = args[i].c_str();
  }

  pid_t pid = fork();
  if (pid == -1) {
    close(pipefd[0]);
    close(pipefd[1]);
    PLOG(ERROR) << "Failed to fork update binary";
    return INSTALL_ERROR;
  }

  if (pid == 0) {
    umask(022);
    close(pipefd[0]);
    execv(chr_args[0], const_cast(chr_args));--->使用子进程进行执行升级
    // Bug: 34769056
    // We shouldn't use LOG/PLOG in the forked process, since they may cause
    // the child process to hang. This deadlock results from an improperly
    // copied mutex in the ui functions.
    fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
    _exit(EXIT_FAILURE);
  }
  close(pipefd[1]);

  std::atomic logger_finished(false);
  std::thread temperature_logger(log_max_temperature, max_temperature, std::ref(logger_finished));

  *wipe_cache = false;
  bool retry_update = false;

  char buffer[1024];
  FILE* from_child = fdopen(pipefd[0], "r");
  while (fgets(buffer, sizeof(buffer), from_child) != nullptr) {
    std::string line(buffer);
    size_t space = line.find_first_of(" \n");
    std::string command(line.substr(0, space));
    if (command.empty()) continue;

    // Get rid of the leading and trailing space and/or newline.
    std::string args = space == std::string::npos ? "" : android::base::Trim(line.substr(space));

    if (command == "progress") {
      std::vector tokens = android::base::Split(args, " ");
      double fraction;
      int seconds;
      if (tokens.size() == 2 && android::base::ParseDouble(tokens[0].c_str(), &fraction) &&
          android::base::ParseInt(tokens[1], &seconds)) {
        ui->ShowProgress(fraction * (1 - VERIFICATION_PROGRESS_FRACTION), seconds);
      } else {
        LOG(ERROR) << "invalid \"progress\" parameters: " << line;
      }
    } else if (command == "set_progress") {--》在主进程中执行显示升级的progress
      std::vector tokens = android::base::Split(args, " ");
      double fraction;
      if (tokens.size() == 1 && android::base::ParseDouble(tokens[0].c_str(), &fraction)) {
        ui->SetProgress(fraction);
      } else {
        LOG(ERROR) << "invalid \"set_progress\" parameters: " << line;
      }
    } else if (command == "ui_print") {
      ui->PrintOnScreenOnly("%s\n", args.c_str());
      fflush(stdout);
    } else if (command == "wipe_cache") {
      *wipe_cache = true;
    } else if (command == "clear_display") {
      ui->SetBackground(RecoveryUI::NONE);
    } else if (command == "enable_reboot") {
      // packages can explicitly request that they want the user
      // to be able to reboot during installation (useful for
      // debugging packages that don't exit).
      ui->SetEnableReboot(true);
    } else if (command == "retry_update") {
      retry_update = true;
    } else if (command == "log") {
      if (!args.empty()) {
        // Save the logging request from updater and write to last_install later.
        log_buffer->push_back(args);
      } else {
        LOG(ERROR) << "invalid \"log\" parameters: " << line;
      }
    } else {
      LOG(ERROR) << "unknown command [" << command << "]";
    }
  }
  fclose(from_child);

  int status;
  waitpid(pid, &status, 0);

  logger_finished.store(true);
  finish_log_temperature.notify_one();
  temperature_logger.join();

  if (retry_update) {
    return INSTALL_RETRY;
  }
  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
    LOG(ERROR) << "Error in " << package << " (Status " << WEXITSTATUS(status) << ")";
    return INSTALL_ERROR;
  }

  return INSTALL_SUCCESS;
}

1.6、finish_recovery,重启
升级完成后保存Log ,reboot

int main(int argc, char **argv) {
    ..........升级完成...... 
    // Save logs and clean up before rebooting or shutting down.
    finish_recovery(); 
    .....
}

2 制作update.zip AB 分区

本部分主要介绍,如何制作ota 升级包
全量升级包
增量升级包

2.1 全量升级包

source build/envsetup.sh
lunch 
make otapackage -j8

全量升级包路径:out/target/product/sdm845/sdm845-ota-eng.zip
在build 完成后,产生制作增量升级的source files , 文件路径是:
out/target/product/sdm845/obj/PACKAGING/target_files_intermediates/dm845-target_files-eng.zip

2.2-增量升级包
/build/tools/releasetools/ota_from_target_files –v –-block -p out/host/ linux-x86 -k build/target/product/security/testkey -i path_to_target_files_v1.zip path_to_target_files_v2.zip update.zip
其中 :path_to_target_files_v1.zip与path_to_target_files_v2.zip就是上面做全量包时候产生的文件target_files

备注1
上面的制作方式,只是在 android 部分,如果包含了第三方文件怎么版本。例如 高通的bp 中的modem bt 等等做差分。
高通bp 差分包之前需要做的事情,就是把bp build 完整,然后copy 生成的bin 到android的ration 目录下。 然后执行2.1, 2.2 部分就可以了

mkdir RADIO 文件夹 在/device/qcom//.
copy non-HLOS.mbn, tz.mbn, rpm.mbn, 等等到 RADIO 文件夹

common/build/ufs/bin/asic/NON-HLOS.bin modem.img
common/build/ufs/bin/BTFM.bin bluetooth.img
common/build/ufs/bin/asic/dspso.bin dsp.img
boot_images/QcomPkg/SDM845Pkg/Bin/845/LA/RELEASE/xbl.elf xbl.img
boot_images/QcomPkg/SDM845Pkg/Bin/845/LA/RELEASE/xbl_config.elf xbl_config.img
trustzone_images/build/ms/bin/WAXAANAA/tz.mbn tz.img
aop_proc/build/ms/bin/AAAAANAZO/aop.mbn aop.img
trustzone_images/build/ms/bin/WAXAANAA/hyp.mbn hyp.img
trustzone_images/build/ms/bin/WAXAANAA/keymaster64.mbn keymaster.img
trustzone_images/build/ms/bin/WAXAANAA/cmnlib.mbn cmnlib.img
trustzone_images/build/ms/bin/WAXAANAA/cmnlib64.mbn cmnlib64.img
LINUX/android/out/target/product/sdm845/abl.elf abl.img
trustzone_images/build/ms/bin/WAXAANAA/devcfg.mbn devcfg.img
common/core_qupv3fw/sdm845/rel/1.0/qupv3fw.elf qupfw.img
trustzone_images/build/ms/bin/WAXAANAA/storsec.mbn storsec.img
LINUX/android/out/target/product/sdm845/vbmeta.img vbmeta.img
LINUX/android/out/target/product/sdm845/dtbo.img dtbo.img
boot_images/QcomPkg/SDM845Pkg/Bin/845/LA/RELEASE/imagefv.elf ImageFv.img

对应关系:

    NON-HLOS.bin /dev/block/bootdevice/by-name/modem
    BTFM.bin /dev/block/bootdevice/by-name/bluetooth
    dspso.bin /dev/block/bootdevice/by-name/dsp 
    mdtpsecapp.mbn /dev/block/bootdevice/by-name/mdtpsecapp
    mdtp.img /dev/block/bootdevice/by-name/mdtp
    xbl.elf /dev/block/bootdevice/by-name/xbl
    xbl_config.elf /dev/block/bootdevice/by-name/xbl_config
    tz.mbn /dev/block/bootdevice/by-name/tz
    aop.mbn /dev/block/bootdevice/by-name/aop
    hyp.mbn /dev/block/bootdevice/by-name/hyp
    keymaster64.mbn /dev/block/bootdevice/by-name/keymaster
    cmnlib.mbn /dev/block/bootdevice/by-name/cmnlib
    cmnlib64.mbn /dev/block/bootdevice/by-name/cmnlib64
    abl.elf /dev/block/bootdevice/by-name/abl
    devcfg.mbn /dev/block/bootdevice/by-name/devcfg
    qupv3fw.elf /dev/block/bootdevice/by-name/qupfw
    storsec.mbn /dev/block/bootdevice/by-name/storsec
    vbmeta.img /dev/block/bootdevice/by-name/vbmeta
    dtbo.img /dev/block/bootdevice/by-name/dtbo
    imagefv.elf /dev/block/bootdevice/by-name/ImageFv

make AB_OTA_PARTITIONS="abl aop bluetooth cmnlib64 cmnlib devcfg dsp hyp keymaster modem qupfw tz xbl boot system vendor xbl_config dtbo vbmeta storsec ImageFv" -j

备注2:
如果升级差分包失败,考虑base版本是否fastboot 下面的路径版本
out/target/product/sdm845/obj/PACKAGING/target_files_intermediates

你可能感兴趣的:(ota 原理流程)