简介
系统升级,相当于两个系统之间通信,他们是怎么实现跨“系统”操作升级?
框架
Bootloader
|
|
misc分区
/\
/ \
/ \
Main Recovery
代码列表
frameworks/base/core/java/android/os/RecoverySystem.java
frameworks/base/services/core/java/com/android/server/RecoverySystemService.java
bootable/recovery
注:
7.1
案例分析一:apk调系统接口升级
system部分
1.app调用接口:RecoverySystem.installPackage
1)权限
android.permission.REBOOT
android.permission.RECOVERY
2)分析
installPackage
public static void installPackage(Context context, File packageFile, boolean processed)
throws IOException {
synchronized (sRequestLock) {
···
String filename = packageFile.getCanonicalPath();//升级包路径
Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
···
// 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/")) {//升级包的根目录一定要在/data下面
if (processed) {
···
} else {
FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
try {
uncryptFile.write(filename + "\n");//将升级包路径写入文件/cache/recovery/uncrypt_file
} finally {
uncryptFile.close();
}
···
}
// If the package is on the /data partition, use the block map
// file as the package name instead.
filename = "@/cache/recovery/block.map";//特别注意,这里的路径为block.map
}
final String filenameArg = "--update_package=" + filename + "\n";
final String localeArg = "--locale=" + Locale.getDefault().toString() + "\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)) {//设置跨系统标签
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);
pm.reboot(PowerManager.REBOOT_RECOVERY_UPDATE);//根据UNCRYPT_PACKAGE_FILE初始化block.map,再重启系统
throw new IOException("Reboot failed (no permissions?)");
}
}
2.system_server处理setupBcb业务
RecoverySystemService.BinderService
setupBcb --> setupOrClearBcb
private boolean setupOrClearBcb(boolean isSetup, String command) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
final boolean available = checkAndWaitForUncryptService();
if (!available) {
Slog.e(TAG, "uncrypt service is unavailable.");
return false;
}
if (isSetup) {
SystemProperties.set("ctl.start", "setup-bcb");//执行setup-bcb,启动uncrypt进程
} else {
SystemProperties.set("ctl.start", "clear-bcb");
}
// Connect to the uncrypt service socket.
LocalSocket socket = connectService();//system_server与uncrypt跨进程通信
if (socket == null) {
Slog.e(TAG, "Failed to connect to uncrypt socket");
return false;
}
DataInputStream dis = null;
DataOutputStream dos = null;
try {
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
// Send the BCB commands if it's to setup BCB.
if (isSetup) {
dos.writeInt(command.length());//socket写指令长度
dos.writeBytes(command);//socket写指令值
dos.flush();
}
// Read the status from the socket.
int status = dis.readInt();//读取socket状态
// 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) {//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;
}
3.uncrypt进程作为socket服务端处理指令。由SystemProperties.set("ctl.start", "setup-bcb")启动
(bcb -- Bootloader Control Block(BCB))
代码路径:
bootable/recovery/uncrypt
1)uncrypt.rc
service setup-bcb /system/bin/uncrypt --setup-bcb //启动uncrypt
class main
socket uncrypt stream 600 system system
disabled
oneshot
2)uncrypt.cpp --> main
int main(int argc, char** argv) {
enum { UNCRYPT, SETUP_BCB, CLEAR_BCB } action;
const char* input_path = nullptr;
const char* map_file = CACHE_BLOCK_MAP.c_str();
if (argc == 2 && strcmp(argv[1], "--clear-bcb") == 0) {
action = CLEAR_BCB;
} else if (argc == 2 && strcmp(argv[1], "--setup-bcb") == 0) {
action = SETUP_BCB;//执行setup-bcb
} else if (argc == 1) {
action = UNCRYPT;
} else if (argc == 3) {
input_path = argv[1];
map_file = argv[2];
action = UNCRYPT;
} else {
usage(argv[0]);
return 2;
}
if ((fstab = read_fstab()) == nullptr) {
log_uncrypt_error_code(kUncryptFstabReadError);
return 1;
}
// c3. The socket is created by init when starting the service. uncrypt
// will use the socket to communicate with its caller.
unique_fd service_socket(android_get_control_socket(UNCRYPT_SOCKET.c_str()));//生成service_socket对象
if (!service_socket) {
ALOGE("failed to open socket \"%s\": %s", UNCRYPT_SOCKET.c_str(), strerror(errno));
log_uncrypt_error_code(kUncryptSocketOpenError);
return 1;
}
fcntl(service_socket.get(), F_SETFD, FD_CLOEXEC);
if (listen(service_socket.get(), 1) == -1) {//监听socket
ALOGE("failed to listen on socket %d: %s", service_socket.get(), strerror(errno));
log_uncrypt_error_code(kUncryptSocketListenError);
return 1;
}
unique_fd socket_fd(accept4(service_socket.get(), nullptr, nullptr, SOCK_CLOEXEC));//接收socket数据
if (!socket_fd) {
ALOGE("failed to accept on socket %d: %s", service_socket.get(), strerror(errno));
log_uncrypt_error_code(kUncryptSocketAcceptError);
return 1;
}
bool success = false;
switch (action) {
case UNCRYPT:
success = uncrypt_wrapper(input_path, map_file, socket_fd.get());
break;
case SETUP_BCB:
success = setup_bcb(socket_fd.get());//将数据传输到函数setup_bcb种
break;
case CLEAR_BCB:
success = clear_bcb(socket_fd.get());
break;
default: // Should never happen.
ALOGE("Invalid uncrypt action code: %d", action);
return 1;
}
// c13. Read a 4-byte code from the client before uncrypt exits. This is to
// ensure the client to receive the last status code before the socket gets
// destroyed.
int code;
if (android::base::ReadFully(socket_fd.get(), &code, 4)) {
ALOGI(" received %d, exiting now", code);
} else {
ALOGE("failed to read the code: %s", strerror(errno));
}
return success ? 0 : 1;
}
3)setup_bcb对misc分区做数据处理
static bool setup_bcb(const int socket) {
// c5. receive message length
int length;
if (!android::base::ReadFully(socket, &length, 4)) {
ALOGE("failed to read the length: %s", strerror(errno));
return false;
}
length = ntohl(length);
// c7. receive message
std::string content;
content.resize(length);
if (!android::base::ReadFully(socket, &content[0], length)) {//读取socket数据,也及时command
ALOGE("failed to read the length: %s", strerror(errno));
return false;
}
ALOGI(" received command: [%s] (%zu)", content.c_str(), content.size());
std::vector<std::string> options = android::base::Split(content, "\n");
std::string wipe_package;
for (auto& option : options) {
if (android::base::StartsWith(option, "--wipe_package=")) {
std::string path = option.substr(strlen("--wipe_package="));
if (!android::base::ReadFileToString(path, &wipe_package)) {
ALOGE("failed to read %s: %s", path.c_str(), strerror(errno));
return false;
}
option = android::base::StringPrintf("--wipe_package_size=%zu", wipe_package.size());
}
}
// c8. setup the bcb command
std::string err;
if (!write_bootloader_message(options, &err)) {//将数据写道misc分区
ALOGE("failed to set bootloader message: %s", err.c_str());
write_status_to_socket(-1, socket);
return false;
}
···
// c10. send "100" status
write_status_to_socket(100, socket);
return true;
}
4)bootloader_message.cpp实现函数write_bootloader_message
bool write_bootloader_message(const std::vector<std::string>& options, std::string* err) {
bootloader_message boot = {};
strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
for (const auto& s : options) {
strlcat(boot.recovery, s.c_str(), sizeof(boot.recovery));//分段写入结构体boot.recovery变量种
if (s.back() != '\n') {
strlcat(boot.recovery, "\n", sizeof(boot.recovery));
}
}
return write_bootloader_message(boot, err);
}
提醒:
recovery:可被Main System写入,也可被Recovery服务程序写入。该文件的内容格式为:
recovery\n
<recovery command>\n
<recovery command>
该文件存储的就是一个字符串,必须以recovery\n开头,否则这个字段的所有内容域会被忽略。
“recovery\n”之后的部分,是/cache/recovery/command支持的命令。
可以将其理解为Recovery操作过程中对命令操作的备份。
Recovery对其操作的过程为:先读取BCB然后读取/cache/recovery/command,然后将二者重新写回BCB,这样在进入Main system之前,确保操作被执行。
在操作之后进入Main system之前,Recovery又会清空BCB的command域和recovery域,这样确保重启后不再进入Recovery模式。
5)将数据写入misc分区:bootloader_message.cpp : write_bootloader_message --> write_misc_partition
static bool write_misc_partition(const void* p, size_t size, size_t offset, std::string* err) {
std::string misc_blk_device = get_misc_blk_device(err);//获取misc分区目录
//例如:/dev/block/platform/ff0f0000.dwmmc/by-name/misc /misc emmc defaults defaults
//即:misc_blk_device --> /dev/block/platform/ff0f0000.dwmmc/by-name/misc
if (misc_blk_device.empty()) {
return false;
}
android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY | O_SYNC));
if (fd.get() == -1) {
*err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(),
strerror(errno));
return false;
}
if (lseek(fd.get(), static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) {
*err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(),
strerror(errno));
return false;
}
if (!android::base::WriteFully(fd.get(), p, size)) {//将数据写入misc分区
*err = android::base::StringPrintf("failed to write %s: %s", misc_blk_device.c_str(),
strerror(errno));
return false;
}
// TODO: O_SYNC and fsync duplicates each other?
if (fsync(fd.get()) == -1) {
*err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(),
strerror(errno));
return false;
}
return true;
}
补充:
static std::string get_misc_blk_device(std::string* err) {
struct fstab* fstab = read_fstab(err);//读分区表
if (fstab == nullptr) {
return "";
}
fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab, "/misc");//根据misc获取misc分区的数据结构
if (record == nullptr) {
*err = "failed to find /misc partition";
return "";
}
return record->blk_device;
}
4.Powermanager.reboot重启系统
Powermanager.reboot(PowerManager.REBOOT_RECOVERY_UPDATE)
--> PowerManagerService.reboot
--> ShutdownThread.reboot --> shutdownInner --> beginShutdownSequence --> run
public void run() {
···
if (mRebootHasProgressBar) {
···
// If it's to reboot to install an update and uncrypt hasn't been
// done yet, trigger it now.
uncrypt();//执行uncrypt业务
}
···
rebootOrShutdown(mContext, mReboot, mReason);//重启
}
private void uncrypt() {
Log.i(TAG, "Calling uncrypt and monitoring the progress...");
final RecoverySystem.ProgressListener progressListener =
new RecoverySystem.ProgressListener() {
@Override
public void onProgress(int status) {
···
}
};
final boolean[] done = new boolean[1];
done[0] = false;
Thread t = new Thread() {
@Override
public void run() {
RecoverySystem rs = (RecoverySystem) mContext.getSystemService(
Context.RECOVERY_SERVICE);
String filename = null;
try {
filename = FileUtils.readTextFile(RecoverySystem.UNCRYPT_PACKAGE_FILE, 0, null);//利用了UNCRYPT_PACKAGE_FILE
rs.processPackage(mContext, new File(filename), progressListener);//1)将数据传递到RecoverySystemService 2)RecoverySystemService传递给uncrypt进程
} catch (IOException e) {
Log.e(TAG, "Error uncrypting file", e);
}
done[0] = true;
}
};
t.start();
try {
t.join(MAX_UNCRYPT_WAIT_TIME);
} catch (InterruptedException unused) {
}
if (!done[0]) {
···
}
}
5.总结
system_server(RecoverySystemService)通过socket与uncrypt建立了通信,uncrypt做了如下操作:
1.指令保存在misc分区
2.update.zip所在内存地址被保存在/cache/recovery/block.map
另外,update.zip包的路径需要以/data开头
细节如下:
1)在android7.0中,recovery模式中已经不在挂载data分区,而且data分区可以设置加密,这样更促进了在recovery分区中不挂载data分区,加强了用户的安全性。但是这样就会有问题,当升级包较大时cache分区是放不下的,增大cache分区只会浪费资源,最好的办法还是把它放在data分区下。但是因为加密和不挂载的原因导致在recovery模式下是无法使用升级包的。而uncrypt机制就是解决这个问题的。它的基本原理就是,在正常模式下记录升级包所在的磁盘区块号,而且如果加密了就通过读进行解密,再绕过加密写到存储磁盘上。当记录好了磁盘区块号后,启动到recovery模式后就可以不挂载data分区,直接根据区块号读取升级包中的数据进行升级
2)system_server把update.zip路径保存在文件/cache/recovery/uncrypt_file
uncrypt读取/cache/recovery/uncrypt_file生成/cache/recovery/block.map
3)/cache/recovery/block.map 是update.zip存放的数据地址
recovery部分
提出两个问题进行分析:
1)系统怎么知道将进入的是recovery模式呢?
2)recovery怎么知道update.zip放在哪里呢?
代码路径:
boottable/recovery
1.Bootloader通过根据结构体bootloader_message.recovery的值进行判断
注:bootloader_message的初始化来自于misc分区
int main(int argc, char **argv) {
···
get_args(&argc, &argv);//从misc分区中读取数据,并初始化结构体bootloader_message
dumpCmdArgs(argc, argv);
···
int arg;
int option_index;
while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
switch (arg) {
···
case 'u': update_package = optarg; break;
···
}
}
···
if (update_package) {
···
strcpy(updatepath, update_package);
}
···
int status = INSTALL_SUCCESS;
if (update_package != NULL) {
···
status = install_package(reallyPath, &should_wipe_cache,
TEMPORARY_INSTALL_FILE, true, retry_count);//更新系统
if (status == INSTALL_SUCCESS && should_wipe_cache) {
wipe_cache(false, device);
}
···
}
···
finish_recovery(send_intent);//重启misc分区数据
···
// Should be unreachable.
return EXIT_SUCCESS;
}
部分日志如下:
[ 0.002458] I:Boot command: boot-recovery
[ 0.002475] I:Got arguments from boot message
[ 0.004836] === start void dumpCmdArgs(int, char **):296 ===
[ 0.004853] argv[0] = recovery.
[ 0.004859] argv[1] = --update_package=@/cache/recovery/block.map.
[ 0.004865] argv[2] = --locale=zh_CN.
其他
RK的高可靠性业务
BOARD_USES_FULL_RECOVERY_IMAGE
https://blog.csdn.net/guyongqiangx/article/details/71334889
参考学习
https://blog.csdn.net/luzhenrong45/article/details/60968458
网友评论