跟着Code走,详解Symbian Client/Server架构

来源:互联网 发布:php 分割二维数组 编辑:程序博客网 时间:2024/04/26 21:52

(有删减)

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对象指针。

SymbianOSInternalsBook_4.2

(摘自Symbian OS Internals 第四章Inter-thread_Communication )