接前文《Hi3861的SAMGR--系统服务框架子系统-1》
4 结构体的分解
4.1 先上samgr的展开图【附件有原图】
4.2 Samgr:SamgrLiteImpl g_samgrImpl
- typedef struct SamgrLiteImpl SamgrLiteImpl;
- struct SamgrLiteImpl {
- A: SamgrLite vtbl; //SAMGR_GetInstance() return this SamgrLite Instance
- B: MutexId mutex;
- C: BootStatus status; //see BootStatus
- D: Vector services; //a vector to record all services
- E: TaskPool *sharedPool[MAX_POOL_NUM];
- };
注册第一个服务的时候,首先要通过SAMGR_GetInstance()从全局变量g_samgrImpl获取一个samgr的实例对象,g_samgrImpl已经初始化过了的话,可以直接返回samgr对象的引用,g_samgrImpl没有初始化的话,就需要通过Init函数进行初始化相关的配置之后,才能返回samgr对象的引用。
以后的各种service/feature的操作,基本是都是需要通过g_samgrImpl进行管理的。下面把这个全局变量展开来看一下:
A: SamgrLite vtbl;
vtbl 就是SamgrLite 的实例,SAMGR_GetInstance() 时,返回的就是指向它的指针。SamgrLite结构体(或者直接说类)定义了四组十个函数指针,如下图,同一组用同一个颜色框起来:
在g_samgrImpl的初始化(init)时与对应的实现函数对应关联起来,之后就可以通过这四组函数来对service/ feature进行注册、注销、获取API等操作了。
而具体的service、feature在各自INIT时,会通过g_samgrImpl的samgr实例调用这里的Register接口,向g_samgrImpl注册自己和API,把自己纳入samgr的管理体系中,然后就是samgr为service、feature创建queue/taskpool/task等运行环境了。
B: MutexId mutex;
互斥锁。关键代码段的操作要加锁,保证多线程下对共享数据的操作的完整性。
在g_samgrImpl的初始化(init)时就会通过MUTEX_InitValue() 生成互斥锁,MUTEX_InitValue()声明在thread_adapter.h,它的实现则取决于平台使用的内核,M核平台用cmsis接口来实现,A核/Linux平台,则使用posix接口来实现。
类似的还有内存、队列、线程、timer的相关操作,代码见:
- Hi3861/foundation/distributedschedule/samgr_lite/samgr/adapter/
理论上,全局对象g_samgrImpl初始化(init)时就会生成一个mutex,且只要g_samgrImpl没有重新初始化,这个mutex就应该是不变的,Hi3861平台上我看到的也确实如此。
但是Hi3516平台上我看到了一个奇怪的地方,见前文《鸿蒙系统框架层的启动细节》的附件log,搜索关键字“SAMGR_GetInstance|StartServiceByName|Invoke: msg”可以看到:
有若干处mutex是为NULL的,需要重新初始化g_samgrImpl,这样岂不是会把之前注册的service Vector/TaskPool等清除掉吗?先存疑,待进一步仔细阅读Hi3516的代码后再确认。
C: BootStatus status;
系统的启动阶段状态标记。
Hi3861平台上电启动到 system_init.c 的
- void HOS_SystemInit(void)
- {
- ......
- SYS_INIT(service);
- SYS_INIT(feature);
- SAMGR_Bootstrap();
- }
这一步时,通过SYS_INIT() 启动的都是用SYS_SERVICE_INIT()和SYS_FEATURE_INIT() 标记的service和feature,而通过SYSEX_SERVICE_INIT/APP_SERVICE_INIT/ SYSEX_FEATURE_INIT/APP_FEATURE_INIT 标记的service和feature,则会在系统启动到 BOOT_APP 这一步时,由Bootstrap service来启动,见 bootstrap_service.c 的MessageHandle()函数内的 INIT_APP_CALL() 的调用:
系统启动状态标记到 BOOT_DYNAMIC 这一步时,意味着系统所有的应用服务也启动完毕了,进入了一个比较稳定的状态,至于BOOT_DYNAMIC状态还会做其他什么事情,我暂时还没有更多的了解。
为了理解例程代码如何工作,我在这里加了 BOOT_DEBUG和BOOT_DEBUG_WAIT两个状态,用来调用例程里的INIT_TEST_CALL(),跑相关的测试流程,通过相关的log来验证自己的分析,详见附件的log。
D: Vector services;
g_samgrImpl 初始化时:
- g_samgrImpl.services = VECTOR_Make((VECTOR_Key)GetServiceName, (VECTOR_Compare)strcmp);
创建了一个Vector:{0, 0, 0, NULL, key, compare},Vector的定义如下:
其中:
- max:表示下面的**data指针数组的大小,也就是data[max]所能存储的最大指针数目;
- top: 当前使用到了的指针数组的最高位置data[top],当top==max,同时free为0时,表示data[max]已经装满了,需要扩容,一次扩容增加4个element位置,变成data[max+4],详情见VECTOR_Add()函数的实现。
- free:当前0~top之间,释放了的位置的数量。当已注册在册的service unregister时,对应记录这个service的data[i]会被清空,free+1;下次有新的service注册时,会优先使用data[i]来记录新service的Impl对象指针,free-1。详情见VECTOR_Swap()函数的实现
- **data:指针数组data[max],每一个data[i]记录了一个注册进来的service对应的ServiceImpl 对象的指针,初始化为NULL,通过VECTOR_Add()函数内的操作扩容。
- key:是samgr_lite.c定义的GetServiceName(ServiceImpl*)函数指针,它可以通过参数来获取对应的service Name。
- compare:是strcmp函数指针,也就是string标准库提供的字符串比较函数。
Samgr通过这个Vector来管理所有注册进来的service(实际上管理的是serviceImpl对象,通过serviceImpl来关联具体的service和service对应的feature)。
每个service可以有0个/1个或多个feature,每个service(serviceImpl对象)也是通过自己的Vector来管理feature。
对Vector的操作,全部定义在:
Hi3861/foundation/distributedschedule/services/samgr_lite/samgr/source/common.c
里面了,需要仔细阅读分析,去理解相关操作。
下面是几处要点:
- VECTOR_Make() 会创建一个{0, 0, 0, NULL, key, compare} Vector,当需要往Vector中添加element时,会在VECTOR_Add()中扩容。
- VECTOR_Add(Vector *vector, void *element) 的时候,发现Vector的data[max] 空间用尽了,就会重新申请一块增大了4个element的内存空间Newdata[max+4],把旧的data[x]全部拷贝进去(还有4个新的空余的element位置),g_samgrImpl.services.data重新指向Newdata,再把旧的data[x]占用的空间释放掉,这样,新的element (ServiceImpl 对象的指针)就可以记录进来了。
- VECTOR_Swap()用来删除一个已经记录在案的element(也就是unregister一个service),把它对应的 data[x] 置为NULL,free+1,空出的位置会给未来新注册的service 优先使用。
- VECTOR_Find()/VECTOR_FindByKey() 可以查找element(serviceImpl),并返回它的index。
特别是VECTOR_FindByKey(Vector *vector, const void *key),第二个参数“void *key”实际上是一个service的名字字符串,如“Broadcast”。
FindByKey会循环获取data[0~top]的ServiceImpl 对象的指针,并将其作为key函数指针(GetServiceName())的参数来获取service的Name,将Name其与上面的第二个参数的service Name,用compare函数指针(strcmp)进行对比,匹配则返回对应的data[i]的 index i。
E: TaskPool *sharedPool[MAX_POOL_NUM]; // MAX_POOL_NUM 是8
Hi3861平台启动到 system_init.c 的最后一步时:
- void HOS_SystemInit(void)
- {
- ......
- SAMGR_Bootstrap();
- }
调用SAMGR_Bootstrap()去启动已经注册了的service,会为service创建queue和taskPool资源,每个service有一个GetTaskConfig()接口,可以返回这个service运行起来的task配置参数,如 bootstrap service的TaskConfig为:
请自行查看 TaskConfig的定义。
这里需要关注的是 SHARED_TASK 这个标记,在samgr的 AddTaskPool()这一步时:
bootstrap service的TaskConfig配置会被修改成默认的DEFAULT_TASK_CFG,意味着在bootstrap_service.c 中定义的TaskConfig 参数(stackSize和queueSize)其实并没有起作用,想要修改SHARED_TASK的stackSize和queueSize,要去修改DEFAULT_TASK_CFG的配置。
而上面case SHARED_TASK的操作以及g_samgrImpl.sharedPool[]的存在,可以为若干个共同标记了SHARED_TASK的service共享一个Queue和taskPool资源(这样可以节约好多资源),TaskEntry在Queue中收到msg时,可以通过Exchange 消息里面的Identity字段解析出Sid/Fid/Qid信息,以此来确认到底是哪个service/feature需要处理这个消息。
TaskConfig里的priority字段,则确定了service用的是哪个sharePool[x],优先级越高,x越大。
我在代码里全局搜索了一下“SHARED_TASK”关键字,得到如下结果:
主要是在samgr例程里,不同优先级别的service,共享着不同x的sharedPool[x]资源。
不是SHARED_TASK的service则有自己独立的Queue和taskPool,不会直接记录在g_samgrImpl.sharedPool[]里,而是记录在各自service的serviceImpl对象的taskPool* 里。
4.3 ServiceImpl 类
- struct ServiceImpl {
- A: Service* service;
- B: IUnknown* defaultApi;
- C: TaskPool* taskPool;
- D: Vector features;
- E: int16 serviceId;
- F: uint8 inited;
- G: Operations ops;
- };
g_samgrImpl 全局变量的services Vector里,只直接记录ServiceImpl对象的指针,并不直接记录和管理service、feature对象本身。service在向samgr注册自己时,samgr会首先生成一个ServiceImpl对象,将service对象的指针记录在ServiceImpl的Service* service里,而ServiceImpl对象本身的指针,则记录在g_samgrImpl.services.data[i] 中,这样就建立了g_samgrImpl 到具体service的联系,见本文上面的展开图。
A: Service* service;
指向当前ServiceImpl对象所对应的具体的service对象,这些具体的service对象都是Service类的子类对象。
B: IUnknown* defaultApi;
继承了IUnknown接口(INHERIT_IUNKNOWN)的service或者feature,都会有IUnknown的一组三个默认的接口,这组接口主要是记录service/feature对象的引用数量,还可以通过其中的QueryInterface接口实现父类(IUnknown)指针到具体的service/feature子类对象指针的类型转换,以获取子类提供的功能。
详情见下面对IUnknown的分析。
我在《鸿蒙的DFX子系统 》一文中,有对hiview service做过一个展开,可以去那里了解一下。
C: TaskPool* taskPool;
这就是上一小节中,samgr为service创建的taskPool的指针(同时创建的还有消息队列,queueId同时保存在taskPool里面)。
如果service task是SHARED_TASK类型的,那就会有多个service共享一个taskPool和queue,这里的taskPool指针就会指向同一块内存空间,同时,g_samgrImpl的对应优先级别的sharedPool[x]也会记录着这个taskPool指针。
如果service task不是SHARED_TASK类型的,那就只会在这里记录service的taskPool(包括queue),而不会在g_samgrImpl中做记录。
D: Vector features;
feature需要依赖于对应的service才能注册和运行,一个service可以有0个、1个或多个feature。service本身不记录它对应的feature的信息,而是由这个ServiceImpl.features来记录。ServiceImpl 的这个Vector features类似于g_samgrImpl用于记录serviceImpl的Vector services。
一个service没有feature时,features Vector就保持初始化的样子,对应的features.data 是NULL。
一个service有若干个feature时,就会在feature注册时,由samgr生成对应FeatureImpl类对象,将此对象与具体的feature关联起来,再将此对象的指针保存在Vector features.data[x]里,并将这个 x 作为对应feature的ID,另做保存。以后samgr就可以通过它自己的vector找到serviceImpl,再进一步通过这个vector找到对应的featureImpl,从而找到最终的feature。
E: int16 serviceId;
当前的ServiceImpl 对象指针保存在g_samgrImpl.services vector内的data[x]上的这个 x 序号,就作为当前service的ID,保存在这里。这个serviceId也可能同时保存在具体的service对象的Identity 结构体里。
F: uint8 inited;
标记当前ServiceImpl 对应的service的状态,service没有init起来是不能注册feature的,service在处理消息事件的时候,状态也要对应置为 BUSY,处理完消息又要将状态写回IDLE。具体可自行查阅代码。
G: Operations ops;
主要记录了service处理消息事件的时间戳、msg数量(编号?)、步骤和是否存在异常等信息,估计与跨设备的服务/消息处理的同步有关,对此暂未做深入理解,待作进一步的理解。
4.4 FeatureImpl类
- typedef struct FeatureImpl FeatureImpl;
- struct FeatureImpl {
- A: Feature *feature;
- B: IUnknown *iUnknown;
- };
FeatureImpl 类看起来相对简单,直接是一个Feature指针(A)指向对应的具体的feature对象。
有些feature除了继承自Feature类之外,还继承了某些interface,为feature提供额外的功能,这些interface 都是继承了最原始的IUnknown接口类(INHERIT_IUNKNOWN)。
这里的IUnknown *iUnknown与上面的ServiceImpl 的IUnknown* defaultApi; 其实是同一个东西,它们都指 向了一个feature或者service所继承/实现的接口中,IUnknown接口所在的位置,见上面ServiceImpl对IUnknown* defaultApi;的说明。
更多详情见下面对IUnknown的分析。
4.5 Service类及其子类
- struct Service {
- const char *(*GetName)(Service *service); //获取service名称
- BOOL (*Initialize)(Service *service, Identity identity); //service的初始化
- BOOL (*MessageHandle)(Service *service, Request *request); //service的消息处理函数
- TaskConfig (*GetTaskConfig)(Service *service); //获取service 任务运行的配置
- }
Service类是所有service类的父类,它声明了四个函数指针,这是每一个服务都必须要实现的生命周期函数,见上面的注释。
每一个具体的服务类都继承自这个Service类,然后可以扩展自己独特的功能。
下面分别看一下Hi3861默认的三个具体的服务。
A: Bootstrap service
- typedef struct Bootstrap {
- INHERIT_SERVICE; //继承上面的Sevice类
- Identity identity; //bootstrap service对象的id信息
- uint8 flag;
- } Bootstrap;
Bootstrap 除了继承Service之外,还增加了一个Identity identity 和 uint8 flag。
- Identity identity
这是Bootstrap service具体对象的身份信息,里面包括了: serviceId/featureId/queueId三个信息。
serviceId:Bootstrap service 对应的 serviceImpl对象,在g_samgrImpl.services这个Vector.data[]中存放位置 的index,这里值为0.
featureId:Bootstrap service不带feature,所以值为 -1。
queueId: Bootstrap service启动时,samgr会为其创建消息队列,这就是消息队列的ID,是一串数字。
- uint8 flag
一个标记,主要是LOAD_FLAG 0x01这一个位,用来标记非系统service/feature是否已经加载和注册,见 bootstrap_service.c的MessageHandle()内对flag的使用。
B. Broadcast service
- typedef struct BroadcastService BroadcastService;
- struct BroadcastService {
- INHERIT_SERVICE;
- };
Broadcast service仅仅直接继承了Service类,确保它的service对象的生命周期的完整,因为它还会有feature,会在具体的feature对象中保存id信息和其它扩展信息,详见下面的PubSubFeature类的解析。
C. Hiview service
- typedef struct {
- INHERIT_IUNKNOWN;
- void (*Output)(IUnknown *iUnknown, int16 msgId, uint16 type);
- } HiviewInterface;
- typedef struct {
- INHERIT_SERVICE;
- INHERIT_IUNKNOWNENTRY(HiviewInterface);
- Identity identity;
- } HiviewService;
Hiview service除了继承自Service类实现service的生命周期函数之外,还继承了HiviewInterface,这个HiviewInterface又继承了最原始的IUnknown接口类(INHERIT_IUNKNOWN)。通过这种多继承机制,既实现了服务所需的生命周期,又具备了类似feature所提供的部分接口功能(Hiview service实际上又不带feature)。
详情见下面对IUnknown类的分析。
identity则是Hiview service对象的身份信息,同样包括了: serviceId/featureId/queueId三个信息。
4.6 Feature类及其子类
- struct Feature {
- const char *(*GetName)(Feature *feature); //获取feature的名字
- void (*OnInitialize)(Feature *feature, Service *parent, Identity identity); //feature 的初始化
- void (*OnStop)(Feature *feature, Identity identity); //停止对外提供feature功能
- BOOL (*OnMessage)(Feature *feature, Request *request); //对本feature的消息处理
- };
Feature类是所有feature类的父类,也是声明了四个函数指针,这是每一个feature都必须要实现的生命周期函数,见上面的注释。
每一个具体的feature类都继承自这个Feature类,然后扩展自己独特的功能。
下面是Hi3861的Broadcast service 提供的feature类及Impl类的定义:
A: PubSubFeature g_broadcastFeature
- typedef struct PubSubFeature PubSubFeature;
- struct PubSubFeature {
- INHERIT_FEATURE; //继承 Feature 类
- Relation *(*GetRelation)(PubSubFeature *feature, const Topic *topic);
- MutexId mutex;
- Relation relations;
- Identity identity;
- };
PubSubFeature 本尊,它被FeatureImpl 对象以及下面的PubSubImplement 对象引用。
FeatureImpl 对象又会被记录在 ServiceImpl 的Features Vector向量里,获得一个Fid,连同Sid/Qid一起记录在PubSubFeature 的identity里。
PubSubFeature 还提供一个双向链表结构的Relation,以及基于这个双向链表结构的查找节点的函数GetRelation(),这个结构及其作用,我后面再另写文章详细分析。
B: PubSubImplement g_pubSubImplement
- typedef struct PubSubInterface PubSubInterface;
- struct PubSubInterface {
- INHERIT_IUNKNOWN;
- Subscriber subscriber;
- Provider provider;
- };
- typedef struct PubSubImplement {
- INHERIT_IUNKNOWNENTRY(PubSubInterface);
- PubSubFeature *feature;
- } PubSubImplement;
PubSubImplement 对象会引用上面的PubSubFeature对象,记录在这里的PubSubFeature *feature上。
PubSubImplement 还继承了PubSubInterface,实现了Subscriber 和Provider的功能。
它们的关系,见本文最上面的展开图。
这个PubSubFeature 和PubSubImplement 深究下去就有点复杂了,它们是SOA(面向服务的架构)的具体实现:
- Provider:服务的提供者,为系统提供能力(对外接口)。
- Consumer:服务的消费者,调用服务提供的功能(对外接口)。
- Samgr:作为中介者,管理Provider提供的能力,同时帮助Consumer发现Provider的能力。
如下是官方readme上画的架构图。
这里我就先不做进一步详细的分析了,后面会结合broadcast_example.c示例程序来做分析和验证,再单独写一篇文章来做总结。
4.7 IUnknown 接口类及其相关定义
首先需要理解,C语言的struct本质上与C++中的class是一样的,都是一块存储区域,里面有数据和对数据的操作。C++通过“:”关键字来标记继承关系,如Aa继承自A表示为“class Aa : public A”,而C语言直接是struct Aa内嵌套struct A来标记“继承关系”:
- struct A {
- dataA;
- funcPtr* funcA;
- }
- struct Aa {
- struct A;
- dataAa;
- funcPtr* funcAa;
- }
更具体的一些细节,可以自行在网上搜索和学习。
接下来,我们仔细对比一下HivieService 和PubSubImplement,它们都分别通过INHERIT_IUNKNOWNENTRY() 这个关键字来分别继承HiviewInterface和PubSubInterface,而这HiviewInterface和PubSubInterface又都通过INHERIT_IUNKNOWN来继承IUnknown接口类。
下面我们就跟着Hi3861/foundation/distributedschedule/interfaces/innerkits/samgr_lite/samgr/iunknown.h中的定义去展开和理解一下这两个宏(类)。
INHERIT_IUNKNOWN 宏定义是为了方便用C语言的形式“继承”IUnknown接口类。
INHERIT_IUNKNOWNENTRY() 宏定义是为了方便用C语言的形式“继承”【实现了IUnknown接口的】 IUnknownEntry接口类。
这两个宏之间的关系,就是上面struct A和struct Aa之间的父类子类关系,换回struct 的形式,就是:
IUnknown 是父类,IUnknownEntry 是子类,子类是父类的一个implement。
按上面的定义把HiviewService类(或结构体)的定义彻底展开,就是如下的样子:
把HiviewService的全局对象 g_hiviewService的初始化也按上面的形式展开,也会如下:
这样一来,.iUnknown的地址、HiviewService类型、g_hiviewService对象的地址(引用/指针)之间的关系,就可以通过计算和类型转换来互相获取了,这就是下面三个宏的作用:
a. GET_IUNKNOWN(T)
定义在://foundation/distributedschedule/interfaces/innerkits/samgr_lite/samgr/iunknown.h
简单理解为:从g_hiviewService对象中获取其内部的.iUnknown对象的地址。
b. GET_OFFSIZE(T, member)
c. GET_OBJECT(Ptr, T, member)
这两个定义在://foundation/distributedschedule/interfaces/innerkits/samgr_lite/samgr/common.h
简单理解为:从.iUnknown父类对象下转换得到子类HiviewInterface对象或者g_hiviewService对象的地址。
如本文开头的第一张展开图所示,BroadcastImpl和HiviewImpl都有自己的 IUnknown* defaultApi,分别是通过GET_IUNKNOWN(g_pubSubImplement) 和GET_IUNKNOWN(g_hiviewService)来得到的,得到了.iUnknown的地址,也就是得到了(*QueryInterface)/(*AddRef)/(*Release)这三个defaultApi的地址,就可以使用它们了,实际只直接使用(*QueryInterface)这个API,它的作用是“Queries the subclass object of the IUnknown interface of a specified version”
查询/获取指定的版本的IUnknown接口的子类对象(同时增加对该对象的引用次数),调用者通过这个子类对象就可以调用子类中定义的其它接口了。
对于上面的g_hiviewService例子来说,调用QueryInterface接口的例子在hiview_service.c 的HiviewSendMessage() 里,它返回的实际上就是g_hiviewService 这个service对象内部的一个区域的起始地址,这个区域就是HiviewInterface这个类的对象,同时增加了对这个对象的引用次数,之后,就可以通过这个对象来调用HiviewInterface所定义的全部API了(这里主要是Output函数)。
4.8 其它类/结构体
samgr子系统中还有其他的一些很重要的类或结构体,比如Request/Response/Exchange 等等,这里就先不进一步展开了,以后应该还会继续补充完整的,或者在接下来的流程分析中按需进行分解,也请各位自行做一下理解。