出于对开源鸿蒙的好奇,笔者借助几位大佬的博文,根据小型系统的启动日志,对 Openharmony 运作流程进行了梳理。由于个人编译的是最新版本,其源码部分与参考博文中有较多出入,但实现机制是不变的。
参考的博文如下:
《OHOS3.0启动流程分析丨init阶段》
《鸿蒙系统的启动流程v2.0》
笔者在 qemu 上运行 qemu-system-small 系统,日志输出如下图
由于笔者最近刚了解了小部分的内核实现,因此在探究 OHOS 过程中,更多地是带着问题去进行对比学习。让笔者困惑的主要有几个问题:
1 . 内核在OHOS中扮演什么角色?
2 . 与应用程序 app 的执行有什么关系
3 . OHOS的app是如何执行的?
4 . 分布式功能是如何实现的?
由于对内核启动已经有了初步了解,因此本文重点不再探讨内核启动过程,而是自问自答一下,内核到 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如何启动进程的。简单地梳理一下:
其中,比较常见地如 foundation、appspawn、softbus_server 就是这么被拉起的,对应地,我们可以查看 OHOS 当前进程进行确认。如下图所示,可以看到上述几个进程的父进程 PPID 都是1号,即 init 进程
因此,若要进一步探究OHOS,我们可以从每个服务进程入手,根据编译构建文件,找到其源码进行学习