HarmonyOS鸿蒙Next中OHOS 3.1的Init进程two_stages相关分析-2-实现部分

HarmonyOS鸿蒙Next中OHOS 3.1的Init进程two_stages相关分析-2-实现部分

OHOS 3.1的Init进程two_stages相关分析-2-实现部分

梁开祝 2022.05.04

【注:本文可做为《沉浸式剖析OpenHarmony源代码》一书的第5章的5.4小节部分内容的大纲或草稿。】

OHOS LTS3.0版本的标准系统还不支持two_stages,3.1版本开始支持。这里的two_stages是指OHOS 3.1之后的标准系统,从内核态切换到用户态运行init进程时,分成两个stages来完成系统的启动工作:

  • stage0运行在ramdisk中,主要是生成设备节点、挂载根文件系统,并切换到stage1去运行;
  • stage1完成OHOS框架各模块、各进程的启动工作。

不过,OHOS 3.1标准系统烧录到HI3516DV300开发板,跑起来相当吃力,因此本文将基于DAYU200开发板,分别从编译和实现两大部分来对two_stages展开分析,最后再通过log确认一遍相关流程。

【本文很长,分两篇文章来发布:编译部分实现部分

2.实现部分

2.1 initA(stage0)和initB(stage1)的差异

为什么我说initA(stage0)和initB(stage1)是两个完全不同的可执行程序呢?看一下init的实现代码就可以知道了,见//base/startup/init_lite/services/init/main.c

static const pid_t INIT_PROCESS_PID = 1;
int main(int argc, char * const argv[])
{
    int isSecondStage = 0;
    if (argc == 2 && (strcmp(argv[1], "--second-stage") == 0)) {
        isSecondStage = 1;
    }
    if (getpid() != INIT_PROCESS_PID) {
        return 0;
    }
    printf("###############################################################\n");
    printf("##################[Init ] [Stage%d] [%s]###################\n",
        isSecondStage, isSecondStage?"/System":"RamDisk");
    
    if (isSecondStage == 0) {   //Stage0
        printf("               [-][Init ] [main.c] init_main[5-1][Stage0]: SystemPrepare()\n");
        SystemPrepare();
    } else {                    //Stage1
        printf("               [-][Init ] [main.c] init_main[5-1][Stage1]: LogInit()\n");
        LogInit();
    }

    INIT_LOGI("init_main[5-2][Stage%d]: SystemInit()\n", isSecondStage);
    SystemInit();
    INIT_LOGI("init_main[5-3][Stage%d]: SystemExecuteRcs()\n", isSecondStage);
    SystemExecuteRcs();
    INIT_LOGI("init_main[5-4][Stage%d]: SystemConfig() --> DoJob\n", isSecondStage);
    SystemConfig();
    INIT_LOGI("init_main[5-5][Stage%d]: SystemRun() --> Looping...\n", isSecondStage);
    SystemRun();
    INIT_LOGI("init_main[5-5][Stage%d]: End.\n", isSecondStage);  //never run to this step

    return 0;
}

initA即stage0,它在SystemPrepare()里面的StartInitSecondStage()的最后一步,通过执行execv("/bin/init", args),切换到initB即stage1去运行了,并不会跑剩余的[5-2/3/4/5]几个步骤,而是让stage1的initB来跑。initB也不跑SystemPrepare()部分,而是去跑LogInit()以及接下来的[5-2/3/4/5]几个步骤。

如流程图3所示。

由上图可知:

  • initA就仅仅是跑SystemPrepare()而已;
  • initA’又仅仅是跑SystemPrepare()的一部分,就转去执行"/bin/updater"了;
  • initB则是跑init中除了SystemPrepare()之外的其余部分。

从这个角度来看,就可以认为initA、initB、initA’是“三个完全不同的”可执行程序了。 接下来我们看一下initA和initB的具体实现。

2.2 initA(stage0)的实现和流程

SystemPrepare()的前几步,initA和initA’基本相同,没啥说的,看一下代码就明白了。不同的地方是initA’不跑StartInitSecondStage(),而是转去执行"/bin/updater"跑升级流程去了,这里不展开分析。

我们关注一下StartInitSecondStage()里面的五大步骤,用【5-1/2/3/4/5】标记。

【5-1】Fstab* fstab = LoadRequiredFstab()

这一步会去读取并解析“/etc/fstab.required”文件,这个文件就是编译时拷贝到//out/rk3568/目录下的那个,在制作ramdisk镜像和system镜像时,会再次拷贝到镜像的/etc/目录下被使用。

char **devices = GetRequiredDevices(*fstab, &requiredNum)会读取其中带有“required”flag的设备。

注意其中的userdata块设备,没有带“required”flag;而misc块设备,类型是none。

【5-2】StartUeventd(devices, requiredNum)

通过uevent机制去为块设备创建DeviceNode,中间过程稍微有点复杂,这里不展开分析,请感兴趣的小伙伴们自行阅读代码了解一下。

【5-3】MountRequriedPartitions(fstab)

这一步会去按fstab的描述,会把system、vendor两个块设备分别挂载到ramdisk根目录下的/usr、/vendor路径下,而/userdata块设备会因为没有“required”flag而推迟到stage1才去挂载,/misc块设备会因为文件类型为“none”而挂载失败,可以先不用管。

这样,ramdisk目录结构就变成了如下的样子:

【5-4】SwitchRoot("/usr")

在真正 SwitchRoot 之前,我把当前路径(ramdisk)下的一级目录打印了出来,如log中下面这一小段所示:

SwitchRoot: [-]Before SwitchRoot:
.
  [d]vendor/
  [d]lib/
  [d]etc/
  [d]sys/
  [d]storage/
  [d]usr/
  [d]mnt/
  [l]init     //link to 'bin/init',即initA
  [d]system/
  [d]bin/
  [d]proc/
  [d]root/    //空目录,暂不知道哪里生成的
  [d]dev/

这基本上契合了【5-3】步骤后的ramdisk的目录结构,只是我还没找到root这个空目录是在哪里生成的。在build_image.py的_prepare_ramdisk()中,并没有在ramdisk中生成root目录(或挂载点),在SystemPrepare()的前几步中也没看到要生成root目录(或挂载点)的地方。

SwitchRoot("/usr")这一步,非常关键,里面做了以下一组事情,如log所示:

SwitchRoot: [0]Switch root from ramdisk's '/' to '/usr' Begin:
SwitchRoot: MountToNewTarget('/usr')
MountToNewTarget: [0]  continue [/]: [-][is '/'][-
MountToNewTarget: [1]Move mount [/vendor] to [/usr/vendor]
MountToNewTarget: [2]  continue [/usr]: [-][-][mountPoint is same]
MountToNewTarget: [3]  continue [/sys/fs/selinux]: already UnderBasicMountPoint
MountToNewTarget: [4]Move mount [/sys] to [/usr/sys]
MountToNewTarget: [5]Move mount [/proc] to [/usr/proc]
MountToNewTarget: [6]  continue [/dev/pts]: already UnderBasicMountPoint
MountToNewTarget: [7]Move mount [/storage] to [/usr/storage]
MountToNewTarget: [8]Move mount [/mnt] to [/usr/mnt]
MountToNewTarget: [9]Move mount [/dev] to [/usr/dev]
SwitchRoot: chdir('/usr')
SwitchRoot: mount('/usr' to '/')
SwitchRoot: chroot('.')
FreeOldRoot: Failed to unlink[init], err = 20
SwitchRoot: [0]Switch root from ramdisk'/s '/' to '/usr' End. OK

简单来说就是把stage0的“/proc/mounts”上描述的、挂载到ramdisk根目录下的各个设备节点,全部统一重新挂载到/usr/路径下对应节点上。这个/usr/就是【5-3】步骤挂载上去的system.img所描述的块设备。

需要注意的是,这里的“/proc/mounts”是stage0阶段的设备挂载信息,与系统跑完stage1之后,我们在shell上“cat /proc/mounts”所看到的信息,可能还有点不一样,这个请小伙伴们自行确认一下。

Move mount步骤之后,再通过chdir(’/usr’)、mount(’/usr’ to ‘/’)、chroot(’.’)操作,把原先的ramdisk根目录替换成以/usr为根的新的目录结构。

在执行完 SwitchRoot 之后,我再次把当前路径(已经切换到usr/)下的一级目录打印出来,如下log所示:

SwitchRoot: [-]After SwitchRoot:
.
  [d]storage/
  [d]chip_prod/
  [d]chipset/
  [d]mnt/
  [d]tmp/
  [d]sys_prod/
  [d]data/
  [l]etc
  [d]vendor/
  [d]sys/
  [d]proc/
  [d]dev/
  [l]bin
  [l]init
  [l]lib
  [d]lost+found/
  [d]updater/
  [d]config/
  [d]system/

chroot之后,系统的根目录结构,就变成了:

【5-5】execv("/bin/init", args)

这一步就很明朗了,args定义为:

    char * const args[] = {
        "/bin/init",
        "--second-stage",
        NULL,
    };

    printf("StartInitSecondStage[5-5]: execv('/bin/init')-->>[Stage1]\n");
    if (execv("/bin/init", args) != 0) {
        INIT_LOGE("Failed to exec \"/bin/init\", err = %d", errno);
        exit(-1);
    }

带参数去运行/bin/init,这个init就是新的root下的/bin/init,也就是前面说的initB。 stage0的initA进程到此就结束了,它的上下文环境仍然保持不变,但是从这里开始切换去运行initB,即流程图3的右边绿色部分,进入stage1。

2.3 initB(stage1)的实现和流程

这一阶段就是OHOS框架的启动入口了,请小伙伴自己阅读代码去理解一下。

3.Log确认流程

我对init进程的two_stages流程做了一下整理,把相关log打印出来,完整的log如附件所示。

4.思考与讨论

为什么要引入这么复杂的启动流程?有什么好处?


更多关于HarmonyOS鸿蒙Next中OHOS 3.1的Init进程two_stages相关分析-2-实现部分的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

附件的log。

也见《OHOS 3.1的Init进程two_stages相关分析》本站博文。

更多关于HarmonyOS鸿蒙Next中OHOS 3.1的Init进程two_stages相关分析-2-实现部分的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next中,OHOS 3.1的Init进程采用了two_stages(两阶段)启动机制。该机制的核心目的是将系统启动过程分为两个阶段,以提升启动效率和系统稳定性。

第一阶段(Stage 1)主要负责硬件初始化和基础服务的启动。在这一阶段,Init进程会加载必要的硬件驱动,初始化系统时钟、内存管理等基础组件,并启动关键的系统服务,如日志服务、设备管理服务等。这一阶段的目标是确保系统具备基本的运行环境。

第二阶段(Stage 2)则是在第一阶段的基础上,进一步启动用户空间的服务和应用。在这一阶段,Init进程会加载用户配置文件,启动用户态的服务进程,如网络服务、文件系统服务等,并最终启动桌面环境或应用框架。这一阶段的目标是完成系统的全面启动,使用户能够正常使用设备。

在实现上,OHOS 3.1的Init进程通过分阶段启动,有效减少了系统启动时间,并提高了系统的可靠性。两阶段启动机制还允许在第二阶段中对系统进行更灵活的配置和管理,以适应不同的应用场景和需求。

在HarmonyOS鸿蒙Next的OHOS 3.1中,Init进程的two_stages实现分为两个阶段。第一阶段主要负责硬件初始化、内核模块加载和基本系统配置,确保系统能够启动到基本运行状态。第二阶段则进行更复杂的系统服务启动、应用框架初始化和用户空间配置,确保系统能够提供完整的服务。通过这种分阶段的设计,系统启动过程更加稳定和高效,同时便于调试和优化。

回到顶部