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, ¶m); // 二
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
。