基于SMAGR_BootStrap函数谈整个SA服务框架的启动
该篇文章从SMAGR_BootStrap函数出发去探究整个SA服务框架的启动过程
首先我们看到一个存储信息的结构体SAStore
1. SA_Store——服务信息存储
saStore主要存储了service和feature相关的信息数据(包括service的handle和feature的token等),数据结构为循环链表,通过pid可以唯一定位一个service和maps中的一项数据体,在功能函数中使用了插入排序根据pid的大小对maps的数据项进行正向排序,使用二分查找对maps中数据项进行查找
其结构体嵌套较为复杂,所以将其展开平铺,便于便于读者更好的理解结构体之间的嵌套关系:
之后我们正式进入SAMGR_BootStrap的解读
2. SAMGR_BootStrap——服务的初始化
在注册完系统服务和APP服务后,我们通过SAMGR_Bootstrap函数进行smagr的启动,将所有未初始化的服务进行初始化,使得整个系统进入DYNAMIC平稳运行的状态。我们将其拆分为几个函数模块来看
2.1 void SAMGR_Bootstrap(void)——主函数
首先我们宏观把握这个函数做了哪些事,后面再深入具体函数来看它是如何实现这个功能的
函数流程:
1.得到前面创建好的g_samgrImpl并对其进行mutex的检查
2. 创建一个临时的Vector用于保存后面检查出的未初始化的服务
3. 系统对应的BootStatus进入下一个状态
4. 获取互斥锁,开始对共享数据的修改和访问
5. 遍历samgr->services中的每个服务
6. 找到其中所有没有完成初始化的服务将其serviceImpl指针添加到上面的临时Vector中
7. 互斥锁的释放
8. 调用InitializeAllServices将Vector传入,初始化所有服务
9. 调用VECTOR_Clear将临时Vector释放
10. 调用InitCompleted检查是否全部初始化完成
11. 调用HILOG_INFO返回系统状态可以往下一步
注意:函数InitializeAllServices和InitCompleted所做的工作还有很多,包括taskPool的创建,消息处理相关,后面会深入函数进行分析
2.2 总结——服务初始化的三步走
同样通过图示的方式将流程更加清晰地展现
一个完整的服务的创建和运行需要进行三步走:
- 通过SYS_SERVICE_INIT进行服务的注册
- 通过SYS_FEATURE_INIT进行feature和featureApi的注册(如果存在feature)
- 通过SAMGR_BootStrap函数将服务初始化,分配taskPool并初始化messageHandle
经过以上三步一个服务就真正创建完毕了
而一个完整的系统的创建和运行也是三步走:
- 完成所有系统服务和相关feature的注册(包括三个系统服务:BootStrap、Broadcast和hiview)
- 完成所有用户自定义的APP服务和相关feature的注册
- 通过sendBootRequest启动SMAGR_BootStrap将所有注册但是未初始化启动的服务进行初始化启动
下面从两个重要的函数InitializeAllServices和InitCompleted入手具体看服务的初始化是如何实现
3. InitializeAllServices——实现所有服务的初始化
3.1 整体解读
3.1.1 InitializeAllServices
首先我们从整体出发来看看InitializeAllServices都干了哪些工作
函数流程:主要是两个for循环
1.在第一个for循环中,利用VECTOR_At得到Vector中管理的服务serviceImpl
2.然后通过service实现的两个函数GetTaskConfig和GetName获得taskConfig和字符串服务名字
3.根据serviceImpl、taskConfig、serviceName调用AddTaskPool进行三类taskPool的创建
4.再调用InitializeSingleService对每个serviceImpl进行服务初始化
5.第二个for循环中,同样拿到service对应的serviceImpl和name
6.对于每一个serviceImpl调用SAMGR_StartTaskPool开启线程池和队列任务
这里需要注意一点:为什么要分为两个for循环来做呢?——从代码中我们可以看到第二次for循环在获取了互斥锁之后进行操作的——说明涉及到共享数据或者结构体的修改和读写,而上面的for循环并不需要,所以分成了两个for循环
给出InitializeAllServices的流程图供读者参考
3.1.2 重要结构体TaskConfig和TaskPool
这里我们看到一个新的结构体类型TaskConfig
struct TaskConfig {
int16 level;//多服务共享任务的分类level 在enum SpecifyTag中有详细取值
int16 priority;//任务优先级 在enum TaskPrioriy中有详细取值
uint16 stackSize;//任务堆栈的大小
uint16 queueSize;//任务队列的大小
uint8 taskFlags;//任务标志 在enum taskType中有详细取值
};
typedef enum SpecifyTag {
LEVEL_HIGH = 0,
LEVEL_MIDDLE = 1,
LEVEL_LOW = 2,
LEVEL_CUSTOM_BEGIN,
} SpecifyTag;
//鸿蒙原本的注释就比较详细
typedef enum TaskPriority {
/** Low-priority: (9, 15) */
PRI_LOW = 9,
/** Lower than the normal priority: [16, 23) */
PRI_BELOW_NORMAL = 16,
/** Normal priority: [24, 31). The log service is available. */
PRI_NORMAL = 24,
/** Higher than the normal priority: [32, 39). The communication service is available. */
PRI_ABOVE_NORMAL = 32,
/** Upper limit of the priority */
PRI_BUTT = 39,
} TaskPriority;
//任务类型——也就是在AddTaskPool中出现的三类taskPool
typedef enum TaskType {
/** Tasks shared based on their priority by services */
SHARED_TASK = 0,
/** Task exclusively occupied by a service */
SINGLE_TASK = 1,
/** A specified task shared by multiple services */
SPECIFIED_TASK = 2,
/** No task for the service. Generally, this situation does not occur. */
NO_TASK = 0xFF,
} TaskType;
重要结构体TaskPool
在后面讲解AddTaskPool中会对两个结构体进行解读
对于初始化的解读就自然落在了下面三个重要函数身上
3.2 三个重要函数
3.2.1 AddTaskPool
/*
函数功能:三类TaskPool的创建
函数参数:service:taskPool对应的服务 cfg:taskPool的参数设置 name:服务的字符串名称
函数返回:void
*/
static void AddTaskPool(ServiceImpl *service, TaskConfig *cfg, const char *name)
{
if (service->taskPool != NULL) {
return;
}
if (cfg->priority < PRI_LOW || cfg->priority >= PRI_BUTT) {
HILOG_ERROR(HILOG_MODULE_SAMGR, "The %s service pri(%d) is out of range!", name, cfg->priority);
cfg->priority = PRI_LOW;
}
//三类taskPool的处理
switch (cfg->taskFlags) {
case SHARED_TASK: {
int pos = (int)cfg->priority / PROPERTY_STEP;//PROPERTY_STEP:8
SamgrLiteImpl *samgr = GetImplement();
if (samgr->sharedPool[pos] == NULL) {
//{LEVEL_HIGH, (int16) ((pos) * 8 + 1), 0x800, 25, SHARED_TASK}
TaskConfig shareCfg = DEFAULT_TASK_CFG(pos);
//为服务创建相应的队列和TaskPool
samgr->sharedPool[pos] = SAMGR_CreateFixedTaskPool(&shareCfg, name, DEFAULT_SIZE);
}
service->taskPool = samgr->sharedPool[pos];
//用于检测taskPool中的ref是否为空或者超过最大值
if (SAMGR_ReferenceTaskPool(service->taskPool) == NULL) {
HILOG_ERROR(HILOG_MODULE_SAMGR, "shared task:%p pri:%d ref is full", service->taskPool, cfg->priority);
samgr->sharedPool[pos] = NULL;
}
}
break;
case SPECIFIED_TASK:
//根据传入的config寻找config一致的referenceTaskPool
service->taskPool = GetSpecifiedTaskPool(cfg);
if (service->taskPool != NULL) {
break;
}
//每个service单独使用的taskpool
case SINGLE_TASK:
service->taskPool = SAMGR_CreateFixedTaskPool(cfg, name, SINGLE_SIZE);
break;
default:
break;
}
if (service->taskPool == NULL) {
HILOG_ERROR(HILOG_MODULE_SAMGR, "Service<name:%s, flag:%d> create taskPool failed!", name, cfg->taskFlags);
}
}
函数说明:
1.首先对于传入的config中的priority进行规范检测,不规范的进行日志输出并将其改为PRI_LOW(最低级的优先级)
2.switch-case语句,根据config中的taskFlags判断其类型并调用不同函数进行taskPool的创建
3.对于SHARED_TASK来说,它是由g_samgrImpl管理的shareTask,也就是说——对于用于该类型taskPool的服务来说,该服务的taskPool是在g_samgrImpl共享的。
在代码中我们也可以看到:当g_samgrImpl.sharedPool[pos]对应为NULL则调用DEFAULT_TASK_CFG创建一个高优先级的sharedTask类型的config,并调用SAMGR_CreateFixedTaskPool为服务创建相应的队列和taskPool。如果原本g_samgrImpl.sharedPool[pos]就存在了taskPool则直接service->taskPool = samgr->sharedPool[pos]建立共享池的关系。(另外这里的pos是通过cfg->priority / PROPERTY_STEP计算出来的,可取的值为1234,对应四种优先级的taskPool)
所以共享池的共享仍然是根据taskConfig中的不同优先级priority进行分配的,使用服务的共享池有利于提高OS的处理效率(函数SAMGR_CreateFixedTaskPool在下面会提到)
4.对于SPECIFIED_TASK来说,它是多个服务共享的一个任务池的类型。在代码中调用GetSpecifiedTaskPool,根据传入的config寻找referenceTaskPool,并将其赋给service->taskPool。对于如何找到相应的TaskPool则是调用函数GetSpecifiedTaskPool对所有的serviceImpl的taksPool的config和所给定的config进行memcmp和SAMGR_ReferenceTaskPool,最后让service.taskpool指向该config的taskPool
5.对于SINGLE_TASK,它是一个服务单独占用一个任务池。所以直接简单的调用SAMGR_CreateFixedTaskPool将新建的taskPool赋给service->taskPool即可
三类任务池的设置是为了对不同的服务进行更好的资源分配,设置优先级和level的目的也是如此
综上所述:AddTaskPool的实质是对于不同类型taskPool创建的不同处理
这里同样给出流程图供读者参考
3.2.2 InitializeSingleService
分两步走:
1. 如果传入的service没有对应的taskPool则调用DEFAULT_Initialize进行默认初始化处理
2. 否则调用SAMGR_SendSharedDirectRequest传入HandleInitRequest交给对应的线程进行初始化处理 (处理的时候同样也调用了DEFAULT_Initialize)
而DEFAULT_Initialize实质上是进行serviceApi的注册
总的来说serviceApi的注册完成了两件事:第一如果g_remoteRegister不存在,则初始化g_remoteRegister;第二在g_remoteRegister中创建一个服务endpoint并初始化相应的router和policy供终端间的通信使用
具体的终端endpoint代理proxy机制这里不再展开
3.2.3 SAMGR_StartTaskPool
在完成了taskPool和serviceApi的注册后,来到最后一步——taskPool的启动
贴上代码注释:
函数的逻辑也比较清晰
1.首先是attr的创建
2.然后是调用THREAD_Create进行线程的创建(重要的任务入口函数TaskEntry)
3.再是在tasks中记录线程Id——便于以后的调用
4.最后就是根据size的大小,为一个taskPool创建多个thread,直到top>=size为止
具体task的运行机制大家可以自行阅读
函数流程图:
最后总结一下InitializeAllServices实现的具体内容:
它主要实现三件事来初始化所有的服务:
第一,三类taskPool的添加和创建,包括队列的创建;
第二,针对service和对应的feature进行serviceApi的注册;
第三,为创建的taskPool创建多个线程启动任务池
4. InitCompleted——检测服务是否全部初始化完毕并推进系统状态
首先还是从这个函数的源代码讲起,贴上代码注释:
基本的函数流程:
1. 调用GetImplement得到g_samgrImpl
2. 调用GetUninitializedPos得到g_samgrImpl中没有初始化的服务在vector的位置pos
3. pos<size时说明存在未初始化的service,返回EC_SUCCESS
4. 若所有的服务都初始化完成,则查看g_samgrImpl的状态:如果是系统服务初始化完成则进入用户APP服务注册和初始化的状态,调用SendBootRequest进行boot请求
5. 如果是用户APP初始化完成,则进入系统DYNAMIC的状态,同样调用SendBootRequest进行boot请求
在函数SAMGR_Bootstrap中注意到最后的判断是:
int32 err = InitCompleted();
if (err != EC_SUCCESS) {
HILOG_INFO(HILOG_MODULE_SAMGR, "Goto next boot step return code:%d", err);
}
也就是说当其返回值等于EC_SUCCESS时,说明本阶段的初始化工作没有完成;不等于EC_SUCCESS,则进行HILOG_INFO告知系统进入下一个boot阶段——在InitCompleted中初始化完成后就可以调用后面的SendBootRequest来进行下一个阶段的boot工作这是相呼应的
而在源代码的解析中我们可以发现SendBootRequest中使用的handler函数竟然也是SAMGR_Bootstrap,构成了一个类似循环但不是循环的结构:
首先外部调用SYS_INIT进行系统服务的注册,然后利用SAMGR_Bootstrap进行服务的初始化,然后在InitCompleted中检测是否全部初始化完毕,完毕则调用SendBootRequest等待用户APP注册完毕后,再次使用(handler)SAMGR_Bootstrap进行用户APP的初始化,再次调用InitCompleted检查是否全部初始化,完毕再次调用SAMGR_Bootstrap直到系统状态变为DYNAMIC平稳运行态后停止
最后同样给出函数的流程框图,帮助读者更好的理解其中的逻辑:
5. 基于SA框架完整的进程通信机制
经过对全部代码的解读,结合五大模块的功能和原理,最后以图示和文字说明的方式再一次完整的阐明整个SA框架下的进程通信机制
整个流程大致如下:
1. 首先通过整个服务启动的三步走完成系统服务启动
2. 每个服务对应有默认的serviceApi或者有0个、1个或多个featureImpl对应的featureApi其内部机制就是对应的endpoint管理着proxy、router、policy
3. 通过proxy代理开启listen,然后创建一个boss线程进行receive并startLoop
4. 再调用Dispatch根据policy进行信息的规范然后对应router信息发送到对应进程的endpoint上
5. 接收endpoint通过receive接收后调用HandleIPC通过proxy,根据发送message中的operation调用不同的handle进行处理并将reply写入缓冲区发送回endpoint完成一次IPC
至此Samgr_Lite的全部代码解读完毕,当然几篇文章并不足以详尽的写完整个SA服务框架,更多的细节和功能待读者发掘!