前言

在刚接触 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<%s, %d, %d> 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 实现比较熟悉,就暂不对其进行深入探究。挖个坑,以后有机会再来填

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐