从系统启动流程来看,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.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,这个顺序,依次研究每一个服务。这只是大概的计划,关键模块肯定会非常庞大,遇到的时候需要分而学之。