Hi3516的SAMGR--系統(tǒng)服務(wù)框架子系統(tǒng)-client EP的注冊
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
我們接著前文《Hi3516的SAMGR--系統(tǒng)服務(wù)框架子系統(tǒng)-6-系統(tǒng)服務(wù)的啟動》,繼續(xù)分析wms_server進程后繼的啟動步驟和注冊EP的流程,從DEFAULT_Initialize(ServiceImpl *impl) 函數(shù)入口開始。
從前文可以知道,wms_server進程啟動了三個service:Broadcast、WMS和IMS,Broadcast服務(wù)有一個feature:“Provider and subscriber”,另外兩個服務(wù),沒有feature。每個service都有自己的消息隊列,都會收到InitRequest消息,通過HandleInitRequest()函數(shù),service會帶著自己的feature跑一遍DEFAULT_Initialize(serviceImpl)函數(shù)。
- //foundation/distributedschedule/samgr_lite/samgr/source/service.c
DEFAULT_Initialize()內(nèi),很明顯可以分為以下四步的:
針對service跑前兩步
- [4-1] impl->service->Initialize(impl->service, id)
這一步會調(diào)用service自己生命周期的 Initialize() 函數(shù)來做初始化。
- [4-2] SAMGR_RegisterServiceApi(serviceName, NULL, &id, impl->defaultApi)
這一步輕量系統(tǒng)執(zhí)行空函數(shù),沒有實際動作;小型系統(tǒng)執(zhí)行remote_register.c 中定義的函數(shù),注意參數(shù)。
針對service所有的feature,for循環(huán)讓每個feature都執(zhí)行一遍[4-3]和[4-4]兩步,service沒有feature,就不用執(zhí)行這兩步:
- [4-3] feature->feature->OnInitialize(feature->feature, impl->service, id)
這一步會調(diào)用feature自己生命周期的 OnInitialize() 函數(shù)來做初始化。
- [4-4] SAMGR_RegisterServiceApi(serviceName, featureName, &id, feature->iUnknown)
這一步調(diào)用的API與第2步調(diào)用的是同一個API,但是注意參數(shù)的變化,特別是第四個參數(shù)。
- //foundation/distributedschedule/samgr_lite/samgr_client/source/remote_register.c
我們詳細看一下SAMGR_RegisterServiceApi()的工作,它內(nèi)部,也很明顯可以分為以下三步的:
- [3-1] InitializeRegistry()
每一個進程,都有一個全局的RemoteRegister g_remoteRegister,本進程的所有services中,第一個跑到這里的service會去初始化這個 g_remoteRegister,主要是創(chuàng)建互斥信號量、創(chuàng)建一個空的Vector clients、創(chuàng)建本進程的通信終端endpoint。以后的service/feature再跑進這一步時,基本上都會因為已經(jīng)存在endpoint了而就此退出(特定條件下會清空本 g_remoteRegister,重新生成,這里先不管)。
我們先把關(guān)注的重點放在創(chuàng)建 EP上。
- //foundation/distributedschedule/samgr_lite/samgr_endpoint/source/endpoint.c
所有進程都會通過SAMGR_CreateEndpoint("ipc client", NULL) 創(chuàng)建一個名為"ipc client"的EP,只有管理者會創(chuàng)建名為"samgr"的EP,這個后面講。
SAMGR_CreateEndpoint()內(nèi)會初始化如下一些參數(shù)(沒列出來的先略去):
- endpoint->context = OpenLiteIpc(LITEIPC_DEFAULT_MAP_SIZE);
- endpoint->boss = NULL;
- endpoint->routers = VECTOR_Make((VECTOR_Key)GetIServerProxy, (VECTOR_Compare)CompareIServerProxy);
- endpoint->name = name;
- endpoint->identity.handle = (uint32_t)INVALID_INDEX;
- endpoint->identity.token = (uint32_t)INVALID_INDEX;
- endpoint->identity.cookie = (uint32_t)INVALID_INDEX;
- endpoint->registerEP = RegisterRemoteEndpoint;
context = OpenLiteIpc() 打開本進程這一端的IPC通信通道,獲取上下文,相當(dāng)于拿到了開啟通道一端大門的鑰匙,要能夠進行IPC通信,需要另一端的大門也打開才行。
boss:本EP的專用于IPC通信的線程的handle,還沒創(chuàng)建線程,目前是NULL;
endpoint->routers:這里先創(chuàng)建一個空的向量,配置向量的key和compare函數(shù)。本進程內(nèi)所有的service/feature中,符合條件的service/feature才能添加到這個向量中,成為這個routers Vector中的一個element,也就是內(nèi)部的通信節(jié)點,這樣才能對外部進程提供服務(wù)和接口。在具體的IPC通信時,會通過下面的endpoint->identity.token來確認(rèn)是哪個element提供服務(wù)。
name: 就是"ipc client"字符串,作用不大;
endpoint->identity.handle:是本EP向管理者注冊自己后,管理者為本EP返回的一個handle,非常重要,目前還沒有向管理者注冊自己,所以是INVALID_INDEX,這個INVALID_INDEX是特定的值[5],為什么是5,后面再講。
endpoint->identity.token:具體的IPC通信中才會用到,用來標(biāo)記本次IPC通信中,需要endpoint->routers 向量中的具體哪個element提供服務(wù)或接口。
endpoint->registerEP:是函數(shù)指針,指向本EP向管理者注冊自己的函數(shù),因為是"ipc client" EP,所以注冊函數(shù)是RegisterRemoteEndpoint(),要是"samgr" EP,注冊函數(shù)就會是RegisterSamgrEndpoint()。
- [3-2] SAMGR_AddRouter(g_remoteRegister.endpoint, &saName, identity, iUnknown)
在上一步創(chuàng)建好的EP內(nèi),把“符合條件”的service和/或feature作為本EP內(nèi)部的一個通信節(jié)點(router)添加到endpoint->routers向量里面去,進行管理。
“符合條件”的條件,有幾個,關(guān)鍵的兩個如下:
條件1:IUnknown *proxy 不能為NULL
service/feature對外提供的接口不能為空,為空也就意味著外部進程沒法使用service/feature提供的服務(wù)了,也就沒必要加到endpoint->routers向量里去了。
這個條件可以過濾掉很多service,比如Broadcast服務(wù),它在Init時,只通過RegisterService()注冊了服務(wù),并沒有注冊defaultApi,并且它自己也是有feature的,所以它的serviceImpl->defaultApi是NULL:

而向WMS服務(wù)(IMS服務(wù)也如此),在Init時注冊了default feature API,同時它自己也沒有feature,所以它的serviceImpl->defaultApi不是NULL,而是指向了WMSService 結(jié)構(gòu)體內(nèi)部的IUnknown接口對象,以此向外部進程提供功能。

條件2:SERVER_PROXY_VER 要匹配 0x80
上一個條件的defaultApi不為NULL,指向了service/feature結(jié)構(gòu)體內(nèi)部的IUnknown接口對象,而這個接口對象的版本ver,要滿足條件(匹配SERVER_PROXY_VER,即0x80),才能將其添加到endpoint->routers向量里,去對外部進程提供服務(wù)。

都滿足條件了,還要在endpoint->routers向量里先查找一下,確認(rèn)當(dāng)前接口proxy是否已經(jīng)在向量表里了,已經(jīng)在了的話,就不能重復(fù)添加。
當(dāng)前接口proxy沒在向量表里,那就可以創(chuàng)建一個router對象,將提供proxy接口的service/feature、identity、serverProxy等相關(guān)信息配置好,將router對象(指針)添加到endpoint->routers向量里。
添加router成功后,會調(diào)用 Listen(endpoint):

第一句,boss 不為NULL 就return掉,意味著,第一個添加到endpoint->routers向量里的router添加成功后,就進來創(chuàng)建boss線程,專門用于本進程的對外IPC通信,后面再添加router成功時,因為boss線程已經(jīng)在對外IPC通信了,所以不需要重復(fù)創(chuàng)建boss線程。
創(chuàng)建的boss線程,跑Receive()入口,具體做什么事情,我們稍后再講。
- [3-3] SAMGR_ProcPolicy(g_remoteRegister.endpoint, &saName, token)
能跑到這一步來,也是要首先滿足兩個條件:
1. 上一步的router成功添加到endpoint->routers向量里,拿到了有效的token,這個token就是router在向量的位置,也就是endpoint->routers->data[token]是一個指針,指向添加成功的router;
2. 本進程的g_remoteRegister.endpoint->running 標(biāo)記要為TRUE,這意味著管理者那端的IPC通道也打開了,本進程EP已經(jīng)完成了向管理者注冊,拿到了本EP中SvcIdentity identity關(guān)鍵的handle,本進程可以開始對外提供服務(wù)了。
兩個條件都滿足后,這里就是要向管理者注冊feature并且獲取這個feature的訪問權(quán)限策略信息,并保存在router的policyNum/policy 字段內(nèi)。看上去這個函數(shù)的作用與RegisterRemoteFeatures()的作用差不多。
接下來,我們看一下boss線程的Receive()入口函數(shù),都做了些什么事情。我把它分成4個階段來理解:
[4-1] 第一階段,向管理者知名EP注冊本EP,獲取本EP的身份信息中的handle。這步需要管理者g_server跑起來,知名EP打開IPC通道,這里才能通過IPC注冊本EP,在知名EP還沒跑起來之前,本進程EP的這一階段會跑如下的循環(huán):

registerEP是上面創(chuàng)建EP時配置的EP注冊函數(shù),對"ipc client" EP,注冊函數(shù)是RegisterRemoteEndpoint()。
進入RegisterRemoteEndpoint()里面看一下,又是一個while循環(huán),循環(huán)內(nèi)會發(fā)送IPC信息,向知名EP samgr注冊本EP,知名EP的信息直接硬編碼寫成:
- SvcIdentity samgr = {SAMGR_HANDLE, SAMGR_TOKEN, SAMGR_COOKIE}; //{0, 0, 0}
這就是所謂的“知名”了。
這個內(nèi)部循環(huán),注冊成功就拿到了SvcIdentity *identity中的handle,注冊不成功(主要原因是知名EP還沒開始工作),就會sleep(5s)再重新嘗試。
內(nèi)外兩層循環(huán)合在一起,相當(dāng)于在60s內(nèi)嘗試注冊9次,正常情況下只要知名EP跑起來了,就肯定能注冊成功的。
注冊成功,或者一分鐘內(nèi)注冊不成功,就會進入下面的第二階段。
[4-2] 第二階段,注冊成功,就拿到了知名EP返回來的SvcIdentity identity.handle。
一分鐘內(nèi)注冊不成功,直接就exit (-ret),意味著本進程要退出了,它的父進程(用戶態(tài)根進程)Init應(yīng)該就會收到SIGTERM或SIGCHLD信號,Init進程根據(jù) /etc/init.cfg 的配置,來決定是重啟單個進程,還是重啟整個系統(tǒng),后面的事情就另說了。
[4-3] 第三階段,注冊成功,就可以繼續(xù)往下跑了,EP的狀態(tài)endpoint->running = TRUE; 標(biāo)記置起來。
因為知名EP也會跑Receive() 的這些流程,但知名EP不需要跑[4-3]的RegisterRemoteFeatures(endpoint) 這一步,所以通過[4-1]獲取的endpoint->identity.handle來判斷是否是知名EP的handle,是知名EP的話,就跳過,不是的話,就調(diào)用RegisterRemoteFeatures(endpoint)來把本EP的features(也就是本EP的routers向量中的所有element)全部注冊到知名EP里去。
注冊的過程也比較簡單,就是遍歷EP->routers向量,把router在向量中的位置序號填寫到SvcIdentity identity.token,本EP的handle填寫到SvcIdentity identity.handle,連同本router的其它相關(guān)信息一并,通過IPC消息發(fā)給知名EP,并且由知名EP返回注冊成功和訪問權(quán)限策略信息,再次填寫回本EP對應(yīng)的router里。
[4-4] 第四階段,接下來就StartLoop(),本EP的boss線程進入監(jiān)聽IPC通信消息的狀態(tài),如果別的進程有IPC消息發(fā)送到本EP的handle,boss線程就可以監(jiān)聽到,然后調(diào)用Dispatch()函數(shù)來處理該消息。
到這里為止,系統(tǒng)服務(wù)的啟動和注冊就完成了,我們通過log來確認(rèn)一遍上述過程。
附件log是系統(tǒng)用戶態(tài)進程啟動到系統(tǒng)穩(wěn)定的log,開始是shell/apphilogcat先啟動,接著是bundle_daemon/sa_server/sensor_service這三個依賴關(guān)系相對簡單的服務(wù)啟動,它們的啟動流程也會完全符合上面的幾個步驟的,但我們還是接著前文,繼續(xù)往下分析wms_server進程的啟動。

上圖是wms_server進程依賴的service/feature的Init,以及main函數(shù)的[5-1]這步。接著我們跳過一大段media_server的啟動log。

上圖是wms_server進程啟動的[5-2]/[5-3]給service創(chuàng)建線程和消息隊列,開始監(jiān)聽進程內(nèi)部的多線程通信,這些都是前文解釋過了的。

上圖開始進入broadcast 服務(wù)的DEFAULT_Initialize()的流程,很明顯可以看出對應(yīng)著上面分析的四個步驟。
但是SAMGR_RegisterServiceApi()內(nèi)的三個步驟,只跑了前兩步,因為service和feature的SAMGR_AddRouter()這一步都是NG了,沒有router添加成功,自然就不跑第三步了,也就是說本進程中,broadcast service和feature,不對外部進程提供服務(wù)和接口。跑完這里,我打印出了當(dāng)前進程的g_remoteRegister(注意{}內(nèi)的地址,不同進程的g_remoteRegister的地址是不一樣的)全局變量的信息,見 DbgParse_g_remote{0x225922c8},可見還是和初始化狀態(tài)一樣的。

上圖開始進入WMS服務(wù)的DEFAULT_Initialize()的流程,很明顯可以看出對應(yīng)著上面分析的前兩個步驟,因為“RegFeatureApi(NO Feature)”,所以就沒有后面兩步了。
因為defaultApi不為NULL,并且QueryInterface的結(jié)果(版本匹配)也是OK的,所以SAMGR_AddRouter OK,第一個router被添加到了EP里面,所以開始Listen的流程。
為當(dāng)前EP{0x2247cd00}創(chuàng)建專門用于對外IPC通信的boss監(jiān)聽線程,并開始執(zhí)行Receive()入口函數(shù)的[4-1],開始兩層循環(huán)嘗試向知名EP注冊本EP,但是由于知名EP還沒有啟動,所以會得到:
- “[ERR][hm_liteipc] LiteIpcIoctl(IPC_SEND_RECV_MSG) ServiceManager not set!”
這個時候的EP狀態(tài)如下:

有了一個router,但是handle還是 -1,需要等待,一直等到非常后面,如下圖,才會繼續(xù)執(zhí)行[4-2]/[4-3],注冊EP成功并拿到handle為16,然后執(zhí)行[4-4]StartLoop:


上圖是IMS服務(wù)的DEFAULT_Initialize()的流程,與WMS的類似,也可以看出因為沒有feature,只跑了前兩個步驟。因為本EP已經(jīng)有boss線程在跑了,所以這里直接Listen boss線程就可以返回了。
此時的EP狀態(tài)如下圖,兩個router可以對外提供服務(wù),handle需要等待注冊EP成功才會拿到 16 這個值。

接著就是下面的兩步,
- [wms.cpp] main[5-4]: GetInstance()->Run()
- [wms.cpp] main[5-5]: while(1)
等EP拿到handle后,進程wms_server就可以順利對外提供服務(wù)了。
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)