Android10.0OTA升级流程分析
⼀、概述
⽬前Android系统终端的升级主要是通过⽆线进⾏的(FOTA,Firmware Over-The-Air),主要流程是通过⽆线⽅式将升级包下载到终端,⽽后调⽤系统的升级接⼝进⾏升级。本⽂主要分析升级包下载后,调⽤系统升级接⼝之后的流程。
1.1 升级包结构
升级包是⽤make otapackage命令⽣成的,对于差分包,需要⽣成两个ota整包,再⽤系统的编译⼯具利⽤这两个整包⽣成⼀个差分包。下⾯是某终端Android9.0版本的ota升级包结构。
├─firmware-update
└─META-INF
└─com
├─android
├─metadata
├─otacert
└─google
└─android
├─update-binary
├─updater-script
├─boot.img
├─w.dat.br
├─system.patch.dat
├─ansfer.list
├─w.dat.br
├─vendor.patch.dat
├─ansfer.list
firmware-update:⽬录下主要是⼀些固件的升级,如mbn、dtbo、vbmeta等
boot.img:更新boot分区所需要的⽂件,包括kernel+ramdisk
system.patch.dat:升级时⽤到,ota包中⼤⼩为0。
vendor和system类似。
update-binary:⼆进制⽂件,相当于⼀个脚本解释器,能够识别updater-script中描述的操作,⽂件的命名由
bootable/recovery/install.cpp 中的UPDATE_BINARY_NAME值⽽定。
updater-script:升级包的升级脚本,⽂件的命名由bootable/recovery/updater/updater.cpp中的SCRIPT_NAME值⽽定。metadata:描述设备信息及环境变量的元数据,主要包括⼀些编译选项、签名公钥,时间戳以及设备型号等。
otacert:ota签名。
1.2 系统启动模式
Android系统启动主要有两种:
1.组合键
⽤户通过按下组合键,不同的终端可以定义组合键,进⼊不同的⼯作模式,在bootable/bootloader/lk/app/aboot/aboot.c⽂件中判断,具体有两种:
bootloader模式,可进⼀步进⼊fastboot(快速刷机模式)。
Recovery模式,进⼊这种模式系统会出现⼀个简单的ui界⾯,⽤来提⽰⽤户的进⼀步操作。
[外链图⽚转存失败,源站可能有防盗链机制,建议将图⽚保存下来直接上传(img-RT9PaFD4-1577704943830)(/Android OTA升级流程分析/recovery.jpg)]
2.正常启动
没有按下任何组合键,正常开机,bootloader会读取启动控制信息块BCB(bootloader control block),它是⼀个结构体,存放着启动命令Command。根据不同的命令,系统可以进⼊三种不同的启动模式。下⾯是Bootloader Message的定义,通过注释可以了解具体结构体中成员变量的含义。
[->bootloader_message.h]
/* Bootloader Message (2-KiB)
*
* This structure describes the content of a block in flash
* that is used for recovery and the bootloader to talk to
* each other.
*
* The command field is updated by linux when it wants to
* reboot into recovery or to update radio or bootloader firmware.
* It is also updated by the bootloader when firmware update语文老师自我介绍
* is complete (to boot into recovery for any final cleanup)
*
* The status field was used by the bootloader after the completion
* of an "update-radio" or "update-hboot" command, which has been
* deprecated since Froyo.
*
* The recovery field is only written by linux and used
* for the system to send a message to recovery or the
* other way around.
*
* The stage field is written by packages which restart themselves
* multiple times, so that the UI can reflect which invocation of the
* package it is. If the value is of the format "#/#" (eg, "1/3"),
* the UI will add a simple indicator of that status.
*
* We used to have slot_suffix field for A/B boot control metadata in
* this struct, which gets unintentionally cleared by recovery or
* uncrypt. Move it into struct bootloader_message_ab to avoid the
* issue.
*/
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];
// The 'reserved' field used to be 224 bytes when it was initially
/
/ carved off from the 1024-byte recovery field. Bump it up to
// 1184-byte so that the entire bootloader_message struct rounds up
// to 2048-byte.
char reserved[1184];
};
⼆、OTA升级重启前
前⾯介绍完OTA升级包和系统启动模式,现在看下真正的ota升级的步骤。⽆论ota包是通过⽆线下载还是导⼊到SD卡,最后都会调⽤到RecoverySystem.installPackage⽅法,下⾯分析下这个详细的流程。
2.1 RS.installPackage
[->RecoverySystem.java]
public static void installPackage(Context context, File packageFile, boolean processed) throw
民国是哪一年开始s IOException {
synchronized (sRequestLock) {
LOG_FILE.delete();
// Must delete the file in case it was created by system server.
//删除之前的uncrypt file
UNCRYPT_PACKAGE_FILE.delete();
String filename = CanonicalPath();
Log.w(TAG, " REBOOTING TO INSTALL " + filename + " ");
// If the package name ends with "_s.zip", it's a security update.
boolean securityUpdate = dsWith("_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.
//如果升级包的路径是/data/开始
if (filename.startsWith("/data/")) {
if (processed) {
if (!BLOCK_ists()) {
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
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=" + Default().toLanguageTag() + "/n"; final String securityArg = "--security/n";
String command = filenameArg + localeArg;
if (securityUpdate) {清蒸螃蟹的做法
command += securityArg;
}
RecoverySystem rs = (RecoverySystem) SystemService(
Context.RECOVERY_SERVICE);
//向bootloader control block写⼊命名
if (!rs.setupBcb(command)) {
throw new IOException("Setup BCB failed");
throw new IOException("Setup BCB failed");
员工培训内容}
// Having set up the BCB (bootloader control block), go ahead and reboot
PowerManager pm = (PowerManager) SystemService(Context.POWER_SERVICE);
String reason = PowerManager.REBOOT_RECOVERY_UPDATE;
/
/ On TV, reboot quiescently if the screen is off
if (PackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
WindowManager wm = (WindowManager) SystemService(Context.WINDOW_SERVICE);
if (wm.getDefaultDisplay().getState() != Display.STATE_ON) {
reason += ",quiescent";
}
}
//重启
throw new IOException("Reboot failed (no permissions?)");
}
}
private static final File RECOVERY_DIR = new File("/cache/recovery");
public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, “uncrypt_file”);
这⾥主要是对ota升级包的处理,由于进⼊recovery模式后,data分区将不能加载,因此需要将其通过block⽅式,把ota升级包⽣成稀疏的描述⽂件,保存在/cache/recovery/block.map中。升级的命令写⼊到BCB,重启之后bootloader会读取BCB中的command从⽽进⼊升级模式。
2.2 RS.setupBcb
[->RecoverySystem.java]
/**
* Talks to RecoverySystemService via Binder to set up the BCB.
*/
private boolean setupBcb(String command) {
try {
//通过binder调⽤setupBcb
return mService.setupBcb(command);
} catch (RemoteException unused) {
}
return false;
}冬季空调温度
2.3 RSS.setupBcb
[->RecoverySystemService.java]
@Override // Binder call
public boolean setupBcb(String command) {
if (DEBUG) Slog.d(TAG, "setupBcb: [" + command + "]");
synchronized (sRequestLock) {
return setupOrClearBcb(true, command);
}
}
2.4 RSS.setupOrClearBcb
[->RecoverySystemService.java]
private boolean setupOrClearBcb(boolean isSetup, String command) {
final boolean available = checkAndWaitForUncryptService();
if (!available) {
Slog.e(TAG, "uncrypt service is unavailable.");
return false;
}
//设置ctl.start属性,启动setup-bcb服务
if (isSetup) {
SystemProperties.set("ctl.start", "setup-bcb");
} else {
SystemProperties.set("ctl.start", "clear-bcb");
}
// Connect to the uncrypt service socket.
// 连接uncrypt服务
LocalSocket socket = connectService();
if (socket == null) {
Slog.e(TAG, "Failed to connect to uncrypt socket");
return false;
}
DataInputStream dis = null;
DataOutputStream dos = null;
try {
dis = new InputStream());
dos = new OutputStream());
// Send the BCB commands if it's to setup BCB.
/
/ 发送BCB commands
if (isSetup) {
byte[] cmdUtf8 = Bytes("UTF-8");cvt无级变速
dos.writeInt(cmdUtf8.length);
dos.write(cmdUtf8, 0, cmdUtf8.length);
dos.flush();
}
// Read the status from the socket.
int status = adInt();
// Ack receipt of the status code. uncrypt waits for the ack so
// the socket won't be destroyed before we receive the code.
dos.writeInt(0);
if (status == 100) {
Slog.i(TAG, "uncrypt " + (isSetup ? "setup" : "clear") +
" bcb successfully finished.");
} else {
// Error in /system/bin/uncrypt.
Slog.e(TAG, "uncrypt failed with status: " + status);
return false;
}
} catch (IOException e) {
Slog.e(TAG, "IOException when communicating with uncrypt:", e);
return false;
} finally {
IoUtils.closeQuietly(dis);
IoUtils.closeQuietly(dos);
IoUtils.closeQuietly(socket);
}
return true;
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论