作者:曹芝展

简介

系统在执行升级操作时,执行指令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组件负责处理从内核加载第一个用户态进程开始,到第一个应用程序启动之间的系统服务进程启动过程。启动恢复子系统除负责加载各系统关键进程之外,还需在启动的同时设置其对应权限,并在子进程启动后对指定进程实行保活(若进程意外退出要重新启动),对于特殊进程意外退出时,启动恢复子系统还要执行系统复位操作。

流程图

流程图.png

源码解读

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开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

想了解更多关于开源的内容,请访问:

51CTO 开源基础软件社区

https://ost.51cto.com/#bkwz