二、Android BCB的作用

这篇很多部分来源于其他博客,我个人进行了总结,可以串联起来整个ota流程

1、问题来源
2、固件内容与开机流程
3、开机BCB作用
4、recovery升级交互流程

1、问题来源
查看解密流程和recovery.cpp升级流程,多次提到BCB相关
1、从system到recovery需要用到BCB
文件RecoverySystemService.java //设置BCB
public boolean setupBcb(String command) {
return setupOrClearBcb(true, command);
private boolean setupOrClearBcb(boolean isSetup, String command) {
Slog.i(TAG, "uncrypt " + (isSetup ? "setup" : "clear") +" bcb successfully finished.");
对应Log:RecoverySystemService: uncrypt setup bcb successfully finished.\
2、 recovery升级也需要操作BCB
//get_args方法获取misc分区的command文件
std::vector<std::string> args = get_args(argc, argv);
std::vector<char*> args_to_parse(args.size());
std::transform(args.cbegin(), args.cend(), args_to_parse.begin(),
[](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
Last_log:
I:Boot command: boot-recovery
I:Got 3 arguments from boot message
3、遇到过一次升级问题,因为系统会发送多次开机广播而导致升级失败,第二次开机广播清除了command信息
所以查询了一些资料作为整理该部分涉及到bootloder Misc system recovery boot分区
在升级流程上很少有客户会改动,作为了解就可以

2、固件内容
Boot:包含Linux内核和一个最小的root文件系统(装载到ramdisk中),
用于挂载系统和其他的分区,并开始Runtime。正如名字所代表的意思(注:boot的意思是启动),
这个分区使Android设备可以启动。如果没有这个分区,Android设备通常无法启动到Android系统。
System:这个分区几乎包含了除kerner和ramdisk之外的整个android操作系统,
包括了用户界面、和所有预装的系统应用程序和库文件(AOSP中可以获取到源代码)。
在运行的过程中,这个分区是read-only的。当然,一些Android设备,也允许在remount的情况下,
对system分区进行读写。 擦除这个分区,相当于删除整个安卓系统,会导致不能进入Main System,
但不会影响到Recovery。因此,可以通过进入Recovery程序或者bootloader程序中,升级安装一个新ROM。
Userdata:用户数据区,用户安装的应用程序会把数据保存在这里,
包含了用户的数据:联系人、短信、设置、用户安装的程序。
擦除这个分区,本质上等同于手机恢复出厂设置,也就是手机系统第一次启动时的状态,
或者是最后一次安装官方或第三方ROM后的状态。
在Recovery程序中进行的“data/factory reset ”操作就是在擦除这个分区。
正常情况下OTA是不会清除这里的数据的,指定要删除数据的除外。
Cache:系统缓存区,临时的保存应用数据(要把数据保存在这里,需要特地的app permission),
OTA的升级包也可以保存在这里。OTA升级过程可能会清楚这个分区的数据。一般来讲,Android差分包升级也需要依赖此分区存放一些中间文件。
Recovery:包括了一个完整Linux内核和一些特殊的recovery binary,可以读取升级文件用这些文件来更新其他的分区。
Misc:一个非常小的分区,4 MB左右。recovery用这个分区来保存一些关于升级的信息,
应对升级过程中的设备掉电重启的状况,Bootloader启动的时候,会读取这个分区里面的信息,以决定系统是否进Recovery System 或 Main System。

2、开机流程

3、开机BCB作用

4、Recovery模式中的两个通信接口
在Recovery服务中上述的三个实体之间的通信是必不可少的,他们相互之间又有以下两个通信接口。
(一)通过CACHE分区中的三个文件:
Recovery通过/cache/recovery/目录下的三个文件与mian system通信。具体如下
1/cache/recovery/command:
这个文件保存着Main system传给Recovery的命令行,每一行就是一条命令,支持一下几种的组合。
--send_intent=anystring //write the text out to recovery/intent
在Recovery结束时在finish_recovery函数中将定义的intent字符串作为参数传进来,并写入到/cache/recovery/intent中
--update_package=root:path //verify install an OTA package file
Main system将这条命令写入时,代表系统需要升级,在进入Recovery模式后,将该文件中的命令读取并写入BCB中,
然后进行相应的更新update.zip包的操作。
--wipe_data //erase userdata(and cache),then reboot。擦除用户数据。擦除data分区时必须要擦除cache分区。
--wipe_cache //wipe cache(butnot user data),then reboot。擦除cache分区。
2/cache/recovery/log:
Recovery模式在工作中的log打印。在recovery服务运行过程中,stdout以及stderr会重定位到/tmp/recovery.log
在recovery退出之前会将其转存到/cache/recovery/log中,供查看。
3/cache/recovery/intent:Recovery传递给Main system的信息。作用不详。

(二)通过BCB(Bootloader Control Block):
BCB是bootloader与Recovery的通信接口,也是Bootloader与Main system之间的通信接口。存储在flash中的MISC分区,
占用三个page,其本身就是一个结构体,具体成员以及各成员含义如下:
struct bootloader_message{
char command[32];
char status[32];
char recovery[1024];
};
1command成员:其可能的取值在上文已经分析过了,即当想要在重启进入Recovery模式时,会更新这个成员的值。
另外在成功更新后结束Recovery时,会清除这个成员的值,防止重启时再次进入Recovery模式。
2status:在完成相应的更新后,Bootloader会将执行结果写入到这个字段。
3recovery:可被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模式。

1、RecoverySystem类:
RecoverySystem类的源码所在文件路径为:gingerbread0919/frameworks/base/core/java/android/os/RecoverySystem.java。
我们关心的是installPackage(Context context,FilepackageFile)函数。这个函数首先根据我们传过来的包文件,获取这个包文件的绝对路径filename。
然后将其拼成arg=“--update_package=”+filename。它最终会被写入到BCB中。这个就是重启进入Recovery模式后,Recovery服务要进行的操作。
它被传递到函数bootCommand(context,arg)。
2、bootCommand():
在这个函数中才是Main System在重启前真正做的准备。主要做了以下事情,首先创建/cache/recovery/目录,
删除这个目录下的command和log(可能不存在)文件在sqlite数据库中的备份。
然后将上步中的arg命令写入到/cache/recovery/command文件中。
下一步就是真正重启了。接下来看一下在重启函数reboot中所做的事情。
3、pm.reboot():
重启之前先获得了PowerManager(电源管理)并进一步获得其系统服务。然后调用了pm.reboot(“recovery”)函数。
他就是/bionic/libc/bionic/reboot.cpp中的reboot函数。这个函数实际上是一个系统调用,
即__reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,mode,NULL);从这个函数我们可以看出前两个参数就代表了我们的组合键,
mode就是我们传过来的“recovery”。再进一步跟踪就到了汇编代码了,我们无法直接查看它的具体实现细节。
但可以肯定的是这个函数只将“recovery”参数传递过去了,之后将“boot-recovery”写入到了MISC分区的BCB数据块的command域中。
这样在重启之后Bootloader才知道要进入Recovery模式。
在这里我们无法肯定Main System在重启之前对BCB的recovery域是否进行了操作。其实在重启前是否更新BCB的recovery域是不重要的,
因为进入Recovery服务后,Recovery会自动去/cache/recovery/command中读取要进行的操作然后写入到BCB的recovery域中。
至此,Main System就开始重启并进入Recovery模式。在这之前Main System做的最实质的就是两件事,
一 、是将“boot-recovery”写入BCB的command域,
二、是将--update_package=/cache/update.zip”或则“--update_package=/sdcard/update.zip”写入/cache/recovery/command文件中。

从Bootloader开始如果没有组合键按下,
就从MISC分区读取BCB块的command域(在主系统时已经将“boot-recovery”写入)。然后就以Recovery模式开始启动。
与正常启动不同的是Recovery模式下加载的镜像是recovery.img。这个镜像同boot.img类似,也包含了标准的内核和根文件系统。
其后就与正常的启动系统类似,也是启动内核,然后启动文件系统。在进入文件系统后会执行/init,init的配置文件就是/init.rc。
这个配置文件来自/bootable/recovery/etc/init.rc。这个文件做的事情很简单:
1、设置环境变量。
2、建立etc连接。
3、新建目录,备用。
4、挂载/tmp为内存文件系统tmpfs
5、启动recovery(/sbin/recovery)服务。
6、启动adbd服务(用于调试)。
这里最重要的就是当然就recovery服务了。在Recovery服务中将要完成升级工作。

从recovery.cpp的main函数开始:
1.ui_init():Recovery服务使用了一个基于framebuffer的简单ui(miniui)系统。这个函数对其进行了简单的初始化。
在Recovery服务的过程中主要用于显示一个背景图片(正在安装或安装失败)和一个进度条(用于显示进度)。
另外还启动了两个线程,一个用于处理进度条的显示(progress_thread),另一个用于响应用户的按键(input_thread)。
2.get_arg():
(1)get_bootloader_message():主要工作是根据分区的文件格式类型(mtd或emmc)从MISC分区中读取BCB数据块到一个临时的变量中。
(2)然后开始判断Recovery服务是否有带命令行的参数(/sbin/recovery,根据现有的逻辑是没有的),若没有就从BCB中读取recovery域。
如果读取失败则从/cache/recovery/command中读取然后。这样这个BCB的临时变量中的recovery域就被更新了。
在将这个BCB的临时变量写回真实的BCB之前,又更新的这个BCB临时变量的command域为“boot-recovery”。
这样做的目的是如果在升级失败(比如升级还未结束就断电了)时,系统在重启之后还会进入Recovery模式,直到升级完成。
(3)在这个BCB临时变量的各个域都更新完成后使用set_bootloader_message()写回到真正的BCB块中。
3. parserargc/argv:解析获得参数。注册所解析的命令(register_update_command)
4. if(update_package):判断update_package是否有值,若有就表示需要升级更新包,此时就会调用install_package()
在这一步中将要完成安装实际的升级包。这是最为复杂,也是升级update.zip包最为核心的部分。
5. if(wipe_data/wipe_cache):这一步判断实际是两步,在源码中是先判断是否擦除data分区(用户数据部分)的,然后再判断是否擦除cache分区。值
得注意的是在擦除data分区的时候必须连带擦除cache分区。在只擦除cache分区的情形下可以不擦除data分区。

6.finish_recovery():
这是Recovery关闭并进入Main System的必经之路。其大体流程如下:
将intent(字符串)的内容作为参数传进finish_recovery中。如果有intent需要告知Main System,则将其写入/cache/recovery/intent中。
将内存文件系统中的Recovery服务的日志(/tmp/recovery.log)拷贝到cache(/cache/recovery/log)分区中,
以便告知重启后的Main System发生过什么。
擦除MISC分区中的BCB数据块的内容,以便系统重启后不在进入Recovery模式而是进入更新后的主系统。
删除/cache/recovery/command文件。这一步也是很重要的,因为重启后Bootloader会自动检索这个文件,如果未删除的话又会进入Recovery模式。
7. reboot():
这是一个系统调用。在这一步Recovery完成其服务重启并进入Main System。
这次重启和在主系统中重启进入Recovery模式调用的函数是一样的,但是其方向是不一样的。所以参数也就不一样。
查看源码发现,其重启模式是RB_AUTOBOOT。这是一个系统的宏。

  • 3
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值