从启动日志简单梳理Openharmony启动流程

前言

出于对开源鸿蒙的好奇,笔者借助几位大佬的博文,根据小型系统的启动日志,对 Openharmony 运作流程进行了梳理。由于个人编译的是最新版本,其源码部分与参考博文中有较多出入,但实现机制是不变的。

参考的博文如下:
《OHOS3.0启动流程分析丨init阶段》
《鸿蒙系统的启动流程v2.0》

编译运行

笔者在 qemu 上运行 qemu-system-small 系统,日志输出如下图
从启动日志简单梳理Openharmony启动流程_第1张图片

问题描述

由于笔者最近刚了解了小部分的内核实现,因此在探究 OHOS 过程中,更多地是带着问题去进行对比学习。让笔者困惑的主要有几个问题:

1 . 内核在OHOS中扮演什么角色?

2 . 与应用程序 app 的执行有什么关系

3 . OHOS的app是如何执行的?

4 . 分布式功能是如何实现的?

init 进程

由于对内核启动已经有了初步了解,因此本文重点不再探讨内核启动过程,而是自问自答一下,内核到 app 的过程是怎样的。可以从日志中看到关于内核log内容:
从启动日志简单梳理Openharmony启动流程_第2张图片

在Linux内核学习系列中,可以得知内核启动后会执行 init() 方法,进而通过 fork+exec 的方式启动 /bin/sh 这一 shell 程序。在 OHOS 中,将 /bin/sh 更换成了 /bin/init。而其源码的入口地址位于 base/startup/init_lite/services/init/main.c

int main(int argc, char * const argv[])
{
    int isSecondStage = 0;
    // Number of command line parameters is 2
    if (argc == 2 && (strcmp(argv[1], "--second-stage") == 0)) {
        isSecondStage = 1;
    }
    if (getpid() != INIT_PROCESS_PID) {
        INIT_LOGE("Process id error %d!", getpid());
        return 0;
    }

    if (isSecondStage == 0) {
        SystemPrepare();
    } else {
        LogInit();
    }
    SystemInit();
    SystemExecuteRcs();
    SystemConfig();
    SystemRun();
    return 0;
}

由此可见, init 主要执行 SystemInit(),SystemExecuteRcs() ,SystemConfig(),SystemRun()

在 SystemInit() 中主要初始化了信号 SignalInit(),并创建了 “/dev/unix/socket”

void SystemInit(void)
{
    SignalInit();
    MakeDirRecursive("/dev/unix/socket", S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
}

SystemExecuteRcs() 用于模拟 Linux 执行 EXEC_RCS 功能

void SystemExecuteRcs(void)
{
#if (defined __LINUX__) && (defined NEED_EXEC_RCS_LINUX)
    pid_t retPid = fork();
    if (retPid < 0) {
        INIT_LOGE("ExecuteRcs, fork failed! err %d.", errno);
        return;
    }

    // child process
    if (retPid == 0) {
        INIT_LOGI("ExecuteRcs, child process id %d.", getpid());
        if (execle("/bin/sh", "sh", "/etc/init.d/rcS", NULL, NULL) != 0) {
            INIT_LOGE("ExecuteRcs, execle failed! err %d.", errno);
        }
        _exit(0x7f); // 0x7f: user specified
    }

    // init process
    sem_t sem;
    if (sem_init(&sem, 0, 0) != 0) {
        INIT_LOGE("ExecuteRcs, sem_init failed, err %d.", errno);
        return;
    }
    SignalRegWaitSem(retPid, &sem);

    // wait until rcs process exited
    if (sem_wait(&sem) != 0) {
        INIT_LOGE("ExecuteRcs, sem_wait failed, err %d.", errno);
    }
#endif
}

SystemConfig() 主要用于对配置文件的解析。从官方文档可以得知,OHOS 可以通过后缀为cfg的配置文件执行一系列命令及启动服务。

void SystemConfig(void)
{
    InitServiceSpace();
    // read config
    ReadConfig();

    // dump config
#ifdef OHOS_SERVICE_DUMP
    DumpAllServices();
#endif

    // execute init
    DoJob("pre-init");

    ReleaseAllJobs();
}

其中 ReadConfig()->ParseInitCfg() 会对 cfg 配置文件进行解析。其解析细节笔者没有深究,在前言提及的博文中有大佬对此进行了详细地分析。笔者更多地只是想知道大概怎么一回事

int ParseInitCfg(const char *configFile, void *context)
{
    UNUSED(context);
    INIT_LOGI("ParseInitCfg %s", configFile);
    static const char *excludeCfg[] = {
        "/system/etc/init/weston.cfg"
    };
    for (int i = 0; i < (int)ARRAY_LENGTH(excludeCfg); i++) {
        if (strcmp(configFile, excludeCfg[i]) == 0) {
            INIT_LOGE("ParseInitCfg %s not support", configFile);
            return 0;
        }
    }
    char *fileBuf = ReadFileToBuf(configFile);
    INIT_ERROR_CHECK(fileBuf != NULL, return -1, "Failed to read file content %s", configFile);

    cJSON *fileRoot = cJSON_Parse(fileBuf);
    INIT_ERROR_CHECK(fileRoot != NULL, free(fileBuf);
        return -1, "Failed to parse json file %s", configFile);

    ParseInitCfgContents(configFile, fileRoot);
    cJSON_Delete(fileRoot);
    free(fileBuf);
    return 0;
}

从日志中可以找到相关 log。可以看到 ParseInitCfg 会对 /etc/init.cfg 进行解析,并且会进一步解析 /system/etc 及 /vendor/etc/ 下的配置文件。进而会通过 StartServiceByName 执行相应地进程。
从启动日志简单梳理Openharmony启动流程_第3张图片
跟踪 StartServiceByName() 发现其通过 ServiceStart() —> ServiceExec() —> execv() 根据文件启动一个服务进程

void StartServiceByName(const char *servName)
{
    INIT_LOGE("StartServiceByName Service %s", servName);
    Service *service = GetServiceByName(servName);
    if (service == NULL) {
        service = GetServiceByExtServName(servName);
    }
    INIT_ERROR_CHECK(service != NULL, return, "Cannot find service %s.", servName);

    if (ServiceStart(service) != SERVICE_SUCCESS) {
        INIT_LOGE("Service %s start failed!", servName);
    }
    // After starting, clear the extra parameters.
    FreeStringVector(service->extraArgs.argv, service->extraArgs.count);
    service->extraArgs.argv = NULL;
    service->extraArgs.count = 0;
    return;
}

对此,我们可以通过 cat /etc/init.cfg 看 init 进程会执行哪些服务进程(cfg的语法在此不展开)。简单地说,init进程 会根据 start 【service_name】执行相应的二进制文件
从启动日志简单梳理Openharmony启动流程_第4张图片
至此,我们可以理清OHOS如何启动进程的。简单地梳理一下:

  1. 内核进行初始化
  2. 切换用户态并执行 Init 进程
  3. init 进程解析 cfg 配置文件进行相应进程的启动

其中,比较常见地如 foundation、appspawn、softbus_server 就是这么被拉起的,对应地,我们可以查看 OHOS 当前进程进行确认。如下图所示,可以看到上述几个进程的父进程 PPID 都是1号,即 init 进程
从启动日志简单梳理Openharmony启动流程_第5张图片
因此,若要进一步探究OHOS,我们可以从每个服务进程入手,根据编译构建文件,找到其源码进行学习

你可能感兴趣的:(内核,openharmony)