本文主要介绍基本原理,源码,制作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