子系统架构

init组件处理从内核加载一个用户态进程开始,到第一个应用程序启动之间的系统服务进程启动过程。启动恢复子系统除负责加载各系统关键进程之外,还需在启动的同时设置其对应权限,并在子进程启动后对指定进程实行保活(若进程意外退出要重新启动),对于特殊进程意外退出时,启动恢复子系统还要执行系统复位操作。OHOS的启动框架图如下图所示。

子系统的目录结构如下:

base/startup/init/
├── device_info         # 提供设备信息的SA服务
├── initsync            # 同步命令(小型系统)
├── interfaces          # 对外接口
├── scripts             # 脚本(LiteOS系统使用)
├── services
│   ├── begetctl        # 命令集合。提供服务的dump,拉起等一系列命令
│   ├── etc             # init配置文件目录(标准系统)
│   ├── etc_lite        # init配置文件目录(小型系统)
│   ├── include         # init头文件目录
│   ├── init            # init核心功能源码
│   │   ├── adapter     # 内核适配层
│   │   ├── include     # 头文件目录
│   │   ├── lite        # init核心功能源码(小型系统)
│   │   └── standard    # init核心功能源码(标准系统)
│   ├── log             # init日志部件。
│   ├── loopevent       # 事件库
│   │   ├── include     # 头文件目录
│   │   ├── loop        # 基于epoll封装的I/O多路复用接口
│   │   ├── signal      # 信号处理接口封装。提供信号的添加,handler注册等功能
│   │   ├── socket      # socket通信接口
│   │   ├── task        # 事件的抽象任务,如signal, timer等事件都要创建对应task
│   │   ├── timer       # 定时器接口
│   │   └── utils       # loopevent通用接口
│   ├── modules         # 插件化模块
│   │   ├── bootchart   # bootchart插件化源码
│   │   ├── bootevent   # bootevent插件化源码
│   │   ├── init_hook   # init提供的回调函数
│   │   ├── reboot      # reboot插件化源码
│   │   ├── seccomp     # seccomp插件化源码
│   │   └── selinux     # selinux插件化源码
│   ├── param           # 系统参数部件
│   └── utils           # init通用接口
├── test                # init组件测试用例源文件目录
├── ueventd             # ueventd服务源码
│   ├── etc             # ueventd配置文件目录
│   ├── include         # ueventd头文件目录
│   ├── lite            # ueventd核心功能源码(小型系统)
│   └── standard        # ueventd核心功能源码(标准系统)
└── watchdog            # 看门狗服务源码

查看//base/startup/init/services/init/standard/BUILD.gn文件中构建规则,用于编译init进程和init_early进程。

ohos_executable("init_early") { # 编译init_early执行程序
  sources = [
    "//base/startup/init/interfaces/innerkits/hookmgr/hookmgr.c",
    "//base/startup/init/services/log/init_commlog.c",
    "bootstagehooker.c",
    "device.c",
    "init_firststage.c",
    "init_mount.c",
    "main_early.c",
    "switch_root.c",
  ]
  
  ......
  
  install_images = [ "ramdisk" ] #安装到ramdisk文件系统中
  install_enable = true
  part_name = "init"
}

ohos_executable("init") { #编译init执行程序
  sources = [
    "../adapter/init_adapter.c",
    "../standard/device.c",
    "../standard/fd_holder_service.c",
    "../standard/init.c",
    "../standard/init_cmdexecutor.c",
    "../standard/init_cmds.c",
    "../standard/init_control_fd_service.c",
    "../standard/init_firststage.c",
    "../standard/init_jobs.c",
    "../standard/init_mount.c",
    "../standard/init_reboot.c",
    "../standard/init_service.c",
    "../standard/init_signal_handler.c",
    "../standard/switch_root.c",
    "bootstagehooker.c",
  ]
  
  ......
  
  install_images = [ # 安装打包到/system文件系统和updater文件系统中
    "system",
    "updater",
  ]
  
  deps +=
      [ "//base/startup/init/services/modules/crashhandler:libcrashhandler" ]
  install_enable = true
  part_name = "init"
  subsystem_name = "startup"
}

Linux内核启动流程

在Linux-5.10中,start_kernel函数开启内核阶段的初始化过程。

//在Linux-5.10内核中,start_kernel函数开启了系统内核的初始化过程。
asmlinkage __visible void __init __no_sanitize_address start_kernel(void)
    page_address_init(); //初始化页表相关的结构
    pr_notice("%s", linux_banner);
    early_security_init();
    //进行体系结构相关的初始化,包括设置内存布局、识别处理器类型、初始化中断控制器等。
    setup_arch(&command_line); 
	boot_cpu_hotplug_init();

	build_all_zonelists(NULL); //建立内存管理的区域列表
	page_alloc_init();
	trap_init(); //初始化中断和异常处理机制
	mm_init();

	ftrace_init();

	/* trace_printk can be enabled here */
	early_trace_init();

	/*
	 * Set up the scheduler prior starting any interrupts (such as the
	 * timer interrupt). Full topology setup happens at smp_init()
	 * time - but meanwhile we still have a functioning scheduler.
	 */
	sched_init(); //初始化调度器相关的数据结构
	/* init some links before init_ISA_irqs() */
	early_irq_init();
	init_IRQ(); //初始化中断
	tick_init();
    ...
    cred_init();
	fork_init(); //初始化进程创建相关的数据结构
	proc_caches_init();
	uts_ns_init();
    ...

	/* Do the rest non-__init'ed, we're now alive */
	arch_call_rest_init(); //调用rest_init 创建一个进程,通常是init进程    

rest_init创建一个内核线程kernel_init,在这个线程里面启动init进程。

	pid = kernel_thread(kernel_init, NULL, CLONE_FS);
//创建一个内核线程来执行kernel_init函数。这个内核线程将负责进一步的系统初始化,
//包括加载初始根文件系统、启动init进程。
//pid = 0
static char *execute_command;
static char *ramdisk_execute_command = "/init";
......
static int __ref kernel_init(void *unused)
{
	int ret;
    do_sysctl_args();
	//启动init进程,该进程负责进一步的系统初始化,如启动系统服务,挂在文件系统等。
	if (ramdisk_execute_command) {
		ret = run_init_process(ramdisk_execute_command);
		if (!ret)
			return 0;
		pr_err("Failed to execute %s (error %d)\n",
		       ramdisk_execute_command, ret);
	}
    ...
}
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
//创建kthreadd内核线程;负责管理和调度其他内核线程。
//pid = 2

调用kernel_init_freeable执行相关初始化。

static noinline void __init kernel_init_freeable(void)
{
	/*
	 * Wait until kthreadd is all set-up.
	 * 等待kthread线程启动完成
	 */
	wait_for_completion(&kthreadd_done);

	/* Now the scheduler is fully set up and can do blocking allocations */
	gfp_allowed_mask = __GFP_BITS_MASK;

	/*
	 * init can allocate pages on any node
	 */
	set_mems_allowed(node_states[N_MEMORY]);

	cad_pid = get_pid(task_pid(current));

	smp_prepare_cpus(setup_max_cpus);

	workqueue_init();

	init_mm_internals();

	rcu_init_tasks_generic();
	//调用do_pre_smp_initcalls()函数初始化内核中所有early_initcall的驱动模块,这些驱动函数是在多喝CPU启动前初始化的。
	do_pre_smp_initcalls();
	lockup_detector_init();

	//调用smp_init()启动剩余的CPU核心,至此多核CPU才是真正的多核CPU了,因为之前都是单核运行的。
	smp_init();
	//启动多核CPU调度功能
	sched_init_smp();

#ifdef CONFIG_ROCKCHIP_THUNDER_BOOT
	kthread_run(defer_free_memblock, NULL, "defer_mem");
#endif

	padata_init();
	page_alloc_init_late();
	/* Initialize page ext after all struct pages are initialized. */
	page_ext_init();
	/*1. 初始化 共享内存shmem/devtmpfs/buses/classes/firmware/hypervisior;
	  2. 初始化平台驱动platform_bus等;
	  3. 注册和初始化irq中断的proc文件系统;
	  4. 调用kernel_constructor_function kernel构造函数,kernel中的构造函数保存在.ctors中;
	  5. 使能usermodelhelper_enable(),usemodeheler:为了在内核中能够直接新建和运行具备root权限的用户空间程序,为init启动做准备;
	  6. 调用do_initcalls();初始化所有需要module_init的驱动入口函数,各个驱动模块在这里开始加载,
	*/
	do_basic_setup();

	kunit_run_all_tests();

#if IS_BUILTIN(CONFIG_INITRD_ASYNC)
	async_synchronize_full();
#endif

	//启动/dev/console
	console_on_rootfs();

	/*
	 * check if there is an early userspace init.  If yes, let it do all
	 * the work
	 * 配置ramdisk_execute_command = '/init';如果文件系统不存在/init,则无文件系统,将
	 * 调用prepare_namespace()来挂载ramdisks等其他的文件系统,创建/dev/ramn,加载/initrd.image镜像。
	 */
	if (init_eaccess(ramdisk_execute_command) != 0) {
		ramdisk_execute_command = NULL;
		prepare_namespace();
	}

	/*
	 * Ok, we have completed the initial bootup, and
	 * we're essentially up and running. Get rid of the
	 * initmem segments and start the user-mode stuff..
	 *
	 * rootfs is available now, try loading the public keys
	 * and default modules
	 */

	integrity_load_keys();
}

最终调用run_init_process启动/init进程,kernel_init线程结束,然后kthreadd承担维护内核线程的调度和管理。

init_early分析

在kernel中启动的/init进程,实际上是一个软链接,指向了/bin/init_early;可在out/rk3568/packages/phone/ramdisk/目录查看。

第一阶段初始化

//在//base/startup/init/services/init/standard/main_early.c文件中,
//是init_early的入口main函数。
int main(int argc, char * const argv[])
{
    long long upTimeInMicroSecs = GetUptimeInMicroSeconds(NULL);

    SystemPrepare(upTimeInMicroSecs);//拥有root权限
    return 0;
}
//base/startup/init/services/init/standard/init_firststage.c
void SystemPrepare(long long upTimeInMicroSecs)
{
    //忽略终端信号,将SIGPIPE交系统处理,SIG_IGN是信号处理的一个常量,标识忽略信号。
    (void)signal(SIGPIPE, SIG_IGN);

    EnableInitLog(INIT_INFO); //使能InitLog的INIT_INFO级别
    EarlyLogInit(); //初始化EarlyLog
    INIT_LOGI("Start init first stage.");

    CreateFsAndDeviceNode(); //创建常用的文件和设备节点

    //执行钩子函数,一般钩子链表是通过—__attribute__((constructor))构造的函数添加的
    HookMgrExecute(GetBootStageHookMgr(), INIT_FIRST_STAGE, NULL, NULL);

    //判断是否为升级模式,升级模式不需要挂载文件系统和切换root权限
    // Updater mode no need to mount and switch root
    if (InUpdaterMode() != 0) {
        return;
    }
    //挂载必要的分区,从命令行/proc/cmdline中的读取fstab文件,挂载必要的分区,同时启动ueventd,监视linux内核设备的注册;
    MountRequiredPartitions();
    /*
        
    ohos.required_mount.system=/dev/block/platform/fe310000.sdhci/byname/system@/usr@ext4@ro,barrier=1@wait,required
    ohos.required_mount.vendor=/dev/block/platform/fe310000.sdhci/byname/vendor@/vendor@ext4@ro,barrier=1@wait,required
    ohos.required_mount.misc=/dev/block/platform/fe310000.sdhci/byname/misc@none@none@none@wait,required
    ohos.required_mount.bootctrl=/dev/block/platform/fe310000.sdhci/byname/bootctrl@none@none@none@wait,required
    */

    //启动第二阶段初始化(INIT)
    StartSecondStageInit(upTimeInMicroSecs);
}

第二阶段初始化

第二阶段主要实现切换root路径到/usr/(system文件系统),并执行init程序的代码。

//base/startup/init/services/init/standard/init_firststage.c

static void StartSecondStageInit(long long uptime)
{
    INIT_LOGI("Start init second stage.");
    // It will panic if close stdio before execv("/bin/sh", NULL)
    CloseStdio();//关闭stdio,此时不能通过printf打印log了.

    SwitchRoot("/usr"); //切换root路径为/usr(system文件系统)
    char buf[64];
    snprintf_s(buf, sizeof(buf), sizeof(buf) - 1, "%lld", uptime);
    // Execute init second stage
    char * const args[] = {
        "/bin/init", //init可执行文件的路径
        "--second-stage", //参数标识是启动的第二阶段
        buf,
        NULL,
    };
    if (execv("/bin/init", args) != 0) { //让init进程执行init程序的代码
        INIT_LOGE("Failed to exec \"/bin/init\", err = %d", errno);
        exit(-1);
    }
}

//base/startup/init/services/init/main.c
int main(int argc, char * const argv[])
{
    const char *uptime = NULL;
    long long upTimeInMicroSecs = 0;
    int isSecondStage = 0;
    (void)signal(SIGPIPE, SIG_IGN);
    // Number of command line parameters is 2
    if (argc > 1 && (strcmp(argv[1], "--second-stage") == 0)) { //是否为二阶段启动
        isSecondStage = 1; //给第二阶段启动标识赋值为1;
        if (argc > 2) {
            uptime = argv[2];
        }
    } else {
        upTimeInMicroSecs = GetUptimeInMicroSeconds(NULL);
    }
    if (getpid() != INIT_PROCESS_PID) { //判断当前的进程ID是否等于1
        INIT_LOGE("Process id error %d!", getpid());
        return 0;
    }
    EnableInitLog(INIT_INFO);

    // Updater mode
    if (isSecondStage == 0) { //不成立,
        SystemPrepare(upTimeInMicroSecs); //执行init_early代码
    } else {
        LogInit();
    }

    SystemInit(); //系统初始化
    SystemExecuteRcs(); //rk3568没有/etc/init.d/rcS脚本
    SystemConfig(uptime);  //系统配置
    SystemRun(); //系统运行
    return 0;
}

系统初始化

//base/startup/init/services/init/standard/init.c
void SystemInit(void)
{
    CloseStdio(); //关闭标准输入输出
#ifndef STARTUP_INIT_TEST
    //设置一个会话密钥,所有进程都将具有访问权限
    // Set up a session keyring that all processes will have access to.
    KeyCtrlGetKeyringId(KEY_SPEC_SESSION_KEYRING, 1);
#endif
    // umaskd调用总是成功并返回先前的掩码值,这里不需要。
    // umask call always succeeds and return the previous mask value which is not needed here
    (void)umask(DEFAULT_UMASK_INIT);
    MakeDirRecursive("/dev/unix/socket", S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
    //初始化套接字
    int sock = FdHolderSockInit();
    if (sock >= 0) {
        //注册套接字监视器
        RegisterFdHoldWatcher(sock);
    }
    InitControlFd(); //初始化控制文件描述符

    // sysclktz 0 //设置系统时钟
    struct timezone tz = { 0 };
    if (settimeofday(NULL, &tz) == -1) {
        INIT_LOGE("Set time of day failed, err = %d", errno);
    }
}

系统配置

//base/startup/init/services/init/standard/init.c
void SystemConfig(const char *uptime)
{
    INIT_TIMING_STAT timingStat;

    // 初始化"/proc/self/oom_score_adj" 为-1000,取值范围-1000到1000,取值-1000时这个进程在low mem时永远杀不掉
    InitSysAdj();
    HOOK_EXEC_OPTIONS options;

    options.flags = 0;
    options.preHook = InitPreHook;
    options.postHook = InitPostHook;
    //初始化服务空间, boot start: device.boot.group; 从设备启动组开始启动
    InitServiceSpace();
    // 最终回调 InitPreHook InitPostIHook
    HookMgrExecute(GetBootStageHookMgr(), INIT_GLOBAL_INIT, (void *)&timingStat, (void *)&options);
    //记录初始化事件
    RecordInitBootEvent("init.prepare");

    HookMgrExecute(GetBootStageHookMgr(), INIT_PRE_PARAM_SERVICE, (void *)&timingStat, (void *)&options);
    // 初始化paramService
    if (InitParamService() != 0) {
        //失败就重启
        ExecReboot("panic");
    }
    //解析/system/etc/device.boot.group.cfg
    InitParseGroupCfg();
    RegisterBootStateChange(BootStateChange); //注册启动状态改变处理函数

    INIT_LOGI("boot stage: init finish.");
    // load SELinux context and policy
    // Do not move position!
    PluginExecCmdByName("loadSelinuxPolicy", ""); //加载selinux 上下文
    RecordInitBootEvent("init.prepare");

    // after selinux loaded
    SignalInit(); //信号处理初始化

    RecordInitBootEvent("init.ParseCfg");
    LoadSpecialParam(); //加载专用的参数

    // parse parameters
    HookMgrExecute(GetBootStageHookMgr(), INIT_PRE_PARAM_LOAD, (void *)&timingStat, (void *)&options);
    InitLoadParamFiles(); //加载param文件

    // Write kernel uptime into system parameter
    WriteUptimeSysParam("ohos.boot.time.kernel", uptime);

    // read config
    HookMgrExecute(GetBootStageHookMgr(), INIT_PRE_CFG_LOAD, (void *)&timingStat, (void *)&options);
    ReadConfig(); //读取cfg文件,实际是json文件,其中定义了各中启动配置信息。
    RecordInitBootEvent("init.ParseCfg");
    INIT_LOGI("boot stage: parse config file finish.");
    HookMgrExecute(GetBootStageHookMgr(), INIT_POST_CFG_LOAD, (void *)&timingStat, (void *)&options);

    IsEnableSandbox();
    // execute init
    //触发执行"pre-init"阶段的启动配置
    PostTrigger(EVENT_TRIGGER_BOOT, "pre-init", strlen("pre-init"));
    //触发执行"init"阶段的启动配置
    PostTrigger(EVENT_TRIGGER_BOOT, "init", strlen("init"));
    //触发启动模式是START_MODE_BOOT的service执行
    TriggerServices(START_MODE_BOOT);
    //触发执行"post-init"阶段的启动配置
    PostTrigger(EVENT_TRIGGER_BOOT, "post-init", strlen("post-init"));
    //触发启动模式是START_MODE_NORMAL的service执行
    TriggerServices(START_MODE_NORMAL);
    clock_gettime(CLOCK_MONOTONIC, &(g_bootJob.startTime));
}

系统运行

//base/startup/init/services/init/standard/init.c
void SystemRun(void)
{
    StartParamService(); //启动ParamService
}
//base/startup/init/services/param/linux/param_service.c
int StartParamService(void)
{
    // read selinux label
    LoadSelinuxLabel("permission");
    return ParamServiceStart();
}
Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐