OpenHarmony-1.启动流程

  • OpenHarmony启动流程

1.kernel的启动

流程图如下所示:
  OpenHarmony(简称OH)的标准系统的底层系统是linux,所以调用如下代码:

c 复制代码
linux-5.10/init/main.c:
noinline void __ref rest_init(void)
{
	struct task_struct *tsk;
	int pid;

	rcu_scheduler_starting();
	/*
	 * We need to spawn init first so that it obtains pid 1, however
	 * the init task will end up wanting to create kthreads, which, if
	 * we schedule it before we create kthreadd, will OOPS.
	 */
	pid = kernel_thread(kernel_init, NULL, CLONE_FS);
	...
}
static int __ref kernel_init(void *unused)
{
    ...
	if (!try_to_run_init_process("/sbin/init") ||
	    !try_to_run_init_process("/etc/init") ||
	    !try_to_run_init_process("/bin/init") ||
	    !try_to_run_init_process("/bin/sh"))
		return 0;

	panic("No working init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/admin-guide/init.rst for guidance.");
}

  由于OH标准系统是基于kernel内核开发的,所以启动init进程,那么OH的init进程的入口为/base/startup/init/services/init/main.c中。

2.init 进程模块架构

  • 基础环境初始化:init 进程挂载 tmpfs 和 procfs,创建基本的 dev 设备节点,提供一个基本的根文件系统。

    • tmpfs:这是一个内存上的文件系统,用于存储临时数据,比如在启动期间创建的目录、缓存等。它不会持久化到磁盘,当系统重启时会自动清除。
    • procfs:这个文件系统提供内核运行时信息的接口,如进程列表、系统配置、硬件状态等。init进程通常会挂载procfs,以便在启动早期获取和管理这些信息。
  • 从/proc/cmdline中读取fstab分区表。

  • 热插拔事件监听:init 进程启动 ueventd 来监控内核中的设备热插拔事件,为新插入的 block 设备分区(如 system和 vendor 分区)创建相应的 dev 设备节点。当设备被插入或移除时,内核会通过 uevent(用户空间事件)机制发送消息给 ueventd。ueventd作为系统服务的一部分,负责监听这些netlink事件,并根据接收到的事件类型动态管理相应的设备节点。

  在OH中BUILD.gn用于编译构建模块的。

c 复制代码
 base/startup/init/services/init/standard/BUILD.gn:
 15 init_common_sources = [
 16   "../init_capability.c",
 17   "../init_common_cmds.c",
 18   "../init_common_service.c",
 19   "../init_config.c",
 20   "../init_group_manager.c",
 21   "../init_service_file.c",
 22   "../init_service_manager.c",
 23   "../init_service_socket.c",
 24   "../main.c",
 25 ]

 33 ohos_executable("init") {
 34   sources = [
 35     "../adapter/init_adapter.c",
 36     "../standard/device.c",
 37     "../standard/fd_holder_service.c",
 38     "../standard/init.c",
 39     "../standard/init_cmdexecutor.c",
 40     "../standard/init_cmds.c",
 41     "../standard/init_control_fd_service.c",
 42     "../standard/init_jobs.c",
 43     "../standard/init_mount.c",
 44     "../standard/init_reboot.c",
 45     "../standard/init_service.c",
 46     "../standard/init_signal_handler.c",
 47     "../standard/switch_root.c",
 48   ]
 49
 50   modulemgr_sources = [
 51     "//base/startup/init/interfaces/innerkits/hookmgr/hookmgr.c",
 52     "//base/startup/init/interfaces/innerkits/modulemgr/modulemgr.c",
 53   ]
 54   sources += modulemgr_sources

  从BUILD.gn看到OH标准系统的init进程的入口就是init_common_sources的main.c。

c 复制代码
base/startup/init/services/init/main.c:
#include <signal.h>
#include "init.h"
#include "init_log.h"

static const pid_t INIT_PROCESS_PID = 1;

int main(int argc, char * const argv[])
{
    int isSecondStage = 0;
    (void)signal(SIGPIPE, SIG_IGN);
    // Number of command line parameters is 2
    //从kernel启动的init进程并未携带任何参数,这里是init的第一阶段
    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;
    }
    EnableInitLog(INIT_INFO);
    //第一次这里走的是SystemPrepare
    if (isSecondStage == 0) {
        SystemPrepare();
    } else {
        LogInit();
    }
    SystemInit();
    //启动rcs进程
    SystemExecuteRcs();
    SystemConfig();
    SystemRun();
    return 0;
}

  这里将init进程的代码分成了通用的和特有的两部分,共同的代码均在 /base/startup/init/services/init/文件夹下,其中有lite/和standard/分别用来构建小型系统和标准系统的init进程。这里主要分析标准进程的启动流程。

2.1.SystemPrepare

  由于从kernel进程启动的init进程并未传递任何参数,所以会先执行SystemPrepare:

c 复制代码
base/startup/init/services/init/standard/init.c:
225 void SystemPrepare(void)
226 {
227     MountBasicFs();     //挂载一些基本目录并创建一些设备节点
228     CreateDeviceNode();
229     LogInit();
230     // Make sure init log always output to /dev/kmsg.
231     EnableDevKmsg();
232     INIT_LOGI("Start init first stage.");
233     HookMgrExecute(GetBootStageHookMgr(), INIT_FIRST_STAGE, NULL, NULL);
234     // Only ohos normal system support
235     // two stages of init.
236     // If we are in updater mode, only one stage of init.
237     if (InUpdaterMode() == 0) {  //检查是否处于升级模式,如果没有处于升级模式,就进入init第二阶段
238         StartInitSecondStage();
239     }
240 }
  • SystemPrepare主要工作:
    挂载一些基本目录,比如/dev,/mnt,/storage,/dev/pts,/proc,/sys,/sys/fs/selinux,接着创建一些设备节点比如/dev/null,/dev/random,/dev/urandom 等,检查系统是否处于升级模式,如果不处于升级模式就启动init的第二阶段。

2.2.StartInitSecondStage

c 复制代码
base/startup/init/services/init/standard/init.c
static void StartInitSecondStage(void)
{
    int requiredNum = 0;
    //从/proc/cmdline中读取fstab分区表
    Fstab *fstab = LoadRequiredFstab();
    char **devices = (fstab != NULL) ? GetRequiredDevices(*fstab, &requiredNum) : NULL;
    if (devices != NULL && requiredNum > 0) {
        //启动Ueventd进程
        int ret = StartUeventd(devices, requiredNum);
        if (ret == 0) {
            //挂载分区
            ret = MountRequriedPartitions(fstab);
        }
        FreeStringVector(devices, requiredNum);
        devices = NULL;
        ReleaseFstab(fstab);
        fstab = NULL;
        if (ret < 0) {
            // If mount required partitions failure.
            // There is no necessary to continue.
            // Just abort
            INIT_LOGE("Mount required partitions failed; please check fstab file");
            // Execute sh for debugging
#ifndef STARTUP_INIT_TEST
            execv("/bin/sh", NULL);
            abort();
#endif
        }
    }

    if (fstab != NULL) {
        ReleaseFstab(fstab);
        fstab = NULL;
    }
    // It will panic if close stdio before execv("/bin/sh", NULL)
    CloseStdio();
    //启动init进程的第二阶段
    INIT_LOGI("Start init second stage.");
    SwitchRoot("/usr");
    // Execute init second stage
    char * const args[] = {
        "/bin/init",
        "--second-stage",
        NULL,
    };
    //启动init进程并传递参数--second-stage 
    if (execv("/bin/init", args) != 0) {
        INIT_LOGE("Failed to exec \"/bin/init\", err = %d", errno);
        exit(-1);
    }
}
  • StartInitSecondStage函数主要就是读取分区表并挂载同时启动Ueventd进程接着再次调用init并传递--second-stage参数。这里的执行execv函数将不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行。所以接下来的都是init第二阶段的执行过程。再次启动init进程后,当然还是走到了/base/startup/init/services/init/main.c不过不同的是由于携带了--second-stage参数,所以会走到LogInit。接着串行执行SystemInit, SystemExecuteRcs,SystemConfig和SystemRun。

startup/init/services/etc/init.cfg

refer to

相关推荐
予昊2 分钟前
Linux 资源控制实战分析:用 LXC 亲手造一个 “迷你虚拟机“
linux·运维·服务器
恋猫de小郭5 分钟前
Dart 3.13 大更新,感觉比 Flutter 更带劲
android·前端·flutter
IT机器猫10 分钟前
RabbitMQ基础二
java·spring boot·分布式·spring cloud·log4j·rabbitmq·springamqp
卷心菜的学习路20 分钟前
基于 Spring Cloud Tencent 元数据路由实现个人开发泳道,告别频繁部署
java·spring cloud
大锅盖133 分钟前
HarmonyOS 6.1.1 ArkWeb:交付门户下载资料前-为什么必须先登记下载代理与来源字段
android·华为·harmonyos
hyf32663340 分钟前
低成本站群泛程序部署:单服务器托管百站稳定运行教程
运维·服务器·前端·搜索引擎·蜘蛛池
xieliyu.1 小时前
UPD协议结构以及开发中注意事项
java·开发语言·笔记·java-ee
张宇Joaquin1 小时前
openEuler编译和更换内核的方法
java·linux·服务器
小此方1 小时前
Re:Linux系统篇(五十六)线程篇 · 九:基于阻塞队列的生产者消费者模型与条件变量
linux·运维·服务器
程序员黑豆1 小时前
Java正则表达式详解
java·前端·ai编程