您现在的位置是:首页 > 鉴定专家

OHOS3.0启动流程分析丨init阶段

岁月留金阁 2025-01-03【鉴定专家】269人已围观

简介init阶段内核启动完是init阶段,源码的路径在base\startup\init_lite\services\src\,虽然文件夹命名为init_lite,但是init部分的代码是小型系统(smallsystem)和标准系统(standardsystem)通用的。相关码仓启动模块init进程()...

init阶段

内核启动完是init阶段,源码的路径在base\startup\init_lite\services\src\,虽然文件夹命名为init_lite,但是init部分的代码是小型系统(smallsystem)和标准系统(standardsystem)通用的。相关码仓启动模块init进程()

接下来参考linux内核梳理下启动流程,

Liteos-A

Linux

INIT_CHECK_ONLY_ELOG(setenv("UV_THREADPOOL_SIZE","1",1)==0,"setUV_THREADPOOL_SIZEerror:%d.",errno);

CloseStdio();//1.关闭输入输出

OpenLogDevice();//2.在串口打印调试信息

PrintSysInfo();

//PrintSysInfo();

MountBasicFs();//3.挂载目录,建立索引节点

CreateDeviceNode();

EnableDevKmsg();//4.开启DevKmsg

MakeSocketDir("/dev/unix/socket/")//5.建立Socket文件夹

SignalInitModule();

SignalInitModule();//6.Singnal初始化

ExecuteRcs();

ExecuteRcs();//7.执行命令脚本文件

InitReadCfg();

InitReadCfg();//8.【重要】解析并执行*.cfg文件。

StartParamService();//9.启动参数服务

(void)pause();

(void)pause();

1.关闭输入输出

/dev/null,空设备,特殊的设备文件,丢弃一切写入其中的数据(但报告写入操作成功),读取它则会立即得到一个EOF。其作用是对stdin/stdout/stderr进行保护,把文件描述符0,1,2分配出去,以后再分配的时候就不会将stdin/stdout/stderr打开,以达到保护目的。

//base\startup\init_lite\services\src\(void){intfd=open("/dev/null",O_RDWR|O_CLOEXEC);if(fd0){return;}dup2(fd,0);dup2(fd,1);dup2(fd,2);close(fd);}
2.在串口打印调试信息

写入/dev/kmsg的信息,可以在dmesg(开机信息)中查看。

//base\startup\init_lite\services\log\init_(void){intfd=open("/dev/kmsg",O_WRONLY|O_CLOEXEC,S_IRUSR|S_IWUSR|S_IRGRP|S_IRGRP);if(fd=0){g_fd=fd;}return;}

标准系统空实现,不深究了。

//base\startup\init_lite\services\src\(){if}
3.挂载目录,建立索引节点
//base\startup\init_lite\services\src\(void){mount("tmpfs","/dev","tmpfs",MS_NOSUID,"mode=0755"mkdir("/dev/pts",S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)mount("devpts","/dev/pts","devpts",0,NULL)mount("proc","/proc","proc",0,"hidepid=2")mount("sysfs","/sys","sysfs",0,NULL)mount("selinuxfs","/sys/fs/selinux","selinuxfs",0,NULL)}voidCreateDeviceNode(void){mknod("/dev/kmsg",S_IFCHR|DEFAULT_NO_AUTHORITY_MODE,makedev(MEM_MAJOR,DEV_KMSG_MINOR)mknod("/dev/null",S_IFCHR|DEFAULT_RW_MODE,makedev(MEM_MAJOR,DEV_NULL_MINOR)mknod("/dev/random",S_IFCHR|DEFAULT_RW_MODE,makedev(MEM_MAJOR,DEV_RANDOM_MINOR)mknod("/dev/urandom",S_IFCHR|DEFAULT_RW_MODE,makedev(MEM_MAJOR,DEV_URANDOM_MINOR)}
4.开启DevKmsg

注释很清楚了,printk_devkmsg默认是流控的,设置为on取消流控。

//base\startup\init_lite\services\log\init_(void){/*printk_devkmsgdefaultvalueisratelimit,Weneedtoset"on"andremovetherestrictions*/intfd=open("/proc/sys/kernel/printk_devkmsg",O_WRONLY|O_CLOEXEC,S_IRUSR|S_IWUSR|S_IRGRP|S_IRGRP);write(fd,"on",strlen("on")+1);close(fd);fd=-1;return;}
5.建立Socket文件夹

/dev/unix/socket/不过这个干啥用的,还没搞清楚。

MakeSocketDir("/dev/unix/socket/",S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);//base\startup\init_lite\services\src\(constchar*path,mode_tmode){intrc=mkdir("/dev/unix/",mode);rc=mkdir("/dev/unix/socket/",mode);}
6.Singnal初始化
//base\startup\init_lite\services\src\init_signal_(){intret=uv_signal_init(uv_default_loop(),g_sigchldHandler);intret1=uv_signal_init(uv_default_loop(),g_sigtermHandler);if(ret!=0ret1!=0){INIT_LOGW("initializesignalhandlerfailed");return;}if(uv_signal_start(g_sigchldHandler,UVSignalHandler,SIGCHLD)!=0){INIT_LOGW("startSIGCHLDhandlerfailed");}if(uv_signal_start(g_sigtermHandler,UVSignalHandler,SIGTERM)!=0){INIT_LOGW("startSIGTERMhandlerfailed");}}
7.执行命令脚本文件

兼容常规的*.rc文件,是执行linux运行命令的脚本文件。

//base\startup\init_lite\services\src\init_(){if}
8.【重要】解析并执行*.cfg文件。

中的命令脚本文件是*.cfg,采用JSON格式,存储的信息更多一些。前面都是启动系统的一些准备工作,而接下来才是重要部分。还是用个表格来分析

base\startup\init_lite\services\src\init_read_

Liteos-A

Linux

InitParamService();//a.初始化服务参数的工作区

LoadDefaultParams("/system/etc/");//这个文件存放了OHOS版本信息等参数。

ParseInitCfg("/etc/");

ParseInitCfg("/etc/");//b.解析文件

ParseOtherCfgs();

ParseOtherCfgs();//c.解析执行/system/etc/init/*.cfg文件

DoJob("pre-init");

TriggerStage(EVENT1,EVENT1_WAITTIME,QS_STAGE1);

DoJob("init");

TriggerStage(EVENT2,EVENT2_WAITTIME,QS_STAGE2);

DoJob("post-init");

TriggerStage(EVENT3,EVENT3_WAITTIME,QS_STAGE3);

InitStageFinished();

ReleaseAllJobs();

PostTrigger(EVENT_BOOT,"pre-init",strlen("pre-init"));//

PostTrigger(EVENT_BOOT,"init",strlen("init"));

PostTrigger(EVENT_BOOT,"post-init",strlen("post-init"));

a.初始化服务参数的工作区
//base\startup\init_lite\services\param\service\param_(){intret=InitParamWorkSpace(g_paramWorkSpace,0,g_initContext);PARAM_CHECK(ret==0,return,"Initparameterworkspacefail");}
b.解析文件

编译框架使用的哪个还有待确定。

//base\startup\init_lite\services\src\init_read_(constchar*configFile)//文件路径"/etc/"{char*fileBuf=ReadFileToBuf(configFile);//读取*.cfg文件cJSON*fileRoot=cJSON_Parse(fileBuf);//解析成JSONParseInitCfgContents(fileRoot);}----------------------------------------------------------------------staticvoidParseInitCfgContents(constcJSON*root)//JSON格式的{ParseAllServices(root);//①.解析"services"部分,并执行ParseAllJobs(root);//Liteos走这边ParseTriggerConfig(root);//Linux走这边,②.解析"jobs"部分,并执行ParseAllImports(root);//③.提取中"import"的部分,并执行}
①.解析"services"部分,并执行
//base\startup\init_lite\services\src\init_service_(constcJSON*fileRoot){intservArrSize=0;cJSON*serviceArr=GetArrItem(fileRoot,servArrSize,"services");//截取中的"services"部分/////////////////////////////////参考格式"services":[{"name":"ueventd","path":["/system/bin/ueventd"],"critical":1},{"name":"console","path":["/system/bin/sh"],"disabled":1,"console":1,"uid":"root","gid":["shell","log","readproc"]}]/////////////////////////////////参考格式//默认服务数量不能超过100Service*retServices=(Service*)realloc(g_services,sizeof(Service)*(g_servicesCnt+servArrSize));//Skipalreadysavedservices,Service*tmp=retServices+g_servicesCnt;if(memset_s(tmp,sizeof(Service)*servArrSize,0,sizeof(Service)*servArrSize)!=EOK){free(retServices);retServices=NULL;return;}//【重要】然后使用一个for循环遍历服务数组for(inti=0;iservArrSize;++i){cJSON*curItem=cJSON_GetArrayItem(serviceArr,i);if(CheckServiceKeyName(curItem)!=SERVICE_SUCCESS){ReleaseServiceMem(tmp[i]);tmp[i].attribute|=SERVICE_ATTR_INVALID;continue;}intret=ParseOneService(curItem,tmp[i]);if(ret!=SERVICE_SUCCESS){//如果服务启动失败//releaseresourcesifitfailsReleaseServiceMem(tmp[i]);tmp[i].attribute|=SERVICE_ATTR_INVALID;INIT_LOGE("Parseinformationforservice%sfailed.",tmp[i].name);continue;}else{//如果服务启动成功INIT_LOGD("service[%d]name=%s,uid=%d,critical=%d,disabled=%d",i,tmp[i].name,tmp[i].,(tmp[i].attributeSERVICE_ATTR_CRITICAL)?1:0,(tmp[i].attributeSERVICE_ATTR_DISABLED)?1:0);}if(GetServiceSocket(curItem,tmp[i])!=SERVICE_SUCCESS){if(tmp[i].socketCfg!=NULL){FreeServiceSocket(tmp[i].socketCfg);tmp[i].socketCfg=NULL;}}if(GetServiceOnRestart(curItem,tmp[i])==SERVICE_FAILURE){INIT_LOGE("FailedGetServiceOnRestartservice");}}//(retServices,servArrSize);//最后注册服务}----------------------------------------------------------------------voidRegisterServices(Service*services,intservicesCnt){if(services==NULL){return;}g_services=services;g_servicesCnt+=servicesCnt;//到这里中的"services"部分就已经解析并执行完毕了。}
②.解析"jobs"部分,并执行
//base\startup\init_lite\services\param\trigger\trigger_(constcJSON*fileRoot){intret=InitTriggerWorkSpace(g_triggerWorkSpace);//初始化触发器的工作空间cJSON*triggers=cJSON_GetObjectItemCaseSensitive(fileRoot,"jobs");//提取中"jobs"的部分intsize=cJSON_GetArraySize(triggers);for(inti=0;isize;++i){cJSON*item=cJSON_GetArrayItem(triggers,i);ParseTrigger(g_triggerWorkSpace,item);}return0;}----------------------------------------------------------------------//截取中jobs段的部分代码,格式如下"jobs":[{"name":"pre-init","cmds":["write/proc/sys/kernel/sysrq0","mkdir/data",]},{"name":"init","cmds":["copy/proc/cmdline/dev/urandom","domainnamelocaldomain"]},{"name":"param:_from_charger_mode=1","condition":"_from_charger_mode=1","cmds":["triggerpost-init"]},],//base\startup\init_lite\services\param\trigger\trigger_(TriggerWorkSpace*workSpace,constcJSON*triggerItem){//提取中jobs段的"name"的部分char*name=cJSON_GetStringValue(cJSON_GetObjectItem(triggerItem,"name"));//提取中jobs段的"condition"的部分char*condition=cJSON_GetStringValue(cJSON_GetObjectItem(triggerItem,"condition"));intindex=GetTriggerIndex(name);u_int32_toffset=0;TriggerNode*trigger=GetTriggerByName(workSpace,name,offset);if(trigger==NULL){offset=AddTrigger(workSpace,index,name,condition);PARAM_CHECK(offset0,return-1,"Failedtocreatetrigger%s",name);trigger=GetTriggerByIndex(workSpace,offset);}else{if(condition!=NULL){PARAM_LOGE("WarningparseTrigger%s%s",name,condition);}}PARAM_LOGD("ParseTrigger%s%u",name,offset);//添加命令行cJSON*cmdItems=cJSON_GetObjectItem(triggerItem,"cmds");//提取中jobs段的"cmds"的部分intcmdLinesCnt=cJSON_GetArraySize(cmdItems);//获取命令数量for(inti=0;icmdLinesCnt;++i){//循环执行char*cmdLineStr=cJSON_GetStringValue(cJSON_GetArrayItem(cmdItems,i));size_tcmdLineLen=strlen(cmdLineStr);constchar*matchCmd=GetMatchCmd(cmdLineStr);if(matchCmd==NULLstrncmp(cmdLineStr,"trigger",strlen("trigger"))==0){matchCmd="trigger";}size_tmatchLen=strlen(matchCmd);if(matchLen==cmdLineLen){offset=AddCommand(workSpace,trigger,matchCmd,NULL);}else{offset=AddCommand(workSpace,trigger,matchCmd,cmdLineStr+matchLen);}PARAM_CHECK(offset0,continue,"Failedtoaddcommand%s",cmdLineStr);}return0;}
③.提取中"import"的部分,并执行
//base\startup\init_lite\services\src\init_(constcJSON*root){/////////////////////////////////参考格式"import":["/etc/","/etc/","/etc/","/etc/","/etc/"],/////////////////////////////////cJSON*importAttr=cJSON_GetObjectItemCaseSensitive(root,"import");//提取中"import"的部分intimportAttrSize=cJSON_GetArraySize(importAttr);for(inti=0;iimportAttrSize;i++){//循环取出每一项cJSON*importItem=cJSON_GetArrayItem(importAttr,i);char*importContent=cJSON_GetStringValue(importItem);//=importContent;#ifINIT_LOGI("Import%s",cfgFile);ParseInitCfg(cfgFile);//取出"import"中的路径,解析方法和解析方式一致。}INIT_LOGD("parseimportfiledone");return;}

到这里就解析并执行完毕了,需要注意的是,import导入的cfg文件是最后才执行的。

c.解析执行/system/etc/init/*.cfg文件

和import的原理类似,遍历system/etc/init文件夹下的*.cfg文件,并执行。我感觉写在的import中应该也是可以的。

//base\startup\init_lite\services\src\init_read_(){ReadCfgs("/system/etc/init");return;}------------------------------------staticvoidReadCfgs(constchar*dirPath){DIR*pDir=opir(dirPath);structdirent*dp;while((dp=readdir(pDir))!=NULL){charfileName[FILE_NAME_MAX_SIZE];if(snprintf_s(fileName,FILE_NAME_MAX_SIZE,FILE_NAME_MAX_SIZE-1,"%s/%s",dirPath,dp-d_name)==-1){INIT_LOGE("ParseCfgssnprintf_sfailed.");closedir(pDir);return;}structstatst;if(stat(fileName,st)==0){if(strstr(dp-d_name,".cfg")==NULL){continue;}INIT_LOGI("ReadCfgs:%sfrom%ssuccess.",fileName,dirPath);ParseInitCfg(fileName);//和同样的解析方式}}closedir(pDir);return;}

PostTrigger(EVENT_BOOT,"pre-init",strlen("pre-init"));

//base\startup\init_lite\services\param\trigger\trigger_(EventTypetype,constchar*content,u_int32_tcontentLen){/////////////////////////////////////参考TriggerDataEventtypedefstruct{uv_work_trequest;EventTypetype;u_int32_tcontentSize;charcontent[0];}TriggerDataEvent;/////////////////////////////////////TriggerDataEvent*event=(TriggerDataEvent*)malloc(sizeof(TriggerDataEvent)+contentLen+1);event-type=type;//=EVENT_=(void*)((char*)event+sizeof(uv_work_t));event-contentSize=contentLen;//=strlen("pre-init")event-content[contentLen]='\0';//"pre-init"[strlen("pre-init")],设置结束符'\0'STriggerEvent(event);}----------------------------------------------------------------------------staticvoidSTriggerEvent(TriggerDataEvent*event){.intctrlSize=strlen("=");if(strncmp(event-content,"=",ctrlSize)==0){//如果event-content为"="char*cmdParam=NULL;constchar*matchCmd=GetCmdInfo(event-content+ctrlSize,event-contentSize-ctrlSize,cmdParam);if(matchCmd!=NULL){DoCmdByName(matchCmd,cmdParam);}else{PARAM_LOGE("STriggerEventcmd%snotfound",event-content);}}elseif(strncmp(event-content,"=",strlen("="))==0){//如果是"="DoCmdByName("start",event-content+strlen("="));}elseif(strncmp(event-content,"=",strlen("="))==0){//如果是"="DoCmdByName("stop",event-content+strlen("="));}else{//否则执行uv_queue_work(),uv_queue_work是将ProcessEvent提交给子线程执行,完成后通知主线程,防止阻塞uv_queue_work(uv_default_loop(),event-request,ProcessEvent,ProcessAfterEvent);event=NULL;}}-----------------------------------------------------------------------//base\startup\init_lite\services\src\init_(constchar*name,constchar*.cmdContent){size_tcmdCnt=sizeof(CMD_TABLE)/sizeof(CMD_TABLE[0]);unsignedinti=0;for(;icmdCnt;++i){if(strncmp(name,CMD_TABLE[i].name,strlen(CMD_TABLE[i].name))==0){CMD_TABLE[i].DoFuncion(cmdContent,CMD_TABLE[i].maxArg);break;}}if(i==cmdCnt){INIT_LOGE("DoCmd,unknowncmdname%s.",name);}}
9.启动参数服务

使用Libuv库,官网地址。作为Nodejs的底层。

相关的API可以参考网址,uv_run。

//base\startup\init_lite\services\param\service\param_(){PARAM_LOGI("StartParamService.");uv_fs_treq;uv_fs_unlink(uv_default_loop(),req,PIPE_NAME,NULL);uv_pipe_tpipeServer;intret=uv_pipe_init(uv_default_loop(),pipeServer,0);ret=uv_pipe_bind(pipeServer,PIPE_NAME);ret=chmod(PIPE_NAME,S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);ret=uv_listen((uv_stream_t*)pipeServer,SOMAXCONN,OnConnection);uv_run(uv_default_loop(),UV_RUN_DEFAULT);//运行事件循环,直到不再有活动和引用的句柄或请求。}

至此,init启动结束,进入pause()。总结下来init首先会执行一些通用的准备操作,同时兼容常规linux内核启动脚本,之后在执行鸿蒙和单板相关*.cfg。以上分析都是我个人见解,如有错误欢迎指正。

很赞哦!(2)