OpenHarmony——分区切换之reboot源码解析 原创 精华
作者:曹芝展
简介
系统在执行升级操作时,执行指令reboot updater,对misc分区写入相关信息,然后重启系统进行分区切换操作,加载updater分区并执行OTA升级,本文仅介绍reboot的操作原理,以下内容主要基于v3.0-Release版进行分析。
代码路径
base\startup\init_lite\services\cmds\reboot\init_cmd_reboot.c
base\startup\init_lite\interfaces\innerkits\reboot\init_reboot.c
base\startup\init_lite\services\src\init_reboot.c
目录结构
base/startup/init_lite/ # init组件
├── LICENSE
└── services
├── include # init组件头文件目录
├── src # init组件源文件目录
└── test # init组件测试用例源文件目录
└── unittest
vendor
└──huawei
└──camera
└──init_configs # init配置文件目录(json格式,镜像烧写后部署于/etc/init.cfg)
init_cmd_reboot.c文件位于init组件中,init组件负责处理从内核加载第一个用户态进程开始,到第一个应用程序启动之间的系统服务进程启动过程。启动恢复子系统除负责加载各系统关键进程之外,还需在启动的同时设置其对应权限,并在子进程启动后对指定进程实行保活(若进程意外退出要重新启动),对于特殊进程意外退出时,启动恢复子系统还要执行系统复位操作。
流程图
源码解读
1.init_cmd_reboot.c文件的main函数
在命令行输入reboot updater指令,会调用到下面的函数中:
int main(int argc, char* argv[])
{
if (argc > REBOOT_CMD_NUMBER) { //判断输入参数是否合法
printf("usage: reboot shutdown\n reboot updater\n reboot updater[:options]\n reboot\n");
return 0;
}
if (argc == REBOOT_CMD_NUMBER && strcmp(argv[1], "shutdown") != 0 &&
strcmp(argv[1], "updater") != 0 &&
strncmp(argv[1], "updater:", strlen("updater:")) != 0) {
printf("usage: reboot shutdown\n reboot updater\n reboot updater[:options]\n reboot\n");
return 0;
}
int ret;
if (argc == REBOOT_CMD_NUMBER) {
ret = DoReboot(argv[1]);
} else {
ret = DoReboot(NULL);
}
if (ret != 0) {
printf("[reboot command] DoReboot Api return error\n");
} else {
printf("[reboot command] DoReboot Api return ok\n");
}
while (1) {
pause();
}
return 0;
}
main函数中主要作用是识别reboot指令,执行DoReboot操作。
2.DoReboot函数中的libuv库
int DoReboot(const char *cmdContent)
{
uid_t uid1 = getuid();
uid_t uid2 = geteuid();//鉴权操作,reboot需要在root权限下执行
if (uid1 != 0 || uid2 != 0) {
INIT_LOGE("uid1=%d, uid2=%d, user MUST be root, error!", uid1, uid2);
return -1;
}
//省略部分不重要代码
/...
.../
if (snprintf_s(value, MAX_REBOOT_NAME_SIZE, MAX_REBOOT_NAME_SIZE - 1, "%s%s", "reboot,", cmdContent) < 0) {
INIT_LOGE("DoReboot api error, MAX_REBOOT_NAME_SIZE is not enough");
return -1;
}
if (SystemSetParameter("sys.powerctrl", value) != 0) {
INIT_LOGE("DoReboot Api SystemSetParameter error");
return -1;
}
return 0;
}
将SystemSetParameter函数展开,其最终调用到了StartRequest,我们来看下StartRequest:
static int StartRequest(int cmd, RequestNode *request)
{
PARAM_CHECK(request != NULL, return -1, "Invalid request");
request->result = -1;
request->msg.type = cmd;
request->loop = uv_loop_new();
PARAM_CHECK(request->loop != NULL, return -1, "StartRequest uv_loop_new failed");
uv_pipe_init(request->loop, &request->handle, 1);
uv_pipe_connect(&request->connect, &request->handle, PIPE_NAME, OnConnection);
uv_run(request->loop, UV_RUN_DEFAULT);
uv_loop_delete(request->loop);
int result = request->result;
free(request);
return result;
}
实际上是通过StartRequest函数是通过libuv库的pipe管道拉起了sys.powerctrl服务之后,再通过该服务调用到具体的do reboot操作。
备注:libuv是一个高性能事件驱动库,屏蔽了各种操作系统的差异从而提供了统一的API。libuv严格使用异步、事件驱动的编程风格。其核心工作是提供事件循环及基于 I/O或其他活动事件的回调机制。libuv库包含了诸如计时器、非阻塞网络支持、异步文件系统访问、线程创建、子进程等核心工具,关于libuv库的详细内容此次不做详解。
3.真正的DoReboot函数
void DoReboot(const char *value)
{
if (value == NULL) {
INIT_LOGE("DoReboot value = NULL");
return;
}
INIT_LOGI("DoReboot value = %s", value);
if (strlen(value) > MAX_VALUE_LENGTH || strlen(value) < strlen("reboot") || strlen(value) == strlen("reboot,")) {
INIT_LOGE("DoReboot reboot value error, value = %s.", value);
return;
}
const char *valueData = NULL;
if (strncmp(value, "reboot,", strlen("reboot,")) == 0) {
valueData = value + strlen("reboot,");
} else if (strlen(value) < strlen("reboot,") && strncmp(value, "reboot", strlen("reboot")) == 0) {
valueData = NULL;
} else {
INIT_LOGE("DoReboot reboot value = %s, must started with reboot ,error.", value);
return;
}
if (valueData != NULL && strncmp(valueData, "shutdown", strlen("shutdown")) != 0 &&
strncmp(valueData, "updater:", strlen("updater:")) != 0 &&
strncmp(valueData, "updater", strlen("updater")) != 0) {
INIT_LOGE("DoReboot value = %s, parameters error.", value);
return;
}
//在执行reboot updater之前需停止所有的服务
StopAllServicesBeforeReboot();
//挂载了vendor和data分区,则需先卸载这两个分区
if (GetMountStatusForMountPoint("/vendor") != 0) {
if (umount("/vendor") != 0) {
INIT_LOGE("DoReboot umount vendor failed! errno = %d.", errno);
}
}
if (GetMountStatusForMountPoint("/data") != 0) {
if (umount("/data") != 0) {
INIT_LOGE("DoReboot umount data failed! errno = %d.", errno);
}
}
int ret = DoRebootCore(valueData);
if (ret != 0) {
INIT_LOGE("DoReboot value = %s, error.", value);
}
return;
}
4.DoRebootCore函数中区分shutdown/upadter
static int DoRebootCore(const char *valueData)
{
if (valueData == NULL) {
reboot(RB_AUTOBOOT);
return 0;
} else if (strncmp(valueData, "shutdown", strlen("shutdown")) == 0) {
reboot(RB_POWER_OFF);
return 0;
} else if (strncmp(valueData, "updater", strlen("updater")) == 0) {
int ret = UpdateUpdaterStatus(valueData);
if (ret == 0) {
reboot(RB_AUTOBOOT);
return 0;
}
} else {
return -1;
}
return 0;
}
若是shutdown指令则直接关机,updater指令则需更新信息到misc分区中,然后再重启系统。
5.UpdateUpdaterStatus函数
static int UpdateUpdaterStatus(const char *valueData)
{
const char *miscFile = "/dev/block/platform/soc/10100000.himci.eMMC/by-name/misc";
struct RBMiscUpdateMessage msg;
bool ret = RBMiscReadUpdaterMessage(miscFile, &msg);
if (!ret) {
INIT_LOGE("RBMiscReadUpdaterMessage error.");
return -1;
}
if (snprintf_s(msg.command, MAX_COMMAND_SIZE, MAX_COMMAND_SIZE - 1, "%s", "boot_updater") == -1) {
INIT_LOGE("RBMiscWriteUpdaterMessage error");
return -1;
}
if (strlen(valueData) > strlen("updater:") && strncmp(valueData, "updater:", strlen("updater:")) == 0) {
if (snprintf_s(msg.update, MAX_UPDATE_SIZE, MAX_UPDATE_SIZE - 1, "%s", valueData + strlen("updater:")) == -1) {
INIT_LOGE("RBMiscWriteUpdaterMessage error");
return -1;
}
ret = RBMiscWriteUpdaterMessage(miscFile, &msg);
if (ret != true) {
INIT_LOGE("RBMiscWriteUpdaterMessage error");
return -1;
}
} else if (strlen(valueData) == strlen("updater") && strncmp(valueData, "updater", strlen("updater")) == 0) {
ret = RBMiscWriteUpdaterMessage(miscFile, &msg);
if (ret != true) {
INIT_LOGE("RBMiscWriteUpdaterMessage error");
return -1;
}
} else {
return -1;
}
return 0;
}
(1) miscFile的文件路径需根据实际的文件路径进行修改;
(2) RBMiscReadUpdaterMessage根据miscFile的路径去获取misc文件的信息,一般情况下会是空的,因为并没有写入什么信息进去;
(3) 拼接boot_updater字符串,放到msg的command字段;
(4) RBMiscWriteUpdaterMessage函数将上文拼接的boot_updater字符串写入到misc文件中去;
至此我们的misc文件中保存着boot_updater的信息,在系统启动时切换分区会用到该信息。
执行效果
在命令行中输入指令reboot updater,相关日志如下:
[ 121.829824] c0 [pid=1][init_reboot.c:216][Init][INFO] DoReboot value = reboot,updater
[ 121.861187] c0 !! lit:we gonna plugin cpu1 !!
[ 121.867579] c0 [pid=1][param_service.c:263][Init][INFO] SystemWriteParam name init.svc.ueventd value: stopping
[ 121.878943] c0 [pid=1][trigger_processor.c:165][Init][INFO] PostParamTrigger init.svc.ueventd success
[ 121.888205] c0 [pid=1][init_service.c:253][Init][INFO] stop service ueventd, pid 216.
[ 121.898739] c0 [pid=1][param_service.c:263][Init][INFO] SystemWriteParam name init.svc.modem_control value: stopping
[ 121.909471] c0 [pid=1][trigger_processor.c:165][Init][INFO] PostParamTrigger init.svc.modem_control success
[ 121.920048] c1 CPU1: update max cpu_capacity 1024
[ 121.921607] c0 [pid=1][init_service.c:253][Init][INFO] stop service modem_control, pid 312.
[ 121.935159] c0 [pid=1][param_service.c:263][Init][INFO] SystemWriteParam name init.svc.slogmodem value: stopping
...
[ 122.764609] c1 [pid=1][init_service.c:253][Init][INFO] stop service installs, pid 260.
[ 122.765543] c1 [pid=1][param_service.c:263][Init][INFO] SystemWriteParam name init.svc.wifi_hal_service value: stopping
[ 122.765613] c1 [pid=1][trigger_processor.c:165][Init][INFO] PostParamTrigger init.svc.wifi_hal_service success
[ 122.765622] c1 [pid=1][init_service.c:253][Init][INFO] stop service wifi_hal_service, pid 279.
[ 123.382642] c2 sprd-powerdebug power-debug: ###--PMU submodule power states--###
[ 123.389967] c2 sprd-powerdebug power-debug: ##--reg offset:0x00bc value:0x00000000:
[ 123.397928] c3 [pid=1][init_reboot.c:248][Init][ERROR] DoReboot umount data failed! errno = 16.
[ 123.405106] c2 sprd-powerdebug power-debug: #--PD_CA53_TOP STATE:0x0
[ 123.405111] c2 sprd-powerdebug power-debug: #--PD_CA53_C0 STATE:0x0
[ 123.405117] c2 sprd-powerdebug power-debug: #--PD_CA53_C1 STATE:0x0
[ 123.405121] c2 sprd-powerdebug power-debug: #--PD_CA53_C2 STATE:0x0
[ 123.405126] c2 sprd-powerdebug power-debug: #--PD_CA53_C3 STATE:0x0
[ 123.405130] c2 sprd-powerdebug power-debug: #--PD_AP_SYS STATE:0x0
[ 123.405140] c2 sprd-powerdebug power-debug: ##--reg offset:0x00c0 value:0x0e001ce7:
[ 123.405145] c2 sprd-powerdebug power-debug: #--PD_WTLCP_HU3GE_A STATE:0x7
[ 123.405149] c2 sprd-powerdebug power-debug: #--PD_WTLCP_TGDSP STATE:0x7
[ 123.405154] c2 sprd-powerdebug power-debug: #--PD_WTLCP_LDSP STATE:0x7
[ 123.405159] c2 sprd-powerdebug power-debug: #--PD_WFI_WRAP STATE:0x0
[ 123.405163] c2 sprd-powerdebug power-debug: #--PD_WTLCP_LTE_P2 STATE:0x0
[ 123.405168] c2 sprd-powerdebug power-debug: #--PD_WTLCP_LTE_P1 STATE:0x7
[ 123.405174] c2 sprd-powerdebug power-debug: ##--reg offset:0x00c4 value:0x0e0000e7:
[ 123.405179] c2 sprd-powerdebug power-debug: #--PD_WTLCP_SYS STATE:0x7
[ 123.405184] c2 sprd-powerdebug power-debug: #--PD_PUBCP_SYS STATE:0x7
[ 123.405190] c2 sprd-powerdebug power-debug: #--PD_WTLCP_LTE_P3 STATE:0x0
[ 123.405194] c2 sprd-powerdebug power-debug: #--PD_WTLCP_LTE_P4 STATE:0x0
[ 123.405199] c2 sprd-powerdebug power-debug: #--PD_PUB_SYS STATE:0x0
[ 123.405204] c2 sprd-powerdebug power-debug: #--PD_GNSS_WRAP STATE:0x7
[ 123.405218] c2 sprd-powerdebug power-debug: PM: has wakeup events in progress:
[ 123.655758] c3 mmc0: clock 0Hz busmode 2 powermode 0 cs 0 Vdd 0 width 1 timing 0
[ 123.671430] c3 sprd-sdhcr10 20600000.sdio: there is no signal voltage!
[ 123.687965] c3 No poweroff alarm found
[ 123.702537] c3 vbc-rxpx-codec-sc27xx sound@0: ASoC: DAPM unknown pin inter Spk1 PA
[ 123.716967] c3 WCN BASE: enter
[ 123.731343] c3 WCN BASE: ctrl_shutdown_reg[0] = 0x57c, val=0x80
[ 123.731362] c3 WCN BASE: ctrl_reg[0] = 0x57c, val=0x0
[ 123.748959] c3 WCN BASE: ctrl_shutdown_reg[1] = 0xb0, val=0x800
[ 123.768481] c3 WCN BASE: ctrl_reg[1] = 0xb0, val=0xc00
[ 123.785221] c3 WCN BASE: enable = 0, ret = 0
[ 123.820360] c3 WCN BASE: enable=0,en_count=0,ret=0,btwf=1,gnss=0
[ 123.820366] c3 WCN BASE: finish!
[ 123.820369] c3 WCN BASE: dev name wcn_btwf
[ 123.820818] c3 [drm] sprd_dsi_encoder_disable()
[ 123.834277] c3 [drm][ dpu_stop] dpu stop
[ 123.891709] c3 [drm] sprd_panel_disable()
[ 124.070280] c3 [drm] dphy ulps enter
[ 124.080117] c3 [drm] sprd_panel_unprepare()
[ 124.119843] c3 [drm] dphy suspend OK
[ 124.130839] c3 [drm] dsi suspend OK
[ 124.145576] c3 [drm] sprd_crtc_atomic_disable()
[ 124.156786] c3 [drm] dpu has already powered off
[ 124.239661] c3 FLASH_DRV: 1: 252 ctrl: led_index 3 status 0x0
[ 124.251967] c3 FLASH_DRV: 1: 252 ctrl: led_index 3 status 0x0
[ 124.252379] c0 Disabling non-boot CPUs ...
[ 124.390047] c0 CPU0: update max cpu_capacity 1024
[ 125.128590] c0 CPU1 killed.
[ 125.140088] c0 !! lit:we gonna plugin cpu1 !!
[ 125.150086] c0 dev->offline error, ret=1
[ 125.191109] c3 CPU3: update max cpu_capacity 1024
[ 125.233114] c0 CPU2 killed.
[ 125.380724] c0 CPU3 killed.
[ 125.384470] c0 reboot: Restarting system
总结
本文仅介绍了在升级时执行reboot updater指令关机之前的一些流程和源码,除了需要向misc分区写入关键信息之外还需要关闭服务和卸载分区,另外需注意根据板子中misc文件的实际位置去修改msicfile变量的路径,重启之后的详细流程将放到下一篇讲解。
更多原创内容请关注:深开鸿技术团队
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
成为大佬之路从读老师的原理进行解读开始!
谢谢分享,学到了
非常感谢分享,学到了学到了