- OpenHarmony启动流程
1.kernel的启动
流程图如下所示:
OpenHarmony(简称OH)的标准系统的底层系统是linux,所以调用如下代码:
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用于编译构建模块的。
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。
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:
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
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
- https://huaweicloud.csdn.net/64df3b15dc60580edc7735f4.html