前言:
当前朋友们对“引导配置数据文件包含的os项目无效”大概比较注意,你们都需要分析一些“引导配置数据文件包含的os项目无效”的相关内容。那么小编在网络上收集了一些有关“引导配置数据文件包含的os项目无效””的相关内容,希望你们能喜欢,姐妹们快快来了解一下吧!本文作者:做事情的幻想家
原文链接:
从系统启动流程来看,init位于kernel启动之后,user程序启动以前。user程序,是指用户可交互的程序(比如Home、Shell、智能快递柜的交互程序等),也指最终的业务程序(比如智能音箱的业务程序、扫地机器人的工作程序等)。
init进程是系统的第一号用户空间进程,所有的系统进程和user进程都是由它fork()而来,即都是它的子进程。
init模块负责解析系统引导配置文件,并执行里面的命令,完成系统的引导操作。鸿蒙OS的引导配置文件使用JSON格式。系统开发人员会在这里接触到鸿蒙系统的第一个配置文件。这一点应该是借鉴Linux系操作系统。我们知道Android系统也是基于Linux内核开发的,也有自己实现的init引导程序和自己的Android initial language编写的init.rc引导配置文件。这些都是遵从或借鉴Linux操作系统,Linux操作系统就是通过init引导程序解析执行不同目录的shell脚本来进行系统引导的。
下面直接撸代码,来看一下鸿蒙init引导程序的实现。(源码部分会有些枯燥,操作代码+注释的形式开展)
init模块源码分析
code-1.0/base/startup/services/init_lite/
上面这个是init模块的代码目录。这个模块很小巧,整个目录所有文件加起来只有108KB,源文件只有8个。最大的源文件代码不超过350行。
init主流程
code-1.0/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;}
以上就是init进程进行系统引导的主流程:
打印系统信息注册信号读取系统引导配置文件并执行相应的任务init进程进入无限循环状态
这个流程很清晰简洁。看过Android操作系统init模块源代码的人,应该会很有感触,这份代码短小精悍,阅读起来很轻松。或许,毕竟这只是HarmonyOS 2.0,不知道经过几个版本的迭代会不会也变得很臃肿呢。
下面详细分析每一步的原理和代码实现。
打印系统信息
这一步是把系统信息输出到控制台,系统信息是由多个字段拼接而成的。这个系统信息类似Android操作系统的fingerprint,是一个很长的字符串,里面包含厂商、品牌、编译类型等。
code-1.0/base/startup/services/init_lite/src/main.c
static void PrintSysInfo(){ char* sysInfo = GetVersionId(); if (sysInfo != NULL) { printf("[Init] %s\n", sysInfo); // 看这两行代码,主动释放内存,用完立刻释放。 // 鸿蒙OS对内存管理的很好,阅读系统源码的时候,随处可以看到这个设计信条 free(sysInfo); sysInfo = NULL; return; } printf("[Init] main, GetVersionId failed!\n");}
code-1.0/base/startup/frameworks/syspara_lite/parameter/src/parameter_common.c
char* GetVersionId(void){ char* value = (char*)malloc(VERSION_ID_LEN); if (value == NULL) { return NULL; } if (memset_s(value, VERSION_ID_LEN, 0, VERSION_ID_LEN) != 0) { free(value); value = NULL; return NULL; } char* productType = GetProductType(); char* manufacture = GetManufacture(); char* brand = GetBrand(); char* productSerial = GetProductSeries(); char* productModel = GetProductModel(); char* softwareModel = GetSoftwareModel(); if (productType == NULL || manufacture == NULL || brand == NULL || productSerial == NULL || productModel == NULL || softwareModel == NULL) { free(productType); free(manufacture); free(brand); free(productSerial); free(productModel); free(softwareModel); free(value); value = NULL; return NULL; } int len = sprintf_s(value, VERSION_ID_LEN, "%s/%s/%s/%s/%s/%s/%s/%s/%s/%s", productType, manufacture, brand, productSerial, g_roBuildOs, productModel, softwareModel, g_roSdkApiLevel, INCREMENTAL_VERSION, BUILD_TYPE); free(productType); free(manufacture); free(brand); free(productSerial); free(productModel); free(softwareModel); if (len < 0) { free(value); value = NULL; return NULL; } return value;}
这里涉及到10个参数,分别是:产品类型、制造商、品牌、产品串号、产品型号、软件型号、操作系统名称、SDK版本号、软件版本、编译类型。用“/”隔开各个字段,形成的字符串就是打印的系统信息。
其中前面6个参数是设备厂商定义的,这包代码在下面的文件进行配置:
code-1.0/vendor/huawei/camera/hals/utils/sys_param/hal_sys_param.c
static const char OHOS_PRODUCT_TYPE[] = {"****"};static const char OHOS_MANUFACTURE[] = {"****"};static const char OHOS_BRAND[] = {"****"};static const char OHOS_PRODUCT_SERIES[] = {"****"};static const char OHOS_PRODUCT_MODEL[] = {"****"};static const char OHOS_SOFTWARE_MODEL[] = {"****"};
第7、8个参数是在下面的文件中定义的,标示操作系统名称和SDK版本:
code-1.0/base/startup/frameworks/syspara_lite/parameter/src/parameter_common.c
static char g_roBuildOs[] = {"OpenHarmony"};static char g_roSdkApiLevel[] = {"3"};
第9个参数是在产品配置的json脚本中配置的,然后通过编译选项传给源代码使用:
code-1.0/base/startup/frameworks/syspara_lite/parameter/src/BUILD.gncode-1.0/build/lite/product/ipcamera_hi3518ev300.json
第10个参数是编译系统时,传入的参数,然后再通过编译选项传给源代码使用:
code-1.0/base/startup/frameworks/syspara_lite/parameter/src/BUILD.gn
defines = [ "INCREMENTAL_VERSION=\"${ohos_version}\"", "BUILD_TYPE=\"${ohos_build_type}\"", "BUILD_USER=\"${ohos_build_user}\"", "BUILD_TIME=\"${ohos_build_time}\"", "BUILD_HOST=\"${ohos_build_host}\"", "BUILD_ROOTHASH=\"${ohos_build_roothash}\"",]
没有开发板,没办法把系统跑起来,就不贴实际输出效果图了。
注册信号
code-1.0/base/startup/services/init_lite/src/init_signal_handler.c
void SignalInitModule(){ struct sigaction act; act.sa_handler = SigHandler; act.sa_flags = SA_RESTART; (void)sigfillset(&act.sa_mask); sigaction(SIGCHLD, &act, NULL); sigaction(SIGTERM, &act, NULL);}
当信号SIGCHLD和SIGTERM发生的时候,会回调函数SigHandler()。
static void SigHandler(int sig){ switch (sig) { case SIGCHLD: { pid_t sigPID; int procStat = 0; printf("[Init] SigHandler, SIGCHLD received.\n"); while (1) { // 非阻塞状态下,等待任意子进程结束返回 sigPID = waitpid(-1, &procStat, WNOHANG); if (sigPID <= 0) { break; } ReapServiceByPID((int)sigPID); } break; } case SIGTERM: { printf("[Init] SigHandler, SIGTERM received.\n"); StopAllServices(); break; } default: printf("[Init] SigHandler, unsupported signal %d.\n", sig); break; }}
SIGCHLD:当子进程停止或退出时通知父进程。
SIGTERM:程序结束信号,与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出。
杀死所有服务
code-1.0/base/startup/services/init_lite/src/init_service_manager.c
void StopAllServices(){ for (size_t i = 0; i < g_servicesCnt; i++) { if (ServiceStop(&g_services[i]) != SERVICE_SUCCESS) { printf("[Init] StopAllServices, service %s stop failed!\n", g_services[i].name); } }复制代码
code-1.0/base/startup/services/init_lite/src/init_service.c
int ServiceStop(Service *service){ service->attribute &= ~SERVICE_ATTR_NEED_RESTART; service->attribute |= SERVICE_ATTR_NEED_STOP; if (service->pid <= 0) { return SERVICE_SUCCESS; } // 直接向服务进程发送SIGKILL信号,杀死进程 if (kill(service->pid, SIGKILL) != 0) { printf("[Init] stop service %s pid %d failed! err %d.\n", service->name, service->pid, errno); return SERVICE_FAILURE; } printf("[Init] stop service %s, pid %d.\n", service->name, service->pid); return SERVICE_SUCCESS;}
如果收到程序结束信号SIGTERM,会遍历服务列表,服务列表里面保存着所有服务的pid,通过向pid发送SIGKILL信号,来杀死进程。
Reap Service
如果收到子进程停止或退出的信号SIGCHLD
code-1.0/base/startup/services/init_lite/src/init_service_manager.c
void ReapServiceByPID(int pid){ for (size_t i = 0; i < g_servicesCnt; i++) { if (g_services[i].pid == pid) { if (g_services[i].attribute & SERVICE_ATTR_IMPORTANT) { // important process exit, need to reboot system g_services[i].pid = -1; StopAllServices(); RebootSystem(); } ServiceReap(&g_services[i]); break; } }}
这里分两种情况:如果死掉的是一个important process,则需要杀死所有服务进程,然后重启系统;否则,进行服务收割。
code-1.0/base/startup/services/init_lite/src/init_service.c
void ServiceReap(Service *service){ // 首先将服务pid设置为-1 service->pid = -1; // init设置了服务属性NEED_STOP,所以不需要重启,直接返回 if (service->attribute & SERVICE_ATTR_NEED_STOP) { service->attribute &= (~SERVICE_ATTR_NEED_STOP); service->crashCnt = 0; return; } // 具有ONCE属性的服务 if (service->attribute & SERVICE_ATTR_ONCE) { // no need to restart if (!(service->attribute & SERVICE_ATTR_NEED_RESTART)) { service->attribute &= (~SERVICE_ATTR_NEED_STOP); return; } // the service could be restart even if it is one-shot service } // the service that does not need to be restarted restarts, indicating that it has crashed if (!(service->attribute & SERVICE_ATTR_NEED_RESTART)) { // crash time and count check time_t curTime = time(NULL); // 记录崩溃次数和时间 if (service->crashCnt == 0) { service->firstCrashTime = curTime; ++service->crashCnt; } else if (difftime(curTime, service->firstCrashTime) > CRASH_TIME_LIMIT) { service->firstCrashTime = curTime; service->crashCnt = 1; } else { ++service->crashCnt; // 崩溃超过4次,就不在尝试重启 if (service->crashCnt > CRASH_COUNT_LIMIT) { printf("[Init] reap service %s, crash too many times!\n", service->name); return; } } } // 重启服务 int ret = ServiceStart(service); if (ret != SERVICE_SUCCESS) { printf("[Init] reap service %s start failed!\n", service->name); } // 清除服务的NEED_RESTART属性 service->attribute &= (~SERVICE_ATTR_NEED_RESTART);}
服务属性的状态变化似有点绕,没太看懂!!!
启动服务
code-1.0/base/startup/services/init_lite/src/init_service.c
int ServiceStart(Service *service){ // 首先检查服务属性,如果是无效属性,不执行服务启动 if (service->attribute & SERVICE_ATTR_INVALID) { printf("[Init] start service %s invalid.\n", service->name); return SERVICE_FAILURE; } struct stat pathStat = {0}; service->attribute &= (~(SERVICE_ATTR_NEED_RESTART | SERVICE_ATTR_NEED_STOP)); // 检查服务可执行文件路径,如果文件不存在,则不执行服务启动 if (stat(service->path, &pathStat) != 0) { service->attribute |= SERVICE_ATTR_INVALID; printf("[Init] start service %s invalid, please check %s.\n", service->name, service->path); return SERVICE_FAILURE; } // 调用fork(),创建子进程 int pid = fork(); if (pid == 0) { // permissions if (SetPerms(service) != SERVICE_SUCCESS) { printf("[Init] service %s exit! set perms failed! err %d.\n", service->name, errno); _exit(0x7f); // 0x7f: user specified } char* argv[] = {service->name, NULL}; char* env[] = {NULL}; // 启动服务的可执行文件,传入文件名称参数 if (execve(service->path, argv, env) != 0) { printf("[Init] service %s execve failed! err %d.\n", service->name, errno); } _exit(0x7f); // 0x7f: user specified } else if (pid < 0) { // 子进程创建失败 printf("[Init] start service %s fork failed!\n", service->name); return SERVICE_FAILURE; } // 将得到的pid保存在服务的数据结构里面 service->pid = pid; printf("[Init] start service %s succeed, pid %d.\n", service->name, service->pid); return SERVICE_SUCCESS;}
启动服务,采用fork+execve。
重启系统
code-1.0/base/startup/services/init_lite/src/init_adapter.c
void RebootSystem(){#ifdef __LINUX__ int ret = reboot(RB_DISABLE_CAD);#else int ret = syscall(__NR_shellexec, "reset", "reset");#endif if (ret != 0) { printf("[Init] reboot failed! syscall ret %d, err %d.\n", ret, errno); }}读取配置文件并执行任务
这是init模块的重点。
code-1.0/base/startup/services/init_lite/src/init_read_cfg.c
void InitReadCfg(){ // 读取json格式的引导配置文件 char* fileBuf = ReadFileToBuf(); if (fileBuf == NULL) { printf("[Init] InitReadCfg, read file %s failed! err %d.\n", INIT_CONFIGURATION_FILE, errno); return; } // 解析json文件 cJSON* fileRoot = cJSON_Parse(fileBuf); free(fileBuf); fileBuf = NULL; if (fileRoot == NULL) { printf("[Init] InitReadCfg, parse failed! please check file %s format.\n", INIT_CONFIGURATION_FILE); return; } // 得到服务数据 ParseAllServices(fileRoot); // 得到任务数据 ParseAllJobs(fileRoot); // 释放内存 cJSON_Delete(fileRoot); // 执行任务 DoJob("pre-init"); DoJob("init"); DoJob("post-init"); // 释放Jobs数据结构占据的内存 ReleaseAllJobs();}
读取配置文件
#define INIT_CONFIGURATION_FILE "/etc/init.cfg"static char* ReadFileToBuf(){ char* buffer = NULL; FILE* fd = NULL; struct stat fileStat = {0}; // ??? do...while...0 看不懂,不知道在搞啥? do { // 检查文件有效性 if (stat(INIT_CONFIGURATION_FILE, &fileStat) != 0 || fileStat.st_size <= 0 || fileStat.st_size > MAX_JSON_FILE_LEN) { break; } // 以只读方式打开文件 fd = fopen(INIT_CONFIGURATION_FILE, "r"); if (fd == NULL) { break; } // 分配文件size+1的空间 buffer = (char*)malloc(fileStat.st_size + 1); if (buffer == NULL) { break; } // 从文件读取数据到buffer if (fread(buffer, fileStat.st_size, 1, fd) != 1) { free(buffer); buffer = NULL; break; } // buffer最后一个字节写空字符 buffer[fileStat.st_size] = '\0'; } while (0); if (fd != NULL) { fclose(fd); fd = NULL; } return buffer;}
解析JSON文件
workspace/code-1.0/third_party/cJSON/cJSON.c
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value){ return cJSON_ParseWithOpts(value, 0, 0);}CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated){ size_t buffer_length; if (NULL == value) { return NULL; } /* Adding null character size due to require_null_terminated. */ buffer_length = strlen(value) + sizeof(""); return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated);}
这里使用开源的cJSON库来进行JSON文件解析。
得到服务数据
code-1.0/base/startup/services/init_lite/src/init_read_cfg.c
static void ParseAllServices(const cJSON* fileRoot){ int servArrSize = 0; cJSON* serviceArr = GetArrItem(fileRoot, &servArrSize, SERVICES_ARR_NAME_IN_JSON); if (serviceArr == NULL) { printf("[Init] InitReadCfg, get array %s failed.\n", SERVICES_ARR_NAME_IN_JSON); return; } // 限制配置服务的最大数量是100个 if (servArrSize > MAX_SERVICES_CNT_IN_FILE) { printf("[Init] InitReadCfg, too many services[cnt %d] detected, should not exceed %d.\n",\ servArrSize, MAX_SERVICES_CNT_IN_FILE); return; } // 申请空间存放服务数据 Service* retServices = (Service*)malloc(sizeof(Service) * servArrSize); if (retServices == NULL) { printf("[Init] InitReadCfg, malloc for %s arr failed! %d.\n", SERVICES_ARR_NAME_IN_JSON, servArrSize); return; } if (memset_s(retServices, sizeof(Service) * servArrSize, 0, sizeof(Service) * servArrSize) != EOK) { free(retServices); retServices = NULL; return; } // 遍历服务队列,读取数据到`retServices` for (int i = 0; i < servArrSize; ++i) { // 取得一个JSON格式的服务数据 cJSON* curItem = cJSON_GetArrayItem(serviceArr, i); // 获取服务name和path if (GetServiceString(curItem, &retServices[i], "name", MAX_SERVICE_NAME) != SERVICE_SUCCESS || GetServiceString(curItem, &retServices[i], "path", MAX_SERVICE_PATH) != SERVICE_SUCCESS) { retServices[i].attribute |= SERVICE_ATTR_INVALID; printf("[Init] InitReadCfg, bad string values for service %d.\n", i); continue; } // 获取服务uid、gid、once、importance、 if (GetServiceNumber(curItem, &retServices[i], UID_STR_IN_CFG) != SERVICE_SUCCESS || GetServiceNumber(curItem, &retServices[i], GID_STR_IN_CFG) != SERVICE_SUCCESS || GetServiceNumber(curItem, &retServices[i], ONCE_STR_IN_CFG) != SERVICE_SUCCESS || GetServiceNumber(curItem, &retServices[i], IMPORTANT_STR_IN_CFG) != SERVICE_SUCCESS) { retServices[i].attribute |= SERVICE_ATTR_INVALID; printf("[Init] InitReadCfg, bad number values for service %d.\n", i); continue; } // 获取服务caps if (GetServiceCaps(curItem, &retServices[i]) != SERVICE_SUCCESS) { retServices[i].attribute |= SERVICE_ATTR_INVALID; printf("[Init] InitReadCfg, bad caps values for service %d.\n", i); } } // 赋值给全局变量`g_services` RegisterServices(retServices, servArrSize);}
// All serivce processes that init will fork+exec.static Service* g_services = NULL;static int g_servicesCnt = 0;void RegisterServices(Service* services, int servicesCnt){ g_services = services; g_servicesCnt = servicesCnt;}
得到任务数据
code-1.0/base/startup/services/init_lite/src/init_jobs.c
void ParseAllJobs(const cJSON* fileRoot){ if (fileRoot == NULL) { printf("[Init] ParseAllJobs, input fileRoot is NULL!\n"); return; } // 取得`jobs`的JSON格式的队列 cJSON* jobArr = cJSON_GetObjectItemCaseSensitive(fileRoot, JOBS_ARR_NAME_IN_JSON); int jobArrSize = 0; if (cJSON_IsArray(jobArr)) { jobArrSize = cJSON_GetArraySize(jobArr); } // 最大支持10个任务(组) if (jobArrSize <= 0 || jobArrSize > MAX_JOBS_COUNT) { printf("[Init] ParseAllJobs, jobs count %d is invalid, should be positive and not exceeding %d.\n",\ jobArrSize, MAX_JOBS_COUNT); return; } // 分配内存 Job* retJobs = (Job*)malloc(sizeof(Job) * jobArrSize); if (retJobs == NULL) { printf("[Init] ParseAllJobs, malloc failed! job arrSize %d.\n", jobArrSize); return; } if (memset_s(retJobs, sizeof(Job) * jobArrSize, 0, sizeof(Job) * jobArrSize) != EOK) { printf("[Init] ParseAllJobs, memset_s failed.\n"); free(retJobs); retJobs = NULL; return; } for (int i = 0; i < jobArrSize; ++i) { cJSON* jobItem = cJSON_GetArrayItem(jobArr, i); ParseJob(jobItem, &(retJobs[i])); } // 赋值给全局变量`g_jobs` g_jobs = retJobs; g_jobCnt = jobArrSize;}
static void ParseJob(const cJSON* jobItem, Job* resJob){ // 取得任务名称。 // 任务名称为pre-init/init/post-init三个中一个 if (!GetJobName(jobItem, resJob)) { (void)memset_s(resJob, sizeof(*resJob), 0, sizeof(*resJob)); return; } // 获取任务对应的cmd的JSON数据 cJSON* cmdsItem = cJSON_GetObjectItem(jobItem, CMDS_ARR_NAME_IN_JSON); if (!cJSON_IsArray(cmdsItem)) { return; } // 获取cmd的数量 int cmdLinesCnt = cJSON_GetArraySize(cmdsItem); if (cmdLinesCnt <= 0) { // empty job, no cmd return; } // 一个任务组的cmd不能超过30个 if (cmdLinesCnt > MAX_CMD_CNT_IN_ONE_JOB) { printf("[Init] ParseAllJobs, too many cmds[cnt %d] in one job, it should not exceed %d.\n",\ cmdLinesCnt, MAX_CMD_CNT_IN_ONE_JOB); return; } // 分配内存 resJob->cmdLines = (CmdLine*)malloc(cmdLinesCnt * sizeof(CmdLine)); if (resJob->cmdLines == NULL) { return; } if (memset_s(resJob->cmdLines, cmdLinesCnt * sizeof(CmdLine), 0, cmdLinesCnt * sizeof(CmdLine)) != EOK) { free(resJob->cmdLines); resJob->cmdLines = NULL; return; } resJob->cmdLinesCnt = cmdLinesCnt; for (int i = 0; i < cmdLinesCnt; ++i) { char* cmdLineStr = cJSON_GetStringValue(cJSON_GetArrayItem(cmdsItem, i)); ParseCmdLine(cmdLineStr, &(resJob->cmdLines[i])); }}
code-1.0/base/startup/services/init_lite/src/init_cmds.c
void ParseCmdLine(const char* cmdStr, CmdLine* resCmd){ if (cmdStr == NULL || strlen(cmdStr) == 0 || resCmd == NULL) { return; } // 取得cmd line字符串长度 size_t cmdLineLen = strlen(cmdStr); // 获得支持的命令数量 size_t supportCmdCnt = sizeof(g_supportedCmds) / sizeof(g_supportedCmds[0]); // 声明并初始化标志位:是否找到命令并解析成功 int foundAndSucceed = 0; // 遍历支持的命令列表,判断这个命令是否在支持的列表里面 for (size_t i = 0; i < supportCmdCnt; ++i) { size_t curCmdNameLen = strlen(g_supportedCmds[i]); // 如果cmd line的长度比比较的这个命令长,并且这个命令+max_cmd_content_len的长度小 // 并且cmd line中的命令和这个命令一样 if (cmdLineLen > curCmdNameLen && cmdLineLen <= (curCmdNameLen + MAX_CMD_CONTENT_LEN) && strncmp(g_supportedCmds[i], cmdStr, curCmdNameLen) == 0) { // 写入cmd_name,并把尾字符写入一个空字符 if (memcpy_s(resCmd->name, MAX_CMD_NAME_LEN, cmdStr, curCmdNameLen) != EOK) { break; } resCmd->name[curCmdNameLen] = '\0'; // 写入cmd_content,并把尾字符写入一个空字符 const char* cmdContent = cmdStr + curCmdNameLen; size_t cmdContentLen = cmdLineLen - curCmdNameLen; if (memcpy_s(resCmd->cmdContent, MAX_CMD_CONTENT_LEN, cmdContent, cmdContentLen) != EOK) { break; } resCmd->cmdContent[cmdContentLen] = '\0'; // 设置标志位:找到命令并解析成功 foundAndSucceed = 1; break; } } // 如果没有找到或解析失败,则向其中全部写入0 if (!foundAndSucceed) { (void)memset_s(resCmd, sizeof(*resCmd), 0, sizeof(*resCmd)); }}
纯字符串操作,看着就是这么舒服!
执行任务
任务的执行分三个阶段,按照时间顺序,依次是:pre-init、init、post-init。
根据init_liteos_a_3518ev300.cfg配置来看:
pre-init阶段主要进行目录创建、文件权限设置、分区挂载等。init阶段主要进行服务程序启动post-init阶段主要进行设备文件权限更改
code-1.0\base\startup\services\init_lite\src\init_jobs.c
void DoJob(const char* jobName){ if (jobName == NULL) { printf("[Init] DoJob, input jobName NULL!\n"); return; } for (int i = 0; i < g_jobCnt; ++i) { if (strncmp(jobName, g_jobs[i].name, strlen(g_jobs[i].name)) == 0) { CmdLine* cmdLines = g_jobs[i].cmdLines; for (int j = 0; j < g_jobs[i].cmdLinesCnt; ++j) { DoCmd(&(cmdLines[j])); } break; } }}void DoCmd(const CmdLine* curCmd){ if (curCmd == NULL) { return; } if (strncmp(curCmd->name, "start ", strlen("start ")) == 0) { DoStart(curCmd->cmdContent); } else if (strncmp(curCmd->name, "mkdir ", strlen("mkdir ")) == 0) { DoMkDir(curCmd->cmdContent); } else if (strncmp(curCmd->name, "chmod ", strlen("chmod ")) == 0) { DoChmod(curCmd->cmdContent); } else if (strncmp(curCmd->name, "chown ", strlen("chown ")) == 0) { DoChown(curCmd->cmdContent); } else if (strncmp(curCmd->name, "mount ", strlen("mount ")) == 0) { DoMount(curCmd->cmdContent); } else { printf("[Init] DoCmd, unknown cmd name %s.\n", curCmd->name); }}
目前鸿蒙2.0支持的命令还很少(或是为了简介),只有5个命令:start、mkdir、chmod、chown、mount。start命令指,启动services配置的服务。其它四个命令就是linux系统的同名命令的功能。
DoStart()展开一下,其它四个命令,感兴趣的可以自己跟一下代码。
static void DoStart(const char* cmdContent){ StartServiceByName(cmdContent);}void StartServiceByName(const char* servName){ // 从全局的服务数据结构里面,通过名字查找服务 int servIdx = FindServiceByName(servName); if (servIdx < 0) { printf("[Init] StartServiceByName, cannot find service %s.\n", servName); return; } // 调用ServiceStart()函数启动服务,前面已经展开过 if (ServiceStart(&g_services[servIdx]) != SERVICE_SUCCESS) { printf("[Init] StartServiceByName, service %s start failed!\n", g_services[servIdx].name); } // ??? 这个有啥作用? sleep(SLEEP_DURATION); return;}service属性
code-1.0/base/startup/services/init_lite/include/init_service.h
#define SERVICE_ATTR_INVALID 0x001 // option invalid#define SERVICE_ATTR_ONCE 0x002 // do not restart when it exits#define SERVICE_ATTR_NEED_RESTART 0x004 // will restart in the near future#define SERVICE_ATTR_NEED_STOP 0x008 // will stop in reap#define SERVICE_ATTR_IMPORTANT 0x010 // will reboot if it crash复制代码
service一共有5种属性,每个属性占据一个bit位,依次为:
INVALID 服务不存在ONCE 服务退出后,不进行重启NEED_RESTART 服务退出后,需要(在不就的将来进行)重启NEED_STOP 如果是init进程强行杀死的服务,会设置服务的这个bit位为1IMPORTANT 重要服务,如果服务退出(异常),会导致系统重启关键数据结构
Service
code-1.0\base\startup\services\init_lite\include\init_service.h
typedef struct { uid_t uID; gid_t gID; unsigned int *caps; unsigned int capsCnt;} Perms;typedef struct { char name[MAX_SERVICE_NAME + 1]; char path[MAX_SERVICE_PATH + 1]; int pid; int crashCnt; time_t firstCrashTime; unsigned int attribute; Perms servPerm;} Service;
Service数据结构,定义一个结构体,然后在结构体里面写入服务的每个数据字段。
Job
code-1.0\base\startup\services\init_lite\include\init_jobs.h
// one job, could have many cmd linestypedef struct { char name[MAX_JOB_NAME_LEN + 1]; int cmdLinesCnt; CmdLine* cmdLines;} Job;
code-1.0\base\startup\services\init_lite\include\init_cmds.h
// one cmd linetypedef struct { char name[MAX_CMD_NAME_LEN + 1]; char cmdContent[MAX_CMD_CONTENT_LEN + 1];} CmdLine;
Job数据结构,一个Job可能含有多个cmd,所以结构体设计了三个字段,分别是:Job名字、cmd数量、cmd指针。
全局变量
code-1.0\base\startup\services\init_lite\src\init_service_manager.c
static Service* g_services = NULL;static int g_servicesCnt = 0;
全局变量g_services保存了所有配置的服务。
引导配置文件
code-1.0\vendor\huawei\camera\init_configs\init_liteos_a_3516dv300.cfg
{ "jobs" : [{ "name" : "pre-init", "cmds" : [ "mkdir /sdcard", "chmod 0777 /sdcard", "mount vfat /dev/mmcblk1 /sdcard rw,umask=000" ] }, { "name" : "init", "cmds" : [ "start foundation", "start appspawn" ] }, { "name" : "post-init", "cmds" : [ "chown 0 99 /dev/dev_mgr", "chown 0 99 /dev/hdfwifi" ] } ], "services" : [{ "name" : "foundation", "path" : "/bin/foundation", "uid" : 7, "gid" : 7, "once" : 0, "importance" : 1, "caps" : [10, 11, 12, 13] }, { "name" : "appspawn", "path" : "/bin/appspawn", "uid" : 1, "gid" : 1, "once" : 0, "importance" : 0, "caps" : [2, 6, 7, 8, 23] } ]}
目前一共支持两种类型的定义,一类是services,一类是jobs。services配置的服务可以在jobs中调用start命令来启动。
services有7个配置项,分别是:name、path、uid、gid、once、importance、caps。其中比较重要的是服务名称、可执行文件路径、挂掉之后要不要重启、重要性。可配置的服务最大数量是100个。
name: 服务名称path: 可执行文件路径uid: 用户IDgid: 组IDonce: 挂掉之后需不需要重新拉起来importance: 服务的重要性caps: 暂不明确有什么作用
jobs是设置一些要执行的命令。目前支持5个命令,分别是start、mkdir、chown、chmod、mount。start命令就是用来启动services中定义的服务。
这个JSON配置文件是有很多限制条件的:
文件大小不能超过100KB配置的服务数量不能超过100个服务的名字不能超过32个字符服务的路径不能超过64个字符配置的任务(任务组)数量不能超过10个一个任务组配置的cmd不能超过30个一行一个cmd,包括cmd_name和cmd_contentcmd_name长度不能超过10个字符cmd_content长度不能超过128个字符3518开发板默认配置开机引导都做了什么
创建log文件夹,创建软总线softbus文件夹,创建sdcard文件夹,挂载sdcard,依次启动服务shell、apphilogcat、foundation、bundle_daemon、media_server、appspawn,修改一些设备权限。
后续学习计划
研究完了init模块,后续该如何继续学习呢?
其实根据系统的启动流程来学习就是一个不错的学习路径。init进程启动之后,接着就是这6大服务的启动,后续会根据shell、apphilogcat、foundation、bundle_daemon、media_server、appspawn,这个顺序,依次研究每一个服务。这只是大概的计划,关键模块肯定会非常庞大,遇到的时候需要分而学之。
本文作者:做事情的幻想家
原文链接:
标签: #引导配置数据文件包含的os项目无效