init进程

  鸿蒙中的init进程作用类似于 Linux 中的init进程,主要的作用是完成系统启动后,用户可以操作前的一些初始化操作,例如孵化一些用户的服务,创建一些必要的文件目录结构等等。在 Linux 中是通过配置init.rc文件来达到此目的,在鸿蒙中则通过修改~/vendor/XXX/init_configs/init_liteos_a_3516dv300.cfg文件来实现相应的功能。
  接下来,将围绕init进程是如何启动的,以及init进程是如何解析并执行配置文件的命令的两个部分展开。

init进程的启动

  第一篇文章的启动流程已经分析过,init进程是由SystemInit()函数来启动的,所以接着从SystemInit()函数接着分析。SystemInit()函数的代码如下所示:

// ~/vendor/st/stm32mp157/board/board.c
SystemInit()							
    ProcFsInit();
	mem_dev_register();
	imx6ull_driver_init();
    imx6ull_mount_rootfs();
	DeviceManagerStart();				//HDF,加载驱动,使外射可以正常工作。
	uart_dev_init();
	......
    OsUserInitProcess();

  在SystemInit()函数中,通过OsUserInitProcess()来启动init进程,所以OsUserInitProcess()函数是接下来分析的重点。OsUserInitProcess()函数的头文件和实现如下:

// ~/kernel/liteos_a/kernel/base/include/los_process_pri.h
// ~/kernel/liteos_a/kernel/base/core/los_process.c
LITE_OS_SEC_TEXT_INIT UINT32 OsUserInitProcess(VOID)
{
	.......
    CHAR *userInitTextStart = (CHAR *)&__user_init_entry;				// 一
    CHAR *userInitBssStart = (CHAR *)&__user_init_bss;
    CHAR *userInitEnd = (CHAR *)&__user_init_end;
    UINT32 initBssSize = userInitEnd - userInitBssStart;
    UINT32 initSize = userInitEnd - userInitTextStart;

    LosProcessCB *processCB = OS_PCB_FROM_PID(g_userInitProcess);
    ret = OsProcessCreateInit(processCB, OS_USER_MODE, "Init", OS_PROCESS_USERINIT_PRIORITY);
    .......
    userText = LOS_PhysPagesAllocContiguous(initSize >> PAGE_SHIFT);
    ......
    (VOID)memcpy_s(userText, initSize, (VOID *)&__user_init_load_addr, initSize);
    ret = LOS_VaddrToPaddrMmap(processCB->vmSpace, (VADDR_T)(UINTPTR)userInitTextStart, LOS_PaddrQuery(userText),
                               initSize, VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE |
                               VM_MAP_REGION_FLAG_PERM_EXECUTE | VM_MAP_REGION_FLAG_PERM_USER);
    if (ret < 0) {
        goto ERROR;
    }

    (VOID)memset_s((VOID *)((UINTPTR)userText + userInitBssStart - userInitTextStart), initBssSize, 0, initBssSize);

    stack = OsUserInitStackAlloc(g_userInitProcess, &size);
    if (stack == NULL) {
        PRINTK("user init process malloc user stack failed!\n");
        ret = LOS_NOK;
        goto ERROR;
    }

    param.pfnTaskEntry = (TSK_ENTRY_FUNC)userInitTextStart;
    param.userParam.userSP = (UINTPTR)stack + size;
    param.userParam.userMapBase = (UINTPTR)stack;
    param.userParam.userMapSize = size;
    param.uwResved = OS_TASK_FLAG_PTHREAD_JOIN;
    ret = OsUserInitProcessStart(g_userInitProcess, &param);		// 二
    if (ret != LOS_OK) {
        (VOID)OsUnMMap(processCB->vmSpace, param.userParam.userMapBase, param.userParam.userMapSize);
        goto ERROR;
    }

    return LOS_OK;

ERROR:
    (VOID)LOS_PhysPagesFreeContiguous(userText, initSize >> PAGE_SHIFT);
    OsDeInitPCB(processCB);
    return ret;
}

  在OsUserInitProcess()函数中,我们首先看到首先通过OsProcessCreateInit()函数创建了一个Process,接着通过OsUserInitStackAlloc()函数创建了栈空间,最后通过OsUserInitProcessStart()函数来启动创建的init进程。而init进程的相关参数(如进程名、栈空间、入口函数)是通过函数OsUserInitProcessStart()前述的变量param来指定的。变量param指定了入口函数为userInitTextStart,而userInitTextStart在函数的开始位置指向了地址__user_init_entry。所以接下来要找到__user_init_entry地址对应的位置。

__user_init_entry 指向哪里

  在编译链接文件~/kernel/liteos_a/tools/build/liteos.ld中,我们找到了__user_init_entry这个地址。

// ~/kernel/liteos_a/tools/build/liteos.ld
......
.user_init USER_INIT_VM_START : ALIGN(0x1000) {
        . = ALIGN(0x4);
        __user_init_load_addr = LOADADDR(.user_init);
        __user_init_entry = .;
        KEEP(libuserinit.O (.user.entry))
        KEEP(libuserinit.O (.user.text))
        KEEP(libuserinit.O (.user.rodata))
        . = ALIGN(0X4);
        __user_init_data = .;
        KEEP(libuserinit.O (.user.data))
        . = ALIGN(0X4);
        __user_init_bss = .;
        KEEP(libuserinit.O (.user.bss))
        . = ALIGN(0x1000);
        __user_init_end = .;
    } > user_ram AT > ram
......

  从上述的链接文件可以大概知道__user_init_entry地址会指向镜像的.user.entry部分。而镜像的.user.entry部分是通过宏定义等方式实现的,具体如下:

// ~/kernel/liteos_a/kernel/user/include/los_user_init.h
#ifndef LITE_USER_SEC_ENTRY
#define LITE_USER_SEC_ENTRY   __attribute__((section(".user.entry")))
#endif

LITE_USER_SEC_ENTRY VOID OsUserInit(VOID *args)
{
#ifdef LOSCFG_KERNEL_DYNLOAD
   sys_call3(__NR_execve, (UINTPTR)g_initPath, 0, 0);
#endif
   while (1) {
   }
}

  首先通过宏定义,定义了一个名为LITE_USER_SEC_ENTRY的宏,这个宏告诉编译器,使用这个宏的函数会被编译到镜像的.user.entry部分。至此init进程的启动进入了函数OsUserInit()函数中。OsUserInit()函数调用的sys_call3()g_initPath变量的定义,如下所示:

// ~/kernel/liteos_a/kernel/user/src/los_user_init.c
#ifdef LOSCFG_KERNEL_DYNLOAD
LITE_USER_SEC_RODATA STATIC CHAR *g_initPath = "/bin/init";
#endif

LITE_USER_SEC_TEXT STATIC UINT32 sys_call3(UINT32 nbr, UINT32 parm1, UINT32 parm2, UINT32 parm3)
{
    register UINT32 reg7 __asm__("r7") = (UINT32)(nbr);
    register UINT32 reg2 __asm__("r2") = (UINT32)(parm3);
    register UINT32 reg1 __asm__("r1") = (UINT32)(parm2);
    register UINT32 reg0 __asm__("r0") = (UINT32)(parm1);

    __asm__ __volatile__
    (
        "svc %1"
        : "=r"(reg0)
        : "i"(SYS_CALL_VALUE), "r"(reg7), "r"(reg0), "r"(reg1), "r"(reg2)
        : "memory", "r14"
    );

    return reg0;
}

  通过上面的分析,我们终于找到了init进程最终是启动了/bin/init可执行程序。

/bin/init到底是什么

  ~/base/startup/services/init_lite/BUILD.gn文件中,描述了/bin/init到底是什么。

// ~/base/startup/services/init_lite/BUILD.gn
executable("init"){
    ......
}

  我们可以从可执行程序/bin/init的入口main()函数得知一二。main()的代码如下:

// ~/base/startup/services/init_lite/src/main.c
int main(int argc, char * const argv[])
{
    // 1. print system info
    PrintSysInfo();

    // 2. signal register
    SignalInitModule();

    // 3. read configuration file and do jobs
    InitReadCfg();

    // 4. keep process alive
    printf("[Init] main, entering wait.\n");
    while (1) {
        // pause only returns when a signal was caught and the signal-catching function returned.
        // pause only returns -1, no need to process the return value.
        (void)pause();
    }
    return 0;
}

  我们看到在main()函数中,有个名为InitReadCfg()的函数,通过对这个函数的分析,很容易知道这个函数其实就是解析对应的xxx.cfg文件的,例如init_liteos_a_3516dv300.cfg。这个文件就对应着linux系统中的init.rc文件。有兴趣的可以看看源码目录~/vendor/huawei/camera/init_configs/init_liteos_a_3516dv300.cfg,看看这个文件到底描述了什么。

OsUserInitProcessStart()启动init进程

  在OsUserInitProcess()函数的分析中,我门提到最后init进程的启动,是通过OsUserInitProcessStart()函数来实现的。OsUserInitProcessStart()函数的代码如下:

// ~/kernel/liteos_a/kernel/base/core/los_process.c
STATIC UINT32 OsUserInitProcessStart(UINT32 processID, TSK_INIT_PARAM_S *param)
{
    UINT32 intSave;
    INT32 taskID;
    INT32 ret;

    taskID = OsCreateUserTask(processID, param);
    if (taskID < 0) {
        return LOS_NOK;
    }

    ret = LOS_SetTaskScheduler(taskID, LOS_SCHED_RR, OS_TASK_PRIORITY_LOWEST);
    if (ret < 0) {
        PRINT_ERR("User init process set scheduler failed! ERROR:%d \n", ret);
        SCHEDULER_LOCK(intSave);
        (VOID)OsTaskDeleteUnsafe(OS_TCB_FROM_TID(taskID), OS_PRO_EXIT_OK, intSave);
        return -ret;
    }

    return LOS_OK;
}

  通过这个函数的代码可知,其实就是通过两个关键的函数OsCreateUserTask()LOS_SetTaskScheduler()来达到的,前者创建了Task,后者通知系统调度这个Task

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐