> 文档中心 > 从启动日志简单梳理Openharmony启动流程

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


前言

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

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

编译运行

笔者在 qemu 上运行 qemu-system-small 系统,日志输出如下图
在这里插入图片描述

问题描述

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

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

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

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

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

init 进程

由于对内核启动已经有了初步了解,因此本文重点不再探讨内核启动过程,而是自问自答一下,内核到 app 的过程是怎样的。可以从日志中看到关于内核log内容:
在这里插入图片描述

在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 执行相应地进程。
在这里插入图片描述
跟踪 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】执行相应的二进制文件
在这里插入图片描述
至此,我们可以理清OHOS如何启动进程的。简单地梳理一下:

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

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