淘先锋技术网

首页 1 2 3 4 5 6 7

1 简介

init组件负责处理从内核加载第一个用户态进程开始,到第一个应用程序启动之间的系统服务进程启动过程。

从系统启动流程来看,init位于kernel启动之后,user程序启动以前。

user程序,是指用户可交互的程序(比如Home、SystemUI、WeChat等)。

init模块负责解析系统引导配置文件,并执行里面的命令,完成系统的引导操作。鸿蒙OS的引导配置文件使用JSON格式。系统开发人员会在这里接触到鸿蒙系统的第一个配置文件。这一点应该是借鉴Linux系操作系统。我们知道Android系统也是基于Linux内核开发的,也有自己实现的init引导程序和自己的Android initial language编写的init.rc引导配置文件。这些都是遵从或借鉴Linux操作系统,Linux操作系统就是通过init引导程序解析执行不同目录的shell脚本来进行系统引导的。

2 代码路径与目录

base/startup/init_lite/             # init组件
├── LICENSE
└── services
    ├── include                  # init组件头文件目录
    ├── src                      # init组件源文件目录
    └── test                     # init组件测试用例源文件目录
        └── unittest
vendor
└──huawei
        └──camera
                └──init_configs  # init配置文件目录(json格式,镜像烧写后部于/etc/init.cfg)

3 init启动主流程

整个init_lite/services目录代码量不大,c文件一共11个文件,70多kb,最大的一个c文件也只有811行。

先来看看init的入口main

//base\startup\init_lite\services\src\main.c
int main(int argc, char * const argv[])
{
#ifdef OHOS_DEBUG
    struct timespec tmEnter;
    if (clock_gettime(CLOCK_REALTIME, &tmEnter) != 0) {
        printf("[Init] main, enter, get time failed! err %d.\n", errno);
    }
#endif // OHOS_DEBUG

    if (getpid() != INIT_PROCESS_PID) {
        printf("[Init] main, current process id is %d not %d, failed!\n", getpid(), INIT_PROCESS_PID);
        return 0;
    }

    // 1. print system info
    PrintSysInfo();

#ifndef OHOS_LITE
    // 2. Mount basic filesystem and create common device node.
    MountBasicFs();
    CreateDeviceNode();
#endif

    // 3. signal register
    SignalInitModule();

#ifdef OHOS_DEBUG
    struct timespec tmSysInfo;
    if (clock_gettime(CLOCK_REALTIME, &tmSysInfo) != 0) {
        printf("[Init] main, after sysinfo, get time failed! err %d.\n", errno);
    }
#endif // OHOS_DEBUG

    // 4. execute rcs
    ExecuteRcs();

#ifdef OHOS_DEBUG
    struct timespec tmRcs;
    if (clock_gettime(CLOCK_REALTIME, &tmRcs) != 0) {
        printf("[Init] main, after rcs, get time failed! err %d.\n", errno);
    }
#endif // OHOS_DEBUG

    // 5. read configuration file and do jobs
    InitReadCfg();

#ifdef OHOS_DEBUG
    struct timespec tmCfg;
    if (clock_gettime(CLOCK_REALTIME, &tmCfg) != 0) {
        printf("[Init] main, after cfg, get time failed! err %d.\n", errno);
    }
#endif // OHOS_DEBUG

    // 6. keep process alive
#ifdef OHOS_DEBUG
    printf("[Init] main, time used: sigInfo %ld ms, rcs %ld ms, cfg %ld ms.\n", \
        TimeDiffMs(&tmEnter, &tmSysInfo), TimeDiffMs(&tmSysInfo, &tmRcs), TimeDiffMs(&tmRcs, &tmCfg));
#endif

    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;
}

从上面的代码可以看到,主流程主要做了这么几件事情:

  1. 打印系统信息
  2. 挂载基本文件系统和创建通用设备节点(针对lite设备)
  3. 注册信号
  4. 执行rcs
  5. 读取系统引导配置文件并执行相应的任务
  6. init进程进入无限循环状态

4 init启动流程分解

4.1 打印系统信息

这一步是把系统信息输出到控制台,系统信息是由多个字段拼接而成的。这个系统信息类似Android操作系统的fingerprint,是一个很长的字符串,里面包含厂商、品牌、编译类型等。

//base\startup\init_lite\services\src\main.c
static void PrintSysInfo()
{
#ifdef OHOS_LITE
    const char* sysInfo = GetVersionId();
    if (sysInfo != NULL) {
        printf("[Init] %s\n", sysInfo);
        return;
    }
    printf("[Init] main, GetVersionId failed!\n");
#endif
}

再来看GetVersionId

//base\startup\syspara_lite\frameworks\parameter\src\parameter_common.c
const char* GetVersionId(void)
{
    static const char* versionId = NULL;
    if (versionId != NULL) {
        return versionId;
    }
    versionId = BuildVersionId();
    if (versionId == NULL) {
        return EMPTY_STR;
    }
    return versionId;
}

static const char* BuildVersionId(void)
{
    char value[VERSION_ID_LEN];
    int len = sprintf_s(value, VERSION_ID_LEN, "%s/%s/%s/%s/%s/%s/%s/%d/%s/%s",
        GetDeviceType(), GetManufacture(), GetBrand(), GetProductSeries(),
        GetOSFullName(), GetProductModel(), GetSoftwareModel(),
        OHOS_SDK_API_VERSION, GetIncrementalVersion(), GetBuildType());
    if (len < 0) {
        return EMPTY_STR;
    }
    const char* versionId = strdup(value);
    return versionId;
}

const char* GetDeviceType(void)
{
    return HalGetDeviceType();
}

主要是设备、厂商、产品型号,版本号等信息,然后看看这些信息是怎么读取的。

//base\startup\syspara_lite\hals\parameter\src\parameter_hal.cpp
const char *HalGetDeviceType()
{
    static const char *productType = nullptr;
    return GetProperty("ro.build.characteristics", &productType);
}

看到ro.build.characteristics,跟Android一样,也是读取的属性。

4.2 挂载基本文件系统和创建通用设备节点

在MountBasicFs函数中,主要是挂载tmpfs、proc、sysfs文件系统,而CreateDeviceNode中,主要是创建/dev/kmsg、/dev/null、/dev/random、/dev/urandom设备节点。

//base\startup\init_lite\services\src\device.c
void MountBasicFs()
{
    if (mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755") != 0) {
        printf("Mount tmpfs failed. %s\n", strerror(errno));
    }
    if (mount("proc", "/proc", "proc", 0, "hidepid=2") != 0) {
        printf("Mount procfs failed. %s\n", strerror(errno));
    }
    if (mount("sysfs", "/sys", "sysfs", 0, NULL) != 0) {
        printf("Mount sysfs failed. %s\n", strerror(errno));
    }
}

void CreateDeviceNode()
{
    if (mknod("/dev/kmsg", S_IFCHR | DEFAULT_NO_AUTHORITY_MODE, makedev(1, DEVICE_ID_ELEVNTH)) != 0) {
        printf("Create /dev/kmsg device node failed. %s\n", strerror(errno));
    }
    if (mknod("/dev/null", S_IFCHR | DEFAULT_RW_MODE, makedev(1, DEVICE_ID_THIRD)) != 0) {
        printf("Create /dev/null device node failed. %s\n", strerror(errno));
    }
    if (mknod("/dev/random", S_IFCHR | DEFAULT_RW_MODE, makedev(1, DEVICE_ID_EIGHTH)) != 0) {
        printf("Create /dev/random device node failed. %s\n", strerror(errno));
    }

    if (mknod("/dev/urandom", S_IFCHR | DEFAULT_RW_MODE, makedev(1, DEVICE_ID_NINTH)) != 0) {
        printf("Create /dev/urandom device node failed. %s\n", strerror(errno));
    }
}

4.3 注册信号

这里主要是注册信号,一共接管了两个SIGCHLD和SIGTERM。

//base\startup\init_lite\services\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

//base\startup\init_lite\services\src\init_signal_handler.c
static void SigHandler(int sig)
{
    switch (sig) {
        case SIGCHLD: {
            pid_t sigPID;
            int procStat = 0;
            while (1) {
                sigPID = waitpid(-1, &procStat, WNOHANG);
                if (sigPID <= 0) {
                    break;
                }
                printf("[Init] SigHandler, SIGCHLD received, sigPID = %d.\n", sigPID);
#ifdef __LINUX__
                CheckWaitPid(sigPID);
#endif /* __LINUX__ */
                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不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出。

4.3.1 ReapService

如果收到子进程停止或退出的信号SIGCHLD

//base\startup\init_lite\services\src\init_service_manager.c
void ReapServiceByPID(int pid)
{
    for (int 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,则需要杀死所有服务进程,然后重启系统;否则,进行服务收割。

先来看看重启系统的方式:

//base\startup\init_lite\services\src\init_adapter.c
void RebootSystem()
{
    int ret = reboot(RB_AUTOBOOT);
    if (ret != 0) {
        printf("[Init] reboot failed! syscall ret %d, err %d.\n", ret, errno);
    }
}

原来是直接调用reboot来重启的。再来看服务收割ServiceReap。

//base\startup\init_lite\services\src\init_service.c
void ServiceReap(Service *service)
{
    if (service == NULL) {
        printf("[Init] reap service failed! null ptr.\n");
        return;
    }

    service->pid = -1;

    // stopped by system-init itself, no need to restart even if it is not one-shot service
    if (service->attribute & SERVICE_ATTR_NEED_STOP) {
        service->attribute &= (~SERVICE_ATTR_NEED_STOP);
        service->crashCnt = 0;
        return;
    }

    // for one-shot service
    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;
            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);
    }

    service->attribute &= (~SERVICE_ATTR_NEED_RESTART);
}

首先将服务pid设置为-1,如果该服务设置了服务属性NEED_STOP,就不需要重启,直接返回 ,即使服务不是oneshot类型的。而对于具有oneshot属性的服务,如果服务没有设置NEED_RESTART属性,则不重启,否则重启服务。另外,如果服务不是NEED_RESTART,则会记录服务crash的时间和次数,如果crash超过4次,就不再重启服务。然后进行重启服务。,最后清除服务的NEED_RESTART属性。

//base\startup\init_lite\services\src\init_service.c
int ServiceStart(Service *service)
{
    if (service == NULL) {
        printf("[Init] start service failed! null ptr.\n");
        return SERVICE_FAILURE;
    }

    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->pathArgs[0], &pathStat) != 0) {
        service->attribute |= SERVICE_ATTR_INVALID;
        printf("[Init] start service %s invalid, please check %s.\n",\
            service->name, service->pathArgs[0]);
        return SERVICE_FAILURE;
    }

    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* env[] = {"LD_LIBRARY_PATH=/storage/app/libs", NULL};
        if (execve(service->pathArgs[0], service->pathArgs, 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;
    }

    service->pid = pid;
    printf("[Init] start service %s succeed, pid %d.\n", service->name, service->pid);
    return SERVICE_SUCCESS;
}

ServiceStart首先检查服务属性,如果是无效属性,不执行服务启动。然后检查服务可执行文件路径,如果文件不存在,则不执行服务启动。

然后调用fork(),创建子进程,启动服务的可执行文件,传入文件名称参数,然后 将得到的pid保存在服务的数据结构里面。

可以看到,启动服务的方式,采用的是fork+execve。

4.3.2 杀死所有服务

当接管到SIGTERM信号时,则杀死所有服务。

//base\startup\init_lite\services\src\init_service_manager.c
void StopAllServices()
{
    for (int 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);
        }
    }
}

StopAllServices直接遍历所有注册的服务,然后调用ServiceStop来杀死服务。

//base\startup\init_lite\services\src\init_service.c
int ServiceStop(Service *service)
{
    if (service == NULL) {
        printf("[Init] stop service failed! null ptr.\n");
        return SERVICE_FAILURE;
    }

    service->attribute &= ~SERVICE_ATTR_NEED_RESTART;
    service->attribute |= SERVICE_ATTR_NEED_STOP;
    if (service->pid <= 0) {
        return SERVICE_SUCCESS;
    }

    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信号,来杀死进程。

4.4 执行Rcs

//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) {
        printf("[Init] ExecuteRcs, fork failed! err %d.\n", errno);
        return;
    }

    // child process
    if (retPid == 0) {
        printf("[Init] ExecuteRcs, child process id %d.\n", getpid());
        if (execle("/bin/sh", "sh", "/etc/init.d/rcS", NULL, NULL) != 0) {
            printf("[Init] ExecuteRcs, execle failed! err %d.\n", errno);
        }
        _exit(0x7f); // 0x7f: user specified
    }

    // init process
    sem_t sem;
    if (sem_init(&sem, 0, 0) != 0) {
        printf("[Init] ExecuteRcs, sem_init failed, err %d.\n", errno);
        return;
    }
    SignalRegWaitSem(retPid, &sem);

    // wait until rcs process exited
    if (sem_wait(&sem) != 0) {
        printf("[Init] ExecuteRcs, sem_wait failed, err %d.\n", errno);
    }
#endif
}

这里主要是fork子进程来执行/etc/init.d/rcS

4.5 读取系统引导配置文件并执行相应的任务

//base\startup\init_lite\services\src\init_read_cfg.c
void InitReadCfg()
{
    // read configuration file in json format
    char* fileBuf = ReadFileToBuf();
    if (fileBuf == NULL) {
        printf("[Init] InitReadCfg, read file %s failed! err %d.\n", INIT_CONFIGURATION_FILE, errno);
        return;
    }

    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;
    }

    // parse services
    ParseAllServices(fileRoot);

    // parse jobs
    ParseAllJobs(fileRoot);

    // release memory
    cJSON_Delete(fileRoot);

    // do jobs
    DoJob("pre-init");
#ifndef __LINUX__
#ifdef OHOS_LITE
    TriggerStage(EVENT1, EVENT1_WAITTIME, QS_STAGE1);
#endif
#endif

    DoJob("init");
#ifndef __LINUX__
#ifdef OHOS_LITE
    TriggerStage(EVENT2, EVENT2_WAITTIME, QS_STAGE2);
#endif
#endif

    DoJob("post-init");
#ifndef __LINUX__
#ifdef OHOS_LITE
    TriggerStage(EVENT3, EVENT3_WAITTIME, QS_STAGE3);

    InitStageFinished();
#endif
#endif
    ReleaseAllJobs();
}

4.5.1 首先读取json格式的引导配置文件

//base\startup\init_lite\services\src\init_read_cfg.c
static char* ReadFileToBuf()
{
    char* buffer = NULL;
    FILE* fd = NULL;
    struct stat fileStat = {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;
        }

        buffer = (char*)malloc(fileStat.st_size + 1); // 分配文件size+1的空间	
        if (buffer == NULL) {
            break;
        }

        if (fread(buffer, fileStat.st_size, 1, fd) != 1) {  // 从文件读取数据到buffer
            free(buffer);
            buffer = NULL;
            break;
        }
        buffer[fileStat.st_size] = '\0'; // buffer最后一个字节写空字符
    } while (0);

    if (fd != NULL) {
        fclose(fd);
        fd = NULL;
    }
    return buffer;
}

可以看到配置文件的路径为/etc/init.cfg

4.5.2 json解析

这里使用开源的cJSON库来进行JSON文件解析,因此不做分析。

4.5.3 解析服务

//base\startup\init_lite\services\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;
    }

    if (servArrSize > MAX_SERVICES_CNT_IN_FILE) { // 限制配置服务的最大数量是100个
        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;
    }

    for (int i = 0; i < servArrSize; ++i) {  // 遍历服务队列,读取数据到`retServices`
        cJSON* curItem = cJSON_GetArrayItem(serviceArr, i); // 取得一个JSON格式的服务数据
         // 获取服务name、path、uid、gid、once、importance、caps
        if (GetServiceName(curItem, &retServices[i]) != SERVICE_SUCCESS || 
            GetServicePathAndArgs(curItem, &retServices[i]) != SERVICE_SUCCESS ||
            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 ||
            GetServiceCaps(curItem, &retServices[i]) != SERVICE_SUCCESS) {
            // release resources if it fails
            ReleaseServiceMem(&retServices[i]);
            retServices[i].attribute |= SERVICE_ATTR_INVALID;
            printf("[Init] InitReadCfg, parse information for service %d failed.\n", i);
            continue;
        }
    }
    // 赋值给全局变量`g_services`
    RegisterServices(retServices, servArrSize);
}
//base\startup\init_lite\services\src\init_service_manager.c
// 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;
}

4.5.4 得到任务数据

//base\startup\init_lite\services\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);
    if (!cJSON_IsArray(jobArr)) {
        printf("[Init] ParseAllJobs, job item is not array!\n");
        return;
    }

    int jobArrSize = cJSON_GetArraySize(jobArr);
    if (jobArrSize <= 0 || jobArrSize > MAX_JOBS_COUNT) { // 最大支持10个任务(组)
        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 = retJobs; // 赋值给全局变量`g_jobs`
    g_jobCnt = jobArrSize;
}
//base\startup\init_lite\services\src\init_jobs.c
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]));	
    }
}
//base\startup\init_lite\services\src\init_jobs.c	
void ParseCmdLine(const char* cmdStr, CmdLine* resCmd)
{
    size_t cmdLineLen = 0;
    // 取得cmd line字符串长度
    if (cmdStr == NULL || resCmd == NULL || (cmdLineLen = strlen(cmdStr)) == 0) {
        return;
    }
	 // 获得支持的命令数量
    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;
        }
    }

    if (!foundAndSucceed) { // 如果没有找到或解析失败,则向其中全部写入0
        (void)memset_s(resCmd, sizeof(*resCmd), 0, sizeof(*resCmd));
    }
}

4.5.5 释放json

4.5.6 执行init

任务的执行分三个阶段,按照时间顺序,依次是:pre-init、init、post-init。

根据init_liteos_a_3518ev300.cfg配置来看:

  • pre-init阶段主要进行目录创建、文件权限设置、分区挂载等。
  • init阶段主要进行服务程序启动
  • post-init阶段主要进行设备文件权限更改
//base\startup\init_lite\services\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;
        }
    }
}
//base\startup\init_lite\services\src\init_cmds.c
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 if (strncmp(curCmd->name, "loadcfg ", strlen("loadcfg ")) == 0) {
        DoLoadCfg(curCmd->cmdContent);
#ifndef OHOS_LITE
    } else if (strncmp(curCmd->name, "insmod ", strlen("insmod ")) == 0) {
        DoInsmod(curCmd->cmdContent);
#endif
    } else {
        printf("[Init] DoCmd, unknown cmd name %s.\n", curCmd->name);
    }
}

目前鸿蒙2.0支持的命令还很少,只有7个命令:start、mkdir、chmod、chown、mount、loadcfg、insmod。start命令指,启动services配置的服务。除了loadcfg,其它5个命令就是linux系统的同名命令的功能。

DoStart()展开一下,其它四个命令,感兴趣的可以自己跟一下代码。

//base\startup\init_lite\services\src\init_cmds.c
static void DoStart(const char* cmdContent)
{
    StartServiceByName(cmdContent);
}
//base\startup\init_lite\services\src\init_service_manager.c
void StartServiceByName(const char* servName)
{
    // find service by name
    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);
    }

    return;
}

4.5.7 释放jobs

//base\startup\init_lite\services\src\init_jobs.c
void ReleaseAllJobs()
{
    if (g_jobs == NULL) {
        return;
    }

    for (int i = 0; i < g_jobCnt; ++i) {
        if (g_jobs[i].cmdLines != NULL) {
            free(g_jobs[i].cmdLines);
            g_jobs[i].cmdLines = NULL;
            g_jobs[i].cmdLinesCnt = 0;
        }
    }

    free(g_jobs);
    g_jobs = NULL;
    g_jobCnt = 0;
}

4.6 init无限循环

//base\startup\init_lite\services\src\main.c
int main(int argc, char * const argv[])
{
    ......
	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;
}

5 配置文件格式

上述每个阶段在配置文件init.cfg中都用一个job表示,每个job都对应一个命令集合,init通过依次执行每个job中的命令来完成系统初始化。job执行顺序:先执行“pre-init”,再执行“init”,最后执行“post-init”,所有job都集中放在init.cfg的jobs数组中。

除上述jobs数组之外,init.cfg中还有一个services数组,用于存放所有需要由init进程启动的系统关键服务的服务名、可执行文件路径、权限和其他属性信息。

配置文件init.cfg位于代码仓库/vendor/hisilicon/hispark_aries/init_configs/目录,部署在/etc/下,采用json格式,文件大小目前限制在100KB以内。

配置文件格式和内容说明如下所示:

//vendor/hisilicon/hispark_aries/init_configs/init_liteos_a_3518ev300.cfg
{
    "jobs" : [{
            "name" : "pre-init",
            "cmds" : [
                "mkdir /storage/data/log",
                "chmod 0755 /storage/data/log",
                "chown 4 4 /storage/data/log",
                "mkdir /storage/data/softbus",
                "chmod 0700 /storage/data/softbus",
                "chown 7 7 /storage/data/softbus",
                "mkdir /sdcard",
                "chmod 0777 /sdcard",
                "mount vfat /dev/mmcblk0 /sdcard rw,umask=000",
                "mount vfat /dev/mmcblk1 /sdcard rw,umask=000",

                "start foundation",
                "start bundle_daemon",
                "start appspawn",
                "start media_server",
                "start wms_server",
                "start shell"
            ]
       }, {
            "name" : "init",
            "cmds" : [
                "start apphilogcat",
                "start hiview",
                "start sensor_service",
                "start ai_server"
            ]
       }, {
            "name" : "post-init",
            "cmds" : [
                "chown 0 99 /dev/hdf/dev_mgr",
                "chown 0 99 /dev/hdf/hdfwifi",
                "chown 0 99 /dev/gpio",
                "chown 0 99 /dev/i2c-0",
                "chown 0 99 /dev/i2c-1",
                "chown 0 99 /dev/i2c-2",
                "chown 0 99 /dev/i2c-3",
                "chown 0 99 /dev/i2c-4",
                "chown 0 99 /dev/i2c-5",
                "chown 0 99 /dev/i2c-6",
                "chown 0 99 /dev/i2c-7",
                "chown 0 99 /dev/uartdev-0",
                "chown 0 99 /dev/uartdev-1",
                "chown 0 99 /dev/uartdev-2",
                "chown 0 99 /dev/uartdev-3",
                "chown 0 99 /dev/spidev0.0",
                "chown 0 99 /dev/spidev1.0",
                "chown 0 99 /dev/spidev2.0",
                "chown 0 99 /dev/spidev2.1"
         ]
        }
    ],
    "services" : [{
            "name" : "foundation",
            "path" : ["/bin/foundation"],
            "uid" : 7,
            "gid" : 7,
            "once" : 0,
            "importance" : 1,
            "caps" : [10, 11, 12, 13]
        }, {
            "name" : "shell",
            "path" : ["/bin/shell"],
            "uid" : 2,
            "gid" : 2,
            "once" : 0,
            "importance" : 0,
            "caps" : [4294967295]
        }, {
            "name" : "appspawn",
            "path" : ["/bin/appspawn"],
            "uid" : 1,
            "gid" : 1,
            "once" : 0,
            "importance" : 0,
            "caps" : [2, 6, 7, 8, 11, 23]
        }, {
            "name" : "apphilogcat",
            "path" : ["/bin/apphilogcat", "-L", "auto"],
            "uid" : 4,
            "gid" : 4,
            "once" : 1,
            "importance" : 0,
            "caps" : []
        }, {
            "name" : "media_server",
            "path" : ["/bin/media_server"],
            "uid" : 5,
            "gid" : 5,
            "once" : 1,
            "importance" : 0,
            "caps" : []
        }, {
            "name" : "wms_server",
            "path" : ["/bin/wms_server"],
            "uid" : 0,
            "gid" : 0,
            "once" : 1,
            "importance" : 0,
            "caps" : []
        }, {
            "name" : "bundle_daemon",
            "path" : ["/bin/bundle_daemon"],
            "uid" : 8,
            "gid" : 8,
            "once" : 0,
            "importance" : 0,
            "caps" : [0, 1]
        }, {
            "name" : "hiview",
            "path" : ["/bin/hiview"],
            "uid" : 4,
            "gid" : 4,
            "once" : 1,
            "importance" : 0,
            "caps" : []
        }, {
	    "name" : "sensor_service",
            "path" : ["/bin/sensor_service"],
            "uid" : 0,
            "gid" : 0,
            "once" : 0,
            "importance" : 0,
            "caps" : []
	}, {
            "name" : "ai_server",
            "path" : ["/bin/ai_server"],
            "uid" : 2,
            "gid" : 2,
            "once" : 0,
            "importance" : 0,
            "caps" : []
        }
    ]
}

6 配置文件使用

表 1 执行job介绍

job名说明
pre-init最先执行的job,如果开发者的进程在启动之前需要首先执行一些操作(例如创建文件夹),可以把操作放到pre-init中先执行。
init中间执行的job,例如服务启动。
post-init最后被执行的job,如果开发者的进程在启动完成之后需要有一些处理(如驱动初始化后再挂载设备),可以把这类操作放到该job执行。

单个job最多支持30条命令(当前仅支持start/mkdir/chmod/chown/mount/loadcfg),命令名称和后面的参数(参数长度≤128字节)之间有且只能有一个空格。

表 2 命令集说明

命令命令格式和示例说明
mkdirmkdir 目标文件夹,如:mkdir /storage/myDirectory创建文件夹命令,mkdir和目标文件夹之间有且只能有一个空格。
chmodchmod 权限目标,如:chmod 0600 /storage/myFile.txt修改权限命令,chmod 权限 目标 之间间隔有且仅有一个空格,权限必须为0xxx格式。
chownchown uid gid 目标,如:chown 900 800 /storage/myDir修改属组命令,chown uid gid 目标 之间间隔有且仅有一个空格。
mountmount fileSystemType src dst flags data,如:mount vfat /dev/mmcblk0 /sdc rw,umask=000,mount jffs2 /dev/mtdblock3 /storage nosuid挂载命令,各参数之间有且仅有一个空格。flags当前仅支持nodev、noexec、nosuid、rdonly,data为可选字段。
startstart serviceName,如 start shell启动服务命令,start后面跟着service名称,该service名称必须能够在services数组中找到。
loadcfgloadcfg filePath, 如:loadcfg /patch/fstab.cfg加载其他cfg文件命令。后面跟着的目标文件大小不得超过50KB,且目前仅支持加载/patch/fstab.cfg,其他文件路径和文件名均不支持。/patch/fstab.cfg文件的每一行都是一条命令,命令类型和格式必须符合本表格描述,命令条数不得超过20条。

表 3 service字段说明

字段名说明
name当前服务的名称,须确保非空且长度≤32字节。
path当前服务的可执行文件全路径和参数,数组形式。须确保第一个数组元素为可执行文件路径、数组元素个数≤20、每个元素为字符串形式以及每个字符串长度≤64字节。
uid当前服务进程的uid值。
gid当前服务进程的gid值。
once当前服务进程是否为一次性进程:
1:一次性进程,当该进程退出时,init不会重新启动该服务进程
0 : 常驻进程,当该进程退出时,init收到SIGCHLD信号并重新启动该服务进程;
注意:对于常驻进程,若在4分钟之内连续退出5次,第5次退出时init将不会再重新拉起该服务进程。
importance当前服务进程是否为关键系统进程:
0:非关键系统进程,当该进程退出时,init不会将系统复位重启;
1:关键系统进程,当该进程退出时,init将系统复位重启。
caps当前服务所需的capability值,根据安全子系统已支持的capability,评估所需的capability,遵循最小权限原则配置(当前最多可配置100个值)。