当前位置:首页 > 应用开发

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

想了解更多内容,启动请访问:

和华为官方合作共建的流程鸿蒙技术社区

https://harmonyos.51cto.com

init阶段

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

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

1.关闭输入输出

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

// base\startup\init_lite\services\src\device.c void CloseStdio(void) {      int fd = open("/dev/null", O_RDWR | O_CLOEXEC);     if (fd < 0) {          return;     }     dup2(fd, 0);     dup2(fd, 1);     dup2(fd, 2);     close(fd); } 

2.在串口打印调试信息

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

// base\startup\init_lite\services\log\init_log.c void OpenLogDevice(void) {      int fd = 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\main.c static void PrintSysInfo() {  #ifdef OHOS_LITE     const char* sysInfo = GetVersionId();     if (sysInfo != NULL) {          INIT_LOGE("%s", sysInfo);         return;     }     INIT_LOGE("main, GetVersionId failed!"); #endif } 

3.挂载目录,建立索引节点

// base\startup\init_lite\services\src\device.c void MountBasicFs(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) } void CreateDeviceNode(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_log.c void EnableDevKmsg(void) {      /* printk_devkmsg default value is ratelimit, We need to set "on" and remove the restrictions */     int fd = 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\device.c int MakeSocketDir(const char *path, mode_t mode) {      int rc = mkdir("/dev/unix/", mode);     ...      rc = mkdir("/dev/unix/socket/", mode);     ... } 

6.Singnal初始化

// base\startup\init_lite\services\src\init_signal_handler.c void SignalInitModule() {      int ret = uv_signal_init(uv_default_loop(), &g_sigchldHandler);     int ret1 = uv_signal_init(uv_default_loop(), &g_sigtermHandler);     if (ret != 0 && ret1 != 0) {          INIT_LOGW("initialize signal handler failed");         return;     }     if (uv_signal_start(&g_sigchldHandler, UVSignalHandler, SIGCHLD) != 0) {          INIT_LOGW("start SIGCHLD handler failed");     }     if (uv_signal_start(&g_sigtermHandler, UVSignalHandler, SIGTERM) != 0) {          INIT_LOGW("start SIGTERM handler failed");     } } 

7.执行命令脚本文件

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

// base\startup\init_lite\services\src\init_adapter.c void ExecuteRcs() {  #if (defined __LINUX__) && (defined NEED_EXEC_RCS_LINUX)     pid_t retPid = fork();     if (retPid < 0) {          INIT_LOGE("ExecuteRcs, fork failed! err %d.", errno);         return;     }     // child process     if (retPid == 0) {          INIT_LOGI("ExecuteRcs, child process id %d.", getpid());         if (execle("/bin/sh", "sh", "/etc/init.d/rcS", NULL, NULL) != 0) {              INIT_LOGE("ExecuteRcs, execle failed! err %d.", errno);         }         _exit(0x7f); // 0x7f: user specified     }     // init process     sem_t sem;     if (sem_init(&sem, 0, 0) != 0) {          INIT_LOGE("ExecuteRcs, sem_init failed, err %d.", errno);         return;     }     SignalRegWaitSem(retPid, &sem);     // wait until rcs process exited     if (sem_wait(&sem) != 0) {          INIT_LOGE("ExecuteRcs, sem_wait failed, err %d.", errno);     } #endif } 

8.【重要】解析并执行*.cfg文件。

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

base\startup\init_lite\services\src\init_read_cfg.c

a.初始化服务参数的工作区

// base\startup\init_lite\services\param\service\param_service.c void InitParamService() {      int ret = InitParamWorkSpace(&g_paramWorkSpace, 0, g_initContext);     PARAM_CHECK(ret == 0, return, "Init parameter workspace fail"); } 

b.解析init.cfg文件

./base/startup/init_lite/services/etc/init.cfg ./base/update/updater/services/etc/init.cfg ./out/ohos-arm-release/packages/phone/system/etc/init.cfg ./out/ohos-arm-release/packages/phone/updater/etc/init.cfg ./out/ohos-arm-release/obj/base/startup/init_lite/services/base/startup/init_lite/services/etc/init.cfg ./out/ohos-arm-release/obj/base/update/updater/services/base/update/updater/services/etc/init.cfg ./device/hisilicon/hi3516dv300/updater/init.cfg 

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

// base\startup\init_lite\services\src\init_read_cfg.c void ParseInitCfg(const char *configFile) // 文件路径"/etc/init.cfg" {      ...     char *fileBuf = ReadFileToBuf(configFile);  // 读取*.cfg文件     cJSON* fileRoot = cJSON_Parse(fileBuf);     // 解析成JSON     ...     ParseInitCfgContents(fileRoot);     ... } ---------------------------------------------------------------------- static void ParseInitCfgContents(const cJSON *root) // JSON格式的init.cfg {      ...     ParseAllServices(root);     // ①.解析"services"部分,源码库并执行     ...     ParseAllJobs(root);         // Liteos走这边     ParseTriggerConfig(root);   // Linux走这边,②.解析"jobs"部分,并执行     ...     ParseAllImports(root);      // ③.提取init.cfg中"import"的部分,并执行 } 

①.解析"services"部分,并执行

// base\startup\init_lite\services\src\init_service_manager.c void ParseAllServices(const cJSON* fileRoot) {      int servArrSize = 0;     cJSON* serviceArr = GetArrItem(fileRoot, &servArrSize, "services");     ... // 截取 init.cfg 中的"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"]             }]     ///////////////////////////////// 参考格式     ... // 默认服务数量不能超过100     Service* retServices = (Service*)realloc(g_services, sizeof(Service) * (g_servicesCnt + servArrSize));     ...      // Skip already saved services,     Service* tmp = retServices + g_servicesCnt;     if (memset_s(tmp, sizeof(Service) * servArrSize, 0, sizeof(Service) * servArrSize) != EOK) {          free(retServices);         retServices = NULL;         return;     }     // 【重要】然后使用一个for循环遍历服务数组     for (int i = 0; i < servArrSize; ++i) {          cJSON* curItem = cJSON_GetArrayItem(serviceArr, i);         if (CheckServiceKeyName(curItem) != SERVICE_SUCCESS) {              ReleaseServiceMem(&tmp[i]);             tmp[i].attribute |= SERVICE_ATTR_INVALID;             continue;         }         int ret = ParseOneService(curItem, &tmp[i]);         if (ret != SERVICE_SUCCESS) {        // 如果服务启动失败             // release resources if it fails             ReleaseServiceMem(&tmp[i]);             tmp[i].attribute |= SERVICE_ATTR_INVALID;             INIT_LOGE("Parse information for service %s failed. ", tmp[i].name);             continue;         } else {                             // 如果服务启动成功             INIT_LOGD("service[%d] name=%s, uid=%d, critical=%d, disabled=%d",                 i, tmp[i].name, tmp[i].servPerm.uID, (tmp[i].attribute & SERVICE_ATTR_CRITICAL) ? 1 : 0,                 (tmp[i].attribute & SERVICE_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("Failed Get Service OnRestart service");         }     }     // Increase service counter.     RegisterServices(retServices, servArrSize);  // 最后注册服务 } ---------------------------------------------------------------------- void RegisterServices(Service* services, int servicesCnt) {      if (services == NULL) {          return;     }     g_services = services;     g_servicesCnt += servicesCnt;     // 到这里init.cfg中的"services"部分就已经解析并执行完毕了。 } 

②.解析"jobs"部分,并执行

// base\startup\init_lite\services\param\trigger\trigger_processor.c int ParseTriggerConfig(const cJSON *fileRoot) {      ...     int ret = InitTriggerWorkSpace(&g_triggerWorkSpace);    // 初始化触发器的工作空间     ...     cJSON *triggers = cJSON_GetObjectItemCaseSensitive(fileRoot, "jobs");   // 提取init.cfg中"jobs"的部分     ...     int size = cJSON_GetArraySize(triggers);     ...     for (int i = 0; i < size; ++i) {          cJSON *item = cJSON_GetArrayItem(triggers, i);         ParseTrigger(&g_triggerWorkSpace, item);     }     return 0; } ---------------------------------------------------------------------- // 截取init.cfg中jobs段的部分代码,格式如下 "jobs" : [{              "name" : "pre-init",             "cmds" : [                 "write /proc/sys/kernel/sysrq 0",                 ...                 "mkdir /data",             ]         }, {              "name" : "init",             "cmds" : [                 "copy /proc/cmdline /dev/urandom",                 ...                 "domainname localdomain"             ]         }, {              "name" : "param:sys.boot_from_charger_mode=1",             "condition" : "sys.boot_from_charger_mode=1",             "cmds" : [                 "trigger post-init"             ]         },           ...     ], // base\startup\init_lite\services\param\trigger\trigger_manager.c int ParseTrigger(TriggerWorkSpace *workSpace, const cJSON *triggerItem) {      ... // 提取init.cfg中jobs段的"name"的部分     char *name = cJSON_GetStringValue(cJSON_GetObjectItem(triggerItem, "name"));     ... // 提取init.cfg中jobs段的"condition"的部分     char *condition = cJSON_GetStringValue(cJSON_GetObjectItem(triggerItem, "condition"));       int index = GetTriggerIndex(name);     ...     u_int32_t offset = 0;     TriggerNode *trigger = GetTriggerByName(workSpace, name, &offset);     if (trigger == NULL) {          offset = AddTrigger(workSpace, index, name, condition);         PARAM_CHECK(offset > 0, return -1, "Failed to create trigger %s", name);         trigger = GetTriggerByIndex(workSpace, offset);     } else {          if (condition != NULL) {              PARAM_LOGE("Warning parseTrigger %s %s", name, condition);         }     }     PARAM_LOGD("ParseTrigger %s %u", name, offset);     // 添加命令行     cJSON* cmdItems = cJSON_GetObjectItem(triggerItem, "cmds"); // 提取init.cfg中jobs段的"cmds"的部分     ...     int cmdLinesCnt = cJSON_GetArraySize(cmdItems); // 获取命令数量     ...     for (int i = 0; i < cmdLinesCnt; ++i) {  // 循环执行         char *cmdLineStr = cJSON_GetStringValue(cJSON_GetArrayItem(cmdItems, i));         ...         size_t cmdLineLen = strlen(cmdLineStr);         const char *matchCmd = GetMatchCmd(cmdLineStr);         if (matchCmd == NULL && strncmp(cmdLineStr, "trigger ", strlen("trigger ")) == 0) {              matchCmd = "trigger ";         }         ...         size_t matchLen = strlen(matchCmd);         if (matchLen == cmdLineLen) {              offset = AddCommand(workSpace, trigger, matchCmd, NULL);         } else {              offset = AddCommand(workSpace, trigger, matchCmd, cmdLineStr + matchLen);         }         PARAM_CHECK(offset > 0, continue, "Failed to add command %s", cmdLineStr);     }     return 0; } 

③.提取init.cfg中"import"的部分,并执行

// base\startup\init_lite\services\src\init_import.c void ParseAllImports(const cJSON *root) {          ///////////////////////////////// 参考格式         "import" : [             "/etc/init.usb.cfg",             "/etc/init.usb.configfs.cfg",             "/etc/init.usb.cfg",             "/etc/init.Hi3516DV300.usb.cfg",             "/etc/init.Hi3516DV300.cfg"     ],         /////////////////////////////////     cJSON *importAttr = cJSON_GetObjectItemCaseSensitive(root, "import");   // 提取init.cfg中"import"的部分     ...     int importAttrSize = cJSON_GetArraySize(importAttr);     for (int i = 0; i < importAttrSize; i++) {   // 循环取出每一项         cJSON *importItem = cJSON_GetArrayItem(importAttr, i);         ...         char *importContent = cJSON_GetStringValue(importItem);         ... // Only OHOS L2 support parameter. #ifndef OHOS_LITE //这里有啥意义,前面都已经判断过了         if (ExtractCfgFile(&cfgFile, importContent) < 0) {              INIT_LOGW("Failed to import from %s", importContent);             if (cfgFile != NULL) {                  free(cfgFile);                 cfgFile = NULL;             }             continue;         } #else         cfgFile = importContent; #endif         INIT_LOGI("Import %s...", cfgFile);         ParseInitCfg(cfgFile);  // 取出"import"中的路径,解析方法和init.cfg解析方式一致。         ...     }     INIT_LOGD("parse import file done");     return; } 

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

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

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

// base\startup\init_lite\services\src\init_read_cfg.c static void ParseOtherCfgs() {      ReadCfgs("/system/etc/init");     return; } ------------------------------------ static void ReadCfgs(const char *dirPath) {      DIR *pDir = opendir(dirPath);     ...     struct dirent *dp;     while ((dp = readdir(pDir)) != NULL) {          char fileName[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("ParseCfgs snprintf_s failed.");             closedir(pDir);             return;         }         struct stat st;         if (stat(fileName, &st) == 0) {              if (strstr(dp->d_name, ".cfg") == NULL) {                  continue;             }             INIT_LOGI("ReadCfgs :%s from %s success.", fileName, dirPath);             ParseInitCfg(fileName);  // 和init.cfg同样的解析方式         }     }     closedir(pDir);     return; } 

d.PostTrigger

PostTrigger(EVENT_BOOT, “pre-init”, strlen(“pre-init”));

// base\startup\init_lite\services\param\trigger\trigger_processor.c void PostTrigger(EventType type, const char *content, u_int32_t contentLen) {          ///////////////////////////////////// 参考TriggerDataEvent     typedef struct {          uv_work_t request;         EventType type;         u_int32_t contentSize;         char content[0];     } TriggerDataEvent;     /////////////////////////////////////     ...     TriggerDataEvent *event = (TriggerDataEvent *)malloc(sizeof(TriggerDataEvent) + contentLen + 1);     ...     event->type = type;  // = EVENT_BOOT     event->request.data = (void *)((char*)event + sizeof(uv_work_t));     event->contentSize = contentLen;  // = strlen("pre-init")     event->content[contentLen] = \0; // "pre-init"[strlen("pre-init")],设置结束符\0     SendTriggerEvent(event);     ... } ---------------------------------------------------------------------------- static void SendTriggerEvent(TriggerDataEvent *event) {      ....     int ctrlSize = strlen("sys.powerctrl=");     if (strncmp(event->content, "sys.powerctrl=", ctrlSize) == 0) {   // 如果event->content为"sys.powerctrl="         char *cmdParam = NULL;         const char *matchCmd = GetCmdInfo(event->content + ctrlSize, event->contentSize - ctrlSize, &cmdParam);         if (matchCmd != NULL) {              DoCmdByName(matchCmd, cmdParam);         } else {              PARAM_LOGE("SendTriggerEvent cmd %s not found", event->content);         }     } else if (strncmp(event->content, "ohos.ctl.start=", strlen("ohos.ctl.start=")) == 0) {  // 如果是"ohos.ctl.start="         DoCmdByName("start ", event->content + strlen("ohos.ctl.start="));     } else if (strncmp(event->content, "ohos.ctl.stop=", strlen("ohos.ctl.stop=")) == 0) {   // 如果是"ohos.ctl.stop="         DoCmdByName("stop ", event->content + strlen("ohos.ctl.stop="));     } 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_cmds.c void DoCmdByName(const char *name, const char *.cmdContent) {      ...     size_t cmdCnt = sizeof(CMD_TABLE) / sizeof(CMD_TABLE[0]);     unsigned int i = 0;     for (; i < cmdCnt; ++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, unknown cmd name %s.", name);     } } 

9.启动参数服务

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

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

// base\startup\init_lite\services\param\service\param_service.c int StartParamService() {      PARAM_LOGI("StartParamService.");     uv_fs_t req;     uv_fs_unlink(uv_default_loop(), &req, PIPE_NAME, NULL);     uv_pipe_t pipeServer;     int ret = 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内核启动脚本,之后在执行鸿蒙init.cfg和单板相关*.cfg。以上分析都是我个人见解,如有错误欢迎指正。

想了解更多内容,请访问:

和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

分享到:

滇ICP备2023006006号-16