浅谈Openharmony系统服务框架Samgr
前言
在刚接触 Samgr 时笔者根本不知道其设计初衷是啥(水平较菜,刚接触C语言),尽管官方文档对其进行了介绍,但仍有种听君一席话胜读一席话的感觉。为此,笔者花了一天对其进行探究和学习,将笔记梳理并记录于本文。
参考博文
《鸿蒙子系统解读-分布式任务调度篇(上)》
《鸿蒙OS开源代码精要解读之—— 系统服务框架子系统(服务启动)》
《深入浅出OpenHarmony架构》
Samgr概念
官方说明
官方仓 https://gitee.com/openharmony/distributedschedule_samgr_lite 对其解释如下:
由于平台资源有限,且硬件平台多样,因此需要屏蔽不同硬件架构和平台资源的不同、以及运行形态的不同,提供统一化的系统服务开发框架。根据RISC-V、Cortex-M、Cortex-A不同硬件平台,分为两种硬件平台,以下简称M核、A核。
-
M核:处理器架构为Cortex-M或同等处理能力的硬件平台,系统内存一般低于512KB,无文件系统或者仅提供一个可有限使用的轻量级文件系统,遵循CMSIS接口规范。
-
A核:处理器架构为Cortex-A或同等处理能力的硬件平台,内存资源大于512KB,文件系统完善,可存储大量数据,遵循POSIX接口规范。
系统服务框架基于面向服务的架构,提供了服务开发、服务的子功能开发、对外接口的开发、以及多服务共进程、进程间服务调用等开发能力。其中:
- M核:包含服务开发、服务的子功能开发、对外接口的开发以及多服务共进程的开发框架。
- A核:在M核能力基础之上,包含了进程间服务调用、进程间服务调用权限控制、进程间服务接口的开发等能力。
面向服务的架构:
- Provider:服务的提供者,为系统提供能力(对外接口)。
- Consumer:服务的消费者,调用服务提供的功能(对外接口)。
- Samgr:作为中介者,管理Provider提供的能力,同时帮助Consumer发现Provider的能力。
系统服务开发框架主体对象:
个人理解
参考了一些资料后,发现一切皆服务的思想,与 Spring 中 Bean 类似,通过一种约定的封装方式调用相关类或者方法。增强了项目的扩展性,同时屏蔽底层实现,只关心接口。例如,可以通过获取serviceName 获取一个服务 service,并调用其接口相关方法。该 service 可以是从本地调用,也可以是从远程调用。
- 如果进行本地调用,只需要在使用前,将 service 存入一个本地缓存中。调用时从缓存中找出该 service 即可,此为直接调用。本地调用也能通过消息机制实现异步调用
- 如果进行远程调用,则需通过 RPC 实现,在 OHOS 中应该是 LiteIPC
当意识到上述问题时,笔者对整个 samgr 的梳理就很顺畅了
实例
参考资料的过程中,相关例子太少,都是开门见山地深入源码,对于笔者这种基础比较差的,着实看得痛苦。因此,在介绍具体细节前,找个本地直接调用的例子说明一下。(简化了代码,代码摘自《深入浅出OpenHarmony架构》)
服务注册
服务的注册分为以下几个步骤
1 . 实现 Service 接口
2 . 创建静态对象
3 . 注册服务和缺省特性
static void Init(void){SAMGR_GetInstance()->RegisterService((Service *)&g_example);SAMGR_GetInstance()->RegisterDefaultFeatureApi(EXAMPLE_SERVICE,GET_IUNKNOWN(g_example));}SYSEX_SERVICE_INT(Init);
4 . 实现 Feature 接口
5 . 注册 Feature
static void Init(void){SAMGR_GetInstance()->RegisterFeature(EXAMPLE_SERVICE,(Feature*)&g_example);SAMGR_GetInstance()->RegisterFeatureApi(EXAMPLE_SERVICE,EXAMPLE_FEATURE,GET_IUNKNOWN(g_example));}SYSEX_FEATURE_INIT(Init);
服务发现
服务/特性完成注册后,使用者可以通过SAMGR接口获取具体接口并进行调用
DemoApi *demoApi = NULL;IUnknown *iUnknown = SAMGR_GetInstance()->GetFeatureApi(EXAMPLE_SERVICE,EXAMPLE_FEATURE);if(iUnknown == NULL){return NULL;}int result = iUnknown->QueryInterface(iUnknown,DEFAULT_VERSION,(void **)&demoApi);if(result !=0 || demoApi == NULL){return NULL;}
若能发现对应接口,则赋值给demoApi,故可进行对应接口 xxFunction 的调用
if(demoApi->xxFunction == NULL){return NULL;}demoApi->xxFunction()
小结
从简化的实例不难看出,想要使用OHOS,需要定义服务 service 和特性 feature ,feature 相当于主执行方法,一个 service 能够对应多个 feature ,一个 feature 也能对应多个 service 。更细节地使用指导不是本篇目的,对此不再将展开。可以见得,不就和 Spring 的 Bean 类似吗,通过 @Conponent 进行定义,再使用 @Autowired 进行自动装载。
简单分析
服务注册与启动
safwk_lite 目录中是 foundation 这个 bin 文件的 main 函数,用于 samgr 启动,初始化所有注册的Service。即前一篇介绍 OHOS 启动流程的文章中,有提及 foundation 进程的拉起,该进程是用于提供 samgr 服务的。
可以看到 main 入口主要调用了 OHOS_SystemInit() ,其实际调用了 SAMGR_Bootstrap()
\distributedschedule\safwk_lite\src\main.c
void __attribute__((weak)) OHOS_SystemInit(void){ SAMGR_Bootstrap();#ifdef DEBUG_SERVICES_SAFWK_LITE printf("[Foundation][D] Default OHOS_SystemInit is called! \n");#endif}int main(int argc, char * const argv[]){#ifdef DEBUG_SERVICES_SAFWK_LITE printf("[Foundation][D] Start server system, begin. \n"); struct timeval tvBefore; (void)gettimeofday(&tvBefore, NULL);#endif OHOS_SystemInit();#ifdef DEBUG_SERVICES_SAFWK_LITE struct timeval tvAfter; (void)gettimeofday(&tvAfter, NULL); printf("[Foundation][D] Start server system, end. duration %d seconds and %d microseconds. \n",\ tvAfter.tv_sec - tvBefore.tv_sec, tvAfter.tv_usec - tvBefore.tv_usec);#endif 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(); }}
具体地,SAMGR_Bootstrap() 调用 InitializeAllServices() 初始化服务。
void SAMGR_Bootstrap(void){ SamgrLiteImpl *samgr = GetImplement(); if (samgr->mutex == NULL) { HILOG_INFO(HILOG_MODULE_SAMGR, "Samgr is not init, no service!"); return; } // --------------省略部分代码-------------- InitializeAllServices(&initServices); VECTOR_Clear(&initServices); int32 err = InitCompleted(); if (err != EC_SUCCESS) { HILOG_INFO(HILOG_MODULE_SAMGR, "Goto next boot step return code:%d", err); }}
事实上,如前所述,本地调用的方式只需要在某个Service缓存中找到出需要调用的 Service。为什么还要大费周章进行 Service 初始化呢。事实上,如上述架构图所示,进程内服务之间除了可以直接调用(Direct use service),还可以通过消息进行异步调用。个人认为,Service 的初始化更多地是准备异步调用环境,因为异步调用需要利用消息队列+线程池的方式进行高效执行。
简单地说,如何实现一个异步调用。我们需要 Consumer 把需调用的函数封装成请求并发送给 Provider,由于请求会有很多,因此需要使用消息队列进行存储和管控。进一步地,需要利用线程池,不断地执行请求并返回调用结果。
对此,可以看到 InitializeAllServices 通过 AddTaskPool 为当前 service 创建了线程池 TaskPool 。最后通过 SAMGR_StartTaskPool 利用线程池中的线程执行轮询消息队列的任务
static void InitializeAllServices(Vector *services){ int16 size = VECTOR_Size(services); int16 i; for (i = 0; i < size; ++i) { ServiceImpl *serviceImpl = (ServiceImpl *)VECTOR_At(services, i); if (serviceImpl == NULL) { continue; } TaskConfig config = serviceImpl->service->GetTaskConfig(serviceImpl->service); const char *name = serviceImpl->service->GetName(serviceImpl->service); AddTaskPool(serviceImpl, &config, name); HILOG_INFO(HILOG_MODULE_SAMGR, "Init service:%s", name); InitializeSingleService(serviceImpl); } SamgrLiteImpl *samgr = GetImplement(); MUTEX_Lock(samgr->mutex); for (i = 0; i < size; ++i) { ServiceImpl *serviceImpl = (ServiceImpl *)VECTOR_At(services, i); if (serviceImpl == NULL) { continue; } const char *name = serviceImpl->service->GetName(serviceImpl->service); SAMGR_StartTaskPool(serviceImpl->taskPool, name); } MUTEX_Unlock(samgr->mutex);}
具体地,SAMGR_StartTaskPool 通过封装好的线程创建方法 THREAD_Create,将轮询消息队列这一任务 TaskEntry 让线程执行
int32 SAMGR_StartTaskPool(TaskPool *pool, const char *name){ if (pool == NULL) { return EC_INVALID; } if (pool->top > 0) { return EC_SUCCESS; } ThreadAttr attr = {name, pool->stackSize, pool->priority, 0, 0}; while (pool->top < pool->size) { register ThreadId threadId = (ThreadId)THREAD_Create(TaskEntry, pool->queueId, &attr); if (threadId == NULL) { HILOG_ERROR(HILOG_MODULE_SAMGR, "Start Task failed!", name, pool->stackSize, pool->priority); break; } pool->tasks[pool->top] = threadId; ++(pool->top); } return EC_SUCCESS;}
跟踪 TaskEntry 可以看到,确实在轮询消息队列,并对到来的消息进行处理。通过 SAMGR_MsgRecv 获取到 Exchange (ps : 这个在dubbo架构中也有相关分层设计,作用类似)。解析 Exchange 能够获得消息中想要请求的服务。进一步地
- ProcResponse 回调exchange的handler
- ProRequest 真正处理请求的函数
static void *TaskEntry(void *argv){ ServiceImpl *serviceImpl = NULL; THREAD_SetThreadLocal(argv); while (TRUE) { Exchange exchange; uint32 msgRcvRet = SAMGR_MsgRecv((MQueueId)argv, (uint8 *)&exchange, sizeof(Exchange)); if (msgRcvRet != EC_SUCCESS) { continue; } if (exchange.type == MSG_EXIT) { SAMGR_FreeMsg(&exchange); break; } serviceImpl = CorrectServiceImpl(&exchange, serviceImpl); BeginWork(serviceImpl); ProcResponse(&exchange); ProcDirectRequest(&exchange); ProcRequest(&exchange, serviceImpl); EndWork(serviceImpl, &exchange); SAMGR_FreeMsg(&exchange); } QUEUE_Destroy((MQueueId)argv); return NULL;}
ProRequest 实际上调用 DEFAULT_MessageHandle 进行请求的处理。可以看到,其主要会调用service.MessageHanle 和 service.feature.OnMessage 方法。这么分析过后,我们可以知道在定义服务的时候,每个成员函数应该实现怎么样的功能。而不是一上来就告诉你这个函数用来干啥干啥
void DEFAULT_MessageHandle(ServiceImpl *serviceImpl, const Identity *identity, Request *msg){ if (serviceImpl->serviceId != identity->serviceId) { return; } if (identity->featureId < 0) { if (serviceImpl->service->MessageHandle != NULL) { serviceImpl->service->MessageHandle(serviceImpl->service, msg); } return; } if (VECTOR_Size(&serviceImpl->features) <= identity->featureId) { return; } FeatureImpl *featureImpl = (FeatureImpl *)VECTOR_At(&(serviceImpl->features), identity->featureId); if (featureImpl == NULL) { return; } featureImpl->feature->OnMessage(featureImpl->feature, msg);}
服务发现
GetFeatureApi 为服务发现接口,用于通过service 及 feature 名找到对应实现。可以找到 samgr 服务对应的 GetFeatureApi 实现如下
static IUnknown *GetFeatureApi(const char *serviceName, const char *feature){ ServiceImpl *serviceImpl = GetService(serviceName); if (serviceImpl == NULL) { return SAMGR_FindServiceApi(serviceName, feature); } FeatureImpl *featureImpl = DEFAULT_GetFeature(serviceImpl, feature); if (featureImpl == NULL && feature == NULL) { return serviceImpl->defaultApi; } return SAMGR_GetInterface(featureImpl);}
具体地,DEFAULT_GetFeature 在缓存中寻找对应 feature。从此处看,似乎GetFeatureApi只能用于调用本地服务的 feature
FeatureImpl *DEFAULT_GetFeature(ServiceImpl *serviceImpl, const char *featureName){ if (serviceImpl == NULL || featureName == NULL) { return NULL; } short pos = VECTOR_FindByKey(&(serviceImpl->features), (void *)featureName); return (FeatureImpl *)VECTOR_At(&(serviceImpl->features), pos);}
远程调用
上述简单分析了samgr框架中,本地调用服务的过程。关于远程调用,如《深入浅出OpenHarmony》书中所言,主要涉及 IServerProxy,IClientProxy 及 LiteIPC 相关内容,本质应该就是 rpc 那一套。由于笔者对 rpc 实现比较熟悉,就暂不对其进行深入探究。挖个坑,以后有机会再来填