跟着Code走,详解Symbian Client/Server架构
(有刪減)
from:http://blog.csdn.net/beyondexisting/article/details/5862363
Client/Server架構是Symbian下最主要的進程間通信方法。
?===How to use========================
【Server編程代碼】
定義一個派生自CServer2的類。
實現必須的NewSessionL純虛函數。
class CTestServer : public CServer2??
{??
private:??
??? virtual CSession2* NewSessionL(const TVersion& aVersion,const RMessage2& aMessage) const;??
…??
}
初始化過程中,各種初始化操作完成后,必須調用基類CServer2的StartL("MyTestServer");函數在內核中注冊Server,傳入的參數是Server的名字。之后所有的Client嘗試連接時,可以通過這個名字找到Server。
為了處理Client的請求,Server還必須實現派生自CSession2的的session類。Server類在NewSessionL函數中,必須新建session類并返回。Client連接Server時,框架會自動調用NewSessionL函數,之后Client的所有命令都由新建的session對象處理。
session對象必須實現ServiceL函數,Client發出請求時,框架會調用session對象的ServiceL函數進行處理。
class CTestSession : public CSession2??
{??
public:???
??? void ServiceL(const RMessage2& aMessage);??
…??
}
【Client編程代碼】
然后再看看Client需要連接Server并請求操作時,需要完成的代碼。Client必須實現派生自RSessionBase的類,并提供連接Server的函數和向Server發送命令的函數。
class RTestClient : public RSessionBase??
{??
public:??
??? TInt Connect();??
??? TInt SendRequest(TIng arg);??
…??
}
連接Server的函數可以直接通過調用基類的CreateSession函數,建立與Server的連接。需要注意的是如果發現Server程序還沒有啟動,首先需要把Server程序啟動。向Server發送命令的函數通過調用基類的SendReceive函數實現,這個函數的參數為一個請求碼和請求參數。
需要注意的是SendReceive函數主要有兩個重載版本,如下。帶TRequestStatus參數的是異步版本,調用后該函數立即返回,請求完成后會向請求線程發送RequestComplete的信號。
void RSessionBase::SendReceive(TInt aFunction,const TIpcArgs& aArgs,TRequestStatus& aStatus) const
TInt RSessionBase::SendReceive(TInt aFunction,const TIpcArgs& aArgs) const
以上就是Client/Server通信的最基本代碼,當然其中很多細節都沒有包括,你可以查看Nokia的文檔了解詳細代碼。
?
【Server用戶態實現代碼】
Server用戶態實現代碼主要包括兩個部分:一是Server本身的初始化,在內核中創建DServer對象;二是從內核中獲取需要處理的Message并進行處理。
- 【Server初始化】
CServer2::StartL函數的實現代碼在Symbian OS源碼?中的Kernal Package的文件kernel/eka/euser/cbase/ub_svr.cpp中,如下。CServer2有一個成員變量RServer2 iServer;這個成員變量保存了當前Server在內核中對應的DServer對象的handle。下面代碼中的第一句iServer.CreateGlobal就是創建內核DServer對象。然后會把自己放入CActiveScheduler中(前面說過CServer2本身是AO)。
EXPORT_C TInt CServer2::Start(const TDesC& aName)??
??? {??
??? TInt r = iServer.CreateGlobal(aName, iSessionType, iServerRole, iServerOpts);??
??? if (r == KErrNone)??
??????? {??
??????? CActiveScheduler::Add(this);??
??????? ReStart();??
??????? }??
??? return r;??
??? }
ReStart函數通過內核DServer句柄,調用異步函數獲得需要處理的Message,然后調用SetActive表示AO激活。
EXPORT_C void CServer2::ReStart()??
??? {??
??? iServer.Receive(iMessage,iStatus);??
??? SetActive();??
??? }
- 【從內核獲取Message并處理】
當從內核DServer獲取到需要處理的Message后,void CServer2::RunL()會被調用。(如果一直沒有需要處理的Message,那么異步函數便一直不返回,直到有Message需要處理為止)void CServer2::RunL()根據Client的請求碼進行處理。請注意iMessage也是R類的成員變量,它只是內核中message的handle,并不是message本身。
小于0的請求碼是建立連接或者斷開連接的請求。如果是連接請求,內核態會創建DSession對象,并放入DServer對象的session列表中,用戶態會調用NewSessionL函數創建session對象,并放入CServer2的session列表中,同時還會把用戶態session對象的指針保存到內核DSession.iSessionCookie中。如果是斷開連接請求,會用從Message中得到的session對象指針,complete Client的請求,內核態如果發現時EDisconnect請求,會在真正complete用戶態Client的請求前,從DServer的session列表中刪除對應的DSession對象,然后刪除內核中的DSession對象。
大于等于0的請求碼對應Client的其他功能請求。首先從通過message handle從內核中獲得對應用戶態session對象指針,然后調用CSession2::ServiceL函數。
處理完一個Message后,在void CServer2::RunL()代碼的最后,會再次調用ReStart函數進入等待Message狀態。
?
【Client用戶態實現代碼】
Client用戶態實現的代碼包括初始創建session連接,后續的命令發送操作,及最后的session斷開操作,下面我們分別看。
- 【創建session連接】
創建session連接在用戶編程代碼中通過調用RSessionBase.CreateSession完成,該函數調用Exec::SessionCreate完成session創建。內核在ExecHandler::SessionCreate函數中處理session創建請求,在正式創建session之前,首先會檢查capability。調用RSessionBase.CreateSession,僅僅觸發內核創建了DSession對象,用戶態Server還并未創建CSession2對象。RSessionBase.CreateSession會接著調用RSessionBase.DoConnect,該函數向Server發送RMessage2::EConnect。在前面【Server用戶態實現代碼】中已經提到,用戶態如果收到RMessage2::EConnect,會創建CSession2對象。
- 【發送Message到Server】
Client調用RSessionBase.SendReveive->RSessionBase.DoSendReceive->RSessionBase.SendSync->Exec::SessionSendSync(iHandle,aFunction,(TAny*)aArgs,&s);完成Message發送。內核對應處理函數為文件kernel/eka/kernel/sipc.cpp中的ExecHandler::SessionSendSync。內核首先會根據session handle從內核對象列表中找到對應的DSession對象,然后把Message放入自己的Message隊列SDblQue?? iMsgQ;,等待用戶態Server從內核獲取Message。如果用戶態Server已經是處于等待Message狀態,內核此時會complete用戶態Server獲取Message的異步請求。
- 【斷開session連接】
用戶代碼調用RSessionBase.Close-> ExecHandler::HandleClose斷開session 連接。內核態實現代碼是文件kernel/eka/kernel/sexec.cpp中的函數ExecHandler::HandleClose。該函數會從經過多個函數調用后,調用DSession.Close,該函數把DSession對象引用計數減1,并向向DServer的Message隊列中加一個EDisconnect消息。
用戶態Server對EDisconnet并沒有多少處理,僅僅立即complete當前正在處理的Client請求。內核在ExecHandler::MessageComplete中處理complete請求的操作,進一步的調用過程為ExecHandler::MessageComplete->DSession::CloseFromDisconnect->DSession::Detach。DSession::Detach會complete Dession.iMsgQ中所有pending的request,如果發現總引用計數減為0,會調用K::ObjDelete刪除DSession對象。
NOTE:我并沒有看到CServer2在處理EDisconnect過程中delete CSession2對象,也沒有看到CServer2在處理新的連接請求時,重用之間new的CSession2,只看到CServer2在析構函數中清理CSession2隊列,并delete CSession2對象。不知道是我漏掉了代碼,還是確實發現了一個bug。
?
【內核態實現代碼】
下面我們再看看Server在內核態的實現代碼。
- 【Server創建過程】RServer2.CreateGlobal
首先從Server啟動時調用iServer.CreateGlobal看起。kernel/eka/euser/cbase/ub_ksvr.cpp中有RServer2.CreateGlobal 實現代碼,它調用Exec::ServerCreateWithOptions(&name8, aMode, aRole, aOpts);實現。根據??跟著Code走,詳解Symbian OS API?中的介紹,我們可以找到這個調用在內核中對應的實現代碼,在Kernel Package中的文件kernel/eka/kernel/sipc.cpp中,其中比較關鍵的幾句代碼如下。主要的動作包括,創建內核DServer對象,設置DServer的名字,并把創建的對象放到全局的對象列表中的EServer類型。
…??
DServer *pS = new DServer;??
…??
r = pS->Create();??
…??
r = pS->SetName(&n);??
…??
r = K::AddObject(pS, EServer);??
…
- 【獲取Message并處理過程】RServer2.Receive
RServer2.Receive的實現代碼在文件kernel/eka/euser/cbase/us_exec.cpp中,它調用Exec::ServerReceive(iHandle, aStatus, &aMessage)實現。其對應的內核實現代碼是文件kernel/eka/kernel/sipc.cpp中的函數void ExecHandler::ServerReceive(DServer* aServer, TRequestStatus& aStatus, TAny* aMsg),這個函數調用DServer.Receive獲取Message。
DServer.Receive如果發現當前Message隊列非空,則會從中獲取一個Message并準備返回給用戶態。如果Message隊列為空,則不會complete用戶態Server取Message的請求,當有Client發送Message到內核后,內核才會返回Message給用戶態Server,并complete用戶態Server的請求。
if (!iDeliveredQ.IsEmpty())??
??????? {??
??????? RMessageK* m = _LOFF(iDeliveredQ.First()->Deque(), RMessageK, iServerLink);??
??????? Accept(m);??
??????? }
DServer返回Message的代碼中大部分都是為了更新DServer的內部狀態,真正把Message相關數據寫到用戶態空間的過程不太容易看懂。
Accept函數的最后會調用Kern::QueueRequestComplete(iOwningThread, iMessage, KErrNone); –> aRequest->EndComplete(aThread); –> TInt r = NKern::QueueUserModeCallback(&aThread->iNThread, this); 函數QueueUserModeCallback的實現代碼如下,它會把RMessageK對象賦值給aThread->iUserModeCallbacks 。
TInt NKern::QueueUserModeCallback(NThreadBase* aThread, TUserModeCallback* aCallback)??
??? {??
??? if (aCallback->iNext != KUserModeCallbackUnqueued)??
??????? return KErrInUse;??
??? TInt r = KErrDied;??
??? NKern::Lock();??
??? TUserModeCallback* listHead = aThread->iUserModeCallbacks;??
??? if (((TLinAddr)listHead & 3) == 0)??
??????? {??
??????? aCallback->iNext = listHead;??
??????? aThread->iUserModeCallbacks = aCallback;??
??????? r = KErrNone;??
??????? }??
??? NKern::Unlock();??
??? return r;??
??? }
這樣當OS API調用返回時,會有調用UserModeCallback的動作(Symbian OS API調用有這樣的機制,OS API調用完成后會檢查是否有UserModeCallback需要執行,如果有則執行。詳細code請查看文件kernel/eka/kernel/arm/victors.cia中的__ArmVectorSwi函數,請注意UserModeCallback是在內核態執行的),這時會調用K::USafeWrite函數,把數據寫入到用戶態空間,詳細過程請查看代碼—文件kernel/eka/kernel/sipc.cpp中的函數RMessageK::CallbackFunc。
- 【DSession管理】
DServer對象創建后,除了在線程中保存其handle外,還會保存在內核全局對象列表中。這樣有Client請求連接時,才能從內核全局對象列表中找到DServer對象。相關代碼是文件kernel/eka/kernel/sipc.cpp中函數ExecHandler::ServerCreateWithOptions的下面一段。K::AddObject把DServer對象pS放到內核全局對象列表中的EServer類型中,K::MakeHandle在當前線程中保存DServer對象pS的handle。
??? r = K::AddObject(pS, EServer);??
??????????? if (r == KErrNone)??
??????????????? r = K::MakeHandle(nameLen ? EOwnerThread : EOwnerProcess, pS);??
??????????? }
DSession對象就不需要全局保存了,只需要保存在線程數據中即可。因為DServer對象中已經保存了DSession對象列表,這保證DSession對象一定可以被找到。文件kernel/eka/kernel/sipc.cpp中函數ExecHandler::SessionCreate有以下兩句代碼,第一句的作用是把新建的DSession對象放到DServer的session隊列(經查看代碼,DServer對象并沒有直接操作DSession,只是保存了DSession指針而已),第二句的作用是把DSession對象handle保存到線程數據中。請注意,session創建是由Client線程發起的,所以這里是保存在Client線程的內核數據中。
…??
r = s->Add(svr, aSecurityPolicy);??
…??
if (r==KErrNone)??
??????? r = s->MakeHandle();??
…
Client要發送請求時,內核從當前線程內核數據中可以得到對應DSession對象的handle。你可以從以下函數調用弄清查找DSession對象的過程。
ExecHandler::SessionSendSync->DSession::SendSync->K::ObjectFromHandle
什么時候刪除DSession對象呢?前面【斷開session連接】已經說明,用戶代碼調用RHandleBase.Close后觸發一系列動作。
- 【Message管理】
首先需要知道的是Message是放在內核全局數據K::SMsgInfo K::MsgInfo;中的(內核全局數據都定義在文件kernel/eka/kernel/sglobals.cpp中)。MsgInfo是如下定義的數據結構,實際對應一個內存chunk,同時保存了下一個可用的message地址,剩余空閑的message空間等。對MsgInfo本身的操作,都封裝在RMessageK中,無非是些內存操作,此處不贅述。
static struct SMsgInfo??
??? {??
??? DChunk* iChunk;??
??? TUint8* iBase;??
??? TUint iMaxSize;??
??? TUint iCurrSize;??
??? DMutex* iMsgChunkLock;??
??? RMessageK* iNextMessage;??
??? TInt iFreeMessageCount;??
??? } MsgInfo;
?
下面我們再看看Message的傳遞過程。用戶態Client調用RHandleBase.SendAsync發送Message到內核,內核的ExecHandler::SessionSend函數開始處理。Message傳遞其實就是函數調用過程,我們先列出Message傳遞相關的函數調用過程,如下。(這里我們列出的是異步請求的Message傳遞過程,同步請求直接使用了線程數據中專為同步請求預留的變量TheCurrentThread->iSyncMsgPt)
ExecHandler::SessionSend->DSession::Send->DSession::Send->DServer::Deliver->DServer::Accept->Kern::QueueRequestComplete->TClientRequest::EndComplete->NKern::QueueUserModeCallback
RMessageK在第一個函數DSession::Send中生成:RMessageK* m = session->GetNextFreeMessage();
第二個DSession::Send函數會把RMessageK放到DSession的iMsgQ列表中。
函數DServer::Deliver中,如果這時用戶態Server處于等待Message狀態,會調用DServer::Accept返回Message,否則調用RMessageK::SetDelivered把Message放到DServer的SDblQue iDeliveredQ;隊列中。
后面的操作就是把RMessageK放到UserModeCallback隊列中,OS API調用返回時RMessageK::CallbackFunc會得到調用。Message數據會被寫入到用戶態Server進程空間。
TClientRequest::EndComplete調用NKern::QueueUserModeCallback之后,還會調用NKern::ThreadRequestSignal(&aThread->iNThread); 這個函數把請求線程的iRequestSemaphore置為有效。這樣Client用戶線程的User::WaitForRequest就會返回,Client得以繼續執行。
?
我們再接下來看complete請求的過程。用戶態Server調用RMessagePtr2.Complete完成請求,內核態的ExecHandler::MessageComplete處理該請求。我們只關注普通請求,像EConnect和EDisconnect這種特殊請求,暫時忽略。complete請求的處理過程并不復雜,其實與上面介紹的返回Message給用戶態Server一樣,這里是返回request結果給用戶態Client。
ExecHandler::MessageComplete->Kern::QueueRequestComplete->TClientRequest::EndComplete->NKern::QueueUserModeCallback
?
【Client/Server架構圖】
綜上所述,用戶態Server實際是一個ActiveObject,Client/Server在用戶態主要通過內核對象的handle實現操作,具體通信過程都在內核中實現。Client/Server架構在用戶態/內核態的實現結構如下圖。其中需要留意一點的是,CServer2對象中雖然保存了CSession2對象的列表,但是并未直接使用,當CServer2通過RServer2 handle從內核中拿到Message需要處理時,從Message中可以得到內核保存的session cookie,實際就是用戶態CSession2對象指針。
(摘自Symbian OS Internals 第四章Inter-thread_Communication?)
總結
以上是生活随笔為你收集整理的跟着Code走,详解Symbian Client/Server架构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ubuntu、CentOS、macOS测
- 下一篇: 印象最深刻的三位老师、难忘的往事