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;
}
从上面的代码可以看到,主流程主要做了这么几件事情:
- 打印系统信息
- 挂载基本文件系统和创建通用设备节点(针对lite设备)
- 注册信号
- 执行rcs
- 读取系统引导配置文件并执行相应的任务
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 命令集说明
命令 | 命令格式和示例 | 说明 |
---|---|---|
mkdir | mkdir 目标文件夹,如:mkdir /storage/myDirectory | 创建文件夹命令,mkdir和目标文件夹之间有且只能有一个空格。 |
chmod | chmod 权限目标,如:chmod 0600 /storage/myFile.txt | 修改权限命令,chmod 权限 目标 之间间隔有且仅有一个空格,权限必须为0xxx格式。 |
chown | chown uid gid 目标,如:chown 900 800 /storage/myDir | 修改属组命令,chown uid gid 目标 之间间隔有且仅有一个空格。 |
mount | mount 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为可选字段。 |
start | start serviceName,如 start shell | 启动服务命令,start后面跟着service名称,该service名称必须能够在services数组中找到。 |
loadcfg | loadcfg 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个值)。 |