從系統啓動流程來看,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
進程進行系統引導的主流程:學習
- 打印系統信息
- 註冊信號
- 讀取系統引導配置文件並執行相應的任務
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,這個順序,依次研究每個服務。這只是大概的計劃,關鍵模塊確定會很是龐大,遇到的時候須要分而學之。
本文參與了「解讀鴻蒙源碼」技術徵文,歡迎正在閱讀的你也加入。