龙空技术网

「HarmonyOS源码学习系列」init模块全解析

闪念基因 680

前言:

当前朋友们对“引导配置数据文件包含的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项目无效