init模塊全解析 | 解讀鴻蒙源碼

從系統啓動流程來看,init位於kernel啓動以後,user程序啓動之前。user程序,是指用戶可交互的程序(好比Home、Shell、智能快遞櫃的交互程序等),也指最終的業務程序(好比智能音箱的業務程序、掃地機器人的工做程序等)。linux

init進程是系統的第一號用戶空間進程,全部的系統進程和user進程都是由它fork()而來,即都是它的子進程。shell

init模塊負責解析系統引導配置文件,並執行裏面的命令,完成系統的引導操做。鴻蒙OS的引導配置文件使用JSON格式。系統開發人員會在這裏接觸到鴻蒙系統的第一個配置文件。這一點應該是借鑑Linux系操做系統。咱們知道Android系統也是基於Linux內核開發的,也有本身實現的init引導程序和本身的Android initial language編寫的init.rc引導配置文件。這些都是聽從或借鑑Linux操做系統,Linux操做系統就是經過init引導程序解析執行不一樣目錄的shell腳原本進行系統引導的。json

下面直接擼代碼,來看一下鴻蒙init引導程序的實現。(源碼部分會有些枯燥,操做代碼+註釋的形式開展)數據結構

init模塊源碼分析

code-1.0/base/startup/services/init_lite/app

上面這個是init模塊的代碼目錄。這個模塊很小巧,整個目錄全部文件加起來只有108KB,源文件只有8個。最大的源文件代碼不超過350行。函數

init主流程源碼分析

code-1.0/base/startup/services/init_lite/src/main.cpost

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進程進行系統引導的主流程:學習

  1. 打印系統信息
  2. 註冊信號
  3. 讀取系統引導配置文件並執行相應的任務
  4. init進程進入無限循環狀態

這個流程很清晰簡潔。看過Android操做系統init模塊源代碼的人,應該會頗有感觸,這份代碼短小精悍,閱讀起來很輕鬆。或許,畢竟這只是HarmonyOS 2.0,不知道通過幾個版本的迭代會不會也變得很臃腫呢。ui

下面詳細分析每一步的原理和代碼實現。

打印系統信息

這一步是把系統信息輸出到控制檯,系統信息是由多個字段拼接而成的。這個系統信息相似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[] = {"****"};

第七、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.gn
code-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位爲1
  • IMPORTANT 重要服務,若是服務退出(異常),會致使系統重啓

關鍵數據結構

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 lines
typedef 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 line
typedef 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: 用戶ID
  • gid: 組ID
  • once: 掛掉以後需不須要從新拉起來
  • importance: 服務的重要性
  • caps: 暫不明確有什麼做用

jobs是設置一些要執行的命令。目前支持5個命令,分別是start、mkdir、chown、chmod、mount。start命令就是用來啓動services中定義的服務。

這個JSON配置文件是有不少限制條件的:

  • 文件大小不能超過100KB
  • 配置的服務數量不能超過100個
  • 服務的名字不能超過32個字符
  • 服務的路徑不能超過64個字符
  • 配置的任務(任務組)數量不能超過10個
  • 一個任務組配置的cmd不能超過30個
  • 一行一個cmd,包括cmd_name和cmd_content
  • cmd_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,這個順序,依次研究每個服務。這只是大概的計劃,關鍵模塊確定會很是龐大,遇到的時候須要分而學之。

本文參與了「解讀鴻蒙源碼」技術徵文,歡迎正在閱讀的你也加入。

相關文章
相關標籤/搜索