Hi3861的SAMGR--系統(tǒng)服務框架子系統(tǒng)-2
接前文《Hi3861的SAMGR--系統(tǒng)服務框架子系統(tǒng)-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函數(shù)進行初始化相關的配置之后,才能返回samgr對象的引用。
以后的各種service/feature的操作,基本是都是需要通過g_samgrImpl進行管理的。下面把這個全局變量展開來看一下:
A: SamgrLite vtbl;
vtbl 就是SamgrLite 的實例,SAMGR_GetInstance() 時,返回的就是指向它的指針。SamgrLite結構體(或者直接說類)定義了四組十個函數(shù)指針,如下圖,同一組用同一個顏色框起來:
在g_samgrImpl的初始化(init)時與對應的實現(xiàn)函數(shù)對應關聯(lián)起來,之后就可以通過這四組函數(shù)來對service/ feature進行注冊、注銷、獲取API等操作了。
而具體的service、feature在各自INIT時,會通過g_samgrImpl的samgr實例調用這里的Register接口,向g_samgrImpl注冊自己和API,把自己納入samgr的管理體系中,然后就是samgr為service、feature創(chuàng)建queue/taskpool/task等運行環(huán)境了。
B: MutexId mutex;
互斥鎖。關鍵代碼段的操作要加鎖,保證多線程下對共享數(shù)據(jù)的操作的完整性。
在g_samgrImpl的初始化(init)時就會通過MUTEX_InitValue() 生成互斥鎖,MUTEX_InitValue()聲明在thread_adapter.h,它的實現(xiàn)則取決于平臺使用的內核,M核平臺用cmsis接口來實現(xiàn),A核/Linux平臺,則使用posix接口來實現(xiàn)。
類似的還有內存、隊列、線程、timer的相關操作,代碼見:
- Hi3861/foundation/distributedschedule/samgr_lite/samgr/adapter/
理論上,全局對象g_samgrImpl初始化(init)時就會生成一個mutex,且只要g_samgrImpl沒有重新初始化,這個mutex就應該是不變的,Hi3861平臺上我看到的也確實如此。
但是Hi3516平臺上我看到了一個奇怪的地方,見前文《鴻蒙系統(tǒng)框架層的啟動細節(jié)》的附件log,搜索關鍵字“SAMGR_GetInstance|StartServiceByName|Invoke: msg”可以看到:
有若干處mutex是為NULL的,需要重新初始化g_samgrImpl,這樣豈不是會把之前注冊的service Vector/TaskPool等清除掉嗎?先存疑,待進一步仔細閱讀Hi3516的代碼后再確認。
C: BootStatus status;
系統(tǒng)的啟動階段狀態(tài)標記。
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,則會在系統(tǒng)啟動到 BOOT_APP 這一步時,由Bootstrap service來啟動,見 bootstrap_service.c 的MessageHandle()函數(shù)內的 INIT_APP_CALL() 的調用:
系統(tǒng)啟動狀態(tài)標記到 BOOT_DYNAMIC 這一步時,意味著系統(tǒng)所有的應用服務也啟動完畢了,進入了一個比較穩(wěn)定的狀態(tài),至于BOOT_DYNAMIC狀態(tài)還會做其他什么事情,我暫時還沒有更多的了解。
為了理解例程代碼如何工作,我在這里加了 BOOT_DEBUG和BOOT_DEBUG_WAIT兩個狀態(tài),用來調用例程里的INIT_TEST_CALL(),跑相關的測試流程,通過相關的log來驗證自己的分析,詳見附件的log。
D: Vector services;
g_samgrImpl 初始化時:
- g_samgrImpl.services = VECTOR_Make((VECTOR_Key)GetServiceName, (VECTOR_Compare)strcmp);
創(chuàng)建了一個Vector:{0, 0, 0, NULL, key, compare},Vector的定義如下:
其中:
- max:表示下面的**data指針數(shù)組的大小,也就是data[max]所能存儲的最大指針數(shù)目;
- top: 當前使用到了的指針數(shù)組的最高位置data[top],當top==max,同時free為0時,表示data[max]已經裝滿了,需要擴容,一次擴容增加4個element位置,變成data[max+4],詳情見VECTOR_Add()函數(shù)的實現(xiàn)。
- free:當前0~top之間,釋放了的位置的數(shù)量。當已注冊在冊的service unregister時,對應記錄這個service的data[i]會被清空,free+1;下次有新的service注冊時,會優(yōu)先使用data[i]來記錄新service的Impl對象指針,free-1。詳情見VECTOR_Swap()函數(shù)的實現(xiàn)
- **data:指針數(shù)組data[max],每一個data[i]記錄了一個注冊進來的service對應的ServiceImpl 對象的指針,初始化為NULL,通過VECTOR_Add()函數(shù)內的操作擴容。
- key:是samgr_lite.c定義的GetServiceName(ServiceImpl*)函數(shù)指針,它可以通過參數(shù)來獲取對應的service Name。
- compare:是strcmp函數(shù)指針,也就是string標準庫提供的字符串比較函數(shù)。
Samgr通過這個Vector來管理所有注冊進來的service(實際上管理的是serviceImpl對象,通過serviceImpl來關聯(lián)具體的service和service對應的feature)。
每個service可以有0個/1個或多個feature,每個service(serviceImpl對象)也是通過自己的Vector來管理feature。
對Vector的操作,全部定義在:
Hi3861/foundation/distributedschedule/services/samgr_lite/samgr/source/common.c
里面了,需要仔細閱讀分析,去理解相關操作。
下面是幾處要點:
- VECTOR_Make() 會創(chuàng)建一個{0, 0, 0, NULL, key, compare} Vector,當需要往Vector中添加element時,會在VECTOR_Add()中擴容。
- VECTOR_Add(Vector *vector, void *element) 的時候,發(fā)現(xiàn)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 優(yōu)先使用。
- VECTOR_Find()/VECTOR_FindByKey() 可以查找element(serviceImpl),并返回它的index。
特別是VECTOR_FindByKey(Vector *vector, const void *key),第二個參數(shù)“void *key”實際上是一個service的名字字符串,如“Broadcast”。
FindByKey會循環(huán)獲取data[0~top]的ServiceImpl 對象的指針,并將其作為key函數(shù)指針(GetServiceName())的參數(shù)來獲取service的Name,將Name其與上面的第二個參數(shù)的service Name,用compare函數(shù)指針(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創(chuàng)建queue和taskPool資源,每個service有一個GetTaskConfig()接口,可以返回這個service運行起來的task配置參數(shù),如 bootstrap service的TaskConfig為:
請自行查看 TaskConfig的定義。
這里需要關注的是 SHARED_TASK 這個標記,在samgr的 AddTaskPool()這一步時:
bootstrap service的TaskConfig配置會被修改成默認的DEFAULT_TASK_CFG,意味著在bootstrap_service.c 中定義的TaskConfig 參數(shù)(stackSize和queueSize)其實并沒有起作用,想要修改SHARED_TASK的stackSize和queueSize,要去修改DEFAULT_TASK_CFG的配置。
而上面case SHARED_TASK的操作以及g_samgrImpl.sharedPool[]的存在,可以為若干個共同標記了SHARED_TASK的service共享一個Queue和taskPool資源(這樣可以節(jié)約好多資源),TaskEntry在Queue中收到msg時,可以通過Exchange 消息里面的Identity字段解析出Sid/Fid/Qid信息,以此來確認到底是哪個service/feature需要處理這個消息。
TaskConfig里的priority字段,則確定了service用的是哪個sharePool[x],優(yōu)先級越高,x越大。
我在代碼里全局搜索了一下“SHARED_TASK”關鍵字,得到如下結果:
主要是在samgr例程里,不同優(yōu)先級別的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的聯(lián)系,見本文上面的展開圖。
A: Service* service;
指向當前ServiceImpl對象所對應的具體的service對象,這些具體的service對象都是Service類的子類對象。
B: IUnknown* defaultApi;
繼承了IUnknown接口(INHERIT_IUNKNOWN)的service或者feature,都會有IUnknown的一組三個默認的接口,這組接口主要是記錄service/feature對象的引用數(shù)量,還可以通過其中的QueryInterface接口實現(xiàn)父類(IUnknown)指針到具體的service/feature子類對象指針的類型轉換,以獲取子類提供的功能。
詳情見下面對IUnknown的分析。
我在《鴻蒙的DFX子系統(tǒng) 》一文中,有對hiview service做過一個展開,可以去那里了解一下。
C: TaskPool* taskPool;
這就是上一小節(jié)中,samgr為service創(chuàng)建的taskPool的指針(同時創(chuàng)建的還有消息隊列,queueId同時保存在taskPool里面)。
如果service task是SHARED_TASK類型的,那就會有多個service共享一個taskPool和queue,這里的taskPool指針就會指向同一塊內存空間,同時,g_samgrImpl的對應優(yōu)先級別的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關聯(lián)起來,再將此對象的指針保存在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的狀態(tài),service沒有init起來是不能注冊feature的,service在處理消息事件的時候,狀態(tài)也要對應置為 BUSY,處理完消息又要將狀態(tài)寫回IDLE。具體可自行查閱代碼。
G: Operations ops;
主要記錄了service處理消息事件的時間戳、msg數(shù)量(編號?)、步驟和是否存在異常等信息,估計與跨設備的服務/消息處理的同步有關,對此暫未做深入理解,待作進一步的理解。
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所繼承/實現(xiàn)的接口中,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的消息處理函數(shù)
- TaskConfig (*GetTaskConfig)(Service *service); //獲取service 任務運行的配置
- }
Service類是所有service類的父類,它聲明了四個函數(shù)指針,這是每一個服務都必須要實現(xiàn)的生命周期函數(shù),見上面的注釋。
每一個具體的服務類都繼承自這個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會為其創(chuàng)建消息隊列,這就是消息隊列的ID,是一串數(shù)字。
- uint8 flag
一個標記,主要是LOAD_FLAG 0x01這一個位,用來標記非系統(tǒng)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類實現(xiàn)service的生命周期函數(shù)之外,還繼承了HiviewInterface,這個HiviewInterface又繼承了最原始的IUnknown接口類(INHERIT_IUNKNOWN)。通過這種多繼承機制,既實現(xiàn)了服務所需的生命周期,又具備了類似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類的父類,也是聲明了四個函數(shù)指針,這是每一個feature都必須要實現(xiàn)的生命周期函數(shù),見上面的注釋。
每一個具體的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,以及基于這個雙向鏈表結構的查找節(jié)點的函數(shù)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,實現(xiàn)了Subscriber 和Provider的功能。
它們的關系,見本文最上面的展開圖。
這個PubSubFeature 和PubSubImplement 深究下去就有點復雜了,它們是SOA(面向服務的架構)的具體實現(xiàn):
- Provider:服務的提供者,為系統(tǒng)提供能力(對外接口)。
- Consumer:服務的消費者,調用服務提供的功能(對外接口)。
- Samgr:作為中介者,管理Provider提供的能力,同時幫助Consumer發(fā)現(xiàn)Provider的能力。
如下是官方readme上畫的架構圖。
這里我就先不做進一步詳細的分析了,后面會結合broadcast_example.c示例程序來做分析和驗證,再單獨寫一篇文章來做總結。
4.7 IUnknown 接口類及其相關定義
首先需要理解,C語言的struct本質上與C++中的class是一樣的,都是一塊存儲區(qū)域,里面有數(shù)據(jù)和對數(shù)據(jù)的操作。C++通過“:”關鍵字來標記繼承關系,如Aa繼承自A表示為“class Aa : public A”,而C語言直接是struct Aa內嵌套struct A來標記“繼承關系”:
- struct A {
- dataA;
- funcPtr* funcA;
- }
- struct Aa {
- struct A;
- dataAa;
- funcPtr* funcAa;
- }
更具體的一些細節(jié),可以自行在網上搜索和學習。
接下來,我們仔細對比一下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語言的形式“繼承”【實現(xiàn)了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接口的子類對象(同時增加對該對象的引用次數(shù)),調用者通過這個子類對象就可以調用子類中定義的其它接口了。
對于上面的g_hiviewService例子來說,調用QueryInterface接口的例子在hiview_service.c 的HiviewSendMessage() 里,它返回的實際上就是g_hiviewService 這個service對象內部的一個區(qū)域的起始地址,這個區(qū)域就是HiviewInterface這個類的對象,同時增加了對這個對象的引用次數(shù),之后,就可以通過這個對象來調用HiviewInterface所定義的全部API了(這里主要是Output函數(shù))。
4.8 其它類/結構體
samgr子系統(tǒng)中還有其他的一些很重要的類或結構體,比如Request/Response/Exchange 等等,這里就先不進一步展開了,以后應該還會繼續(xù)補充完整的,或者在接下來的流程分析中按需進行分解,也請各位自行做一下理解。