Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 73 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
73
Dung lượng
525,81 KB
Nội dung
Request the window server to draw the bitmap Delete the bitmap. By the time the client's buffer is flushed and executed by the window server (the third step above), the bitmap could have been deleted. To avoid this, the FBS client-side implementation calls the window server client's Flush() function (through an interface function) before sending the FBS message to delete the bitmap. Note As well as handling ROM- and RAM-resident bitmaps, the FBS also handles ROM- and RAM-resident fonts. The precise details are different from those for bitmaps, but the motivations for using a server to share font data are essentially the same. The shared memory technique is not without cost: The shared heap is mapped to different addresses in different processes. This means that conventional pointers cannot be used within the heap: a handle system has to be used instead. The shared heap is not the default heap for either client or server process. This means that the default operator new() and operator delete() don't work. For both the above reasons, objects designed for use in the default user heap cannot be placed onto a shared heap. New classes must be written specially for this purpose. You have to use mutex synchronization to control access to objects on the shared heap. You have to make sure that things stay in sync when you delete objects that are shared with other servers. For the font and bitmap server, these costs are low because the shared heap contains large- scale objects (fonts and bitmaps) and because the usage patterns of these objects minimize the difficulties of mutex-based sharing. In addition, the benefits of sharing are very high because of: the intensity of operations involving bit-blitting, the enormous difference the efficient implementation makes to users' perceptions of system efficiency the size of the objects that would have to be exchanged using client-server transactions if the shared heap was not available. Other shared-heap server designs have been implemented on Symbian OS, but in each case the design issues and the cost/benefit analysis have had to be thought through very carefully. 18.3 Servers and Threads The session between a client and a server is owned by the kernel. The kernel specifies that the session is between the client thread or process and the server thread. This has simple, but profound implications for any program that uses servers: Unless sharable sessions are used, client-side resources representing server-side objects may only be used and destroyed by the client thread that created them. Server responsiveness to clients is governed by the duration of the longest possible RunL() of any active object running on the server thread. Most of the time, these implications are not onerous. Most Symbian OS clients and servers are single-threaded, and the active object paradigm for event-handling threads is perfectly adequate. In a few cases, though, these implications raise issues for Symbian OS programmers and, as usual, some techniques have been developed to tackle them. 18.3.1 Sharing Client-side Objects between Threads Many programs written for non-Symbian OS platforms are multithreaded: one thread might be used to handle user input, while another thread handles communication with a sockets protocol. The threads may communicate by means of a file and either thread may wish to update the display. Naturally, the two threads synchronize using mutexes or semaphores. In ER5, this was not possible because servers treated client sessions as being owned by a single client thread. For pure Symbian OS programs, this restriction does not matter. A pure Symbian OS program uses a single thread where other programs would use a process, and active objects where other programs would use threads. However, if you want to port a program from another operating system, this restriction can cause difficulties. For this reason, versions of Symbian OS v6.x onwards provide sharable sessions that allow a server to provide sessions that can be shared between multiple threads in the client process. These sharable sessions do not allow sharing between processes and servers are not obliged to implement sharable sessions, so consider the functions provided by any server that you use. If you need to use a server that does not provide sharable sessions, then the one technique that is available to work around this limitation is to implement a private server in the process that will own the session with the external server. Then, each thread in the process can own its own session with the private server. It can be seen that this is more complex to implement than directly using a server that provides sharable sessions. 18.3.2 Multithreading in the Server A server is implemented as a single event-handling thread. As usual, in Symbian OS, the server thread implements event handling by using active objects. In fact, the server itself is an active object: CServer::RunL() is coded to perform first-level handling of incoming requests, which usually results in a CSharableSession::ServiceL() call to handle a message. CServer::NewSessionL() and CSession/CSharable-Session destructors are also called in response to connect and disconnect messages. The server may contain other active objects for handling input events, timeouts, and so on. 18.3.3 Time-critical Server Performance For some time-critical applications, we can ask two very specific questions about server performance: What is the fastest guaranteed service time required to process a client message? What is the fastest guaranteed time needed to respond to an event on some I/O device owned by the server? By 'fastest guaranteed time', I mean the time that would be required in the worst possible circumstances. Often, the service time or response time will be much better than this, but that can't be guaranteed because something else might be happening instead. It only takes a little thought to arrive at the following important conclusion: Important The fastest guaranteed service time is limited by the duration of the longest-running RunL() of any active object in the server's main thread. This is because, when a client thread makes a request, the server thread may already be running a RunL() for another client request, or for some other activity within the server. The CServer::RunL() for the client request cannot preempt a RunL() that is already in progress. So the client will have to wait until the current RunL() has finished before its request even starts to be handled. We can easily see that the same applies to responding to external events. Important The fastest guaranteed response time to an external event is limited by the duration of the longest-running RunL() in the thread that drives a device. This has important implications for server design. If you are designing a high-performance server, you should not call long-running operations from any of your service functions. Furthermore, you should not perform long-running operations from any of the RunL()s of any other active object in your server's main thread. If you need to deliver long-running operations to clients, or to perform long-running operations for internal reasons, you must run them on a different thread. The most obvious way to structure that thread would be as a server – clearly, a low-performance server. You might run the server as a private server in the main server's process. The low-performance server's 'client' API should deliver its long-running functions asynchronously, so that the high-performance server can kick them off quickly and then handle their completion with an active object. If your server specifies a server-side interface – for instance, one that allows plug-in protocol implementations – and if your server has any critical response time requirements, you should be very clear about the responsibilities of anyone implementing that interface. Obviously, the kernel thread has the highest priority of any thread in the system. Device driver code – in interrupt service routines, device drivers, or delayed function calls (DFCs) – can block any other code. So device driver code, which is a special case of a plug-in API, should be particularly quick. 18.3.4 Thread Priorities If you are interested in server performance, then you are probably also interested in thread priorities. The basic rule is simple. Important A server with a shorter guaranteed response time should have a higher thread priority than a server with a longer guaranteed response time. Otherwise, a long-running RunL() in a low-performance server with a mistakenly high thread priority will block what might otherwise have been a short-running RunL() in a high- performance server whose priority has been sensibly chosen, but is lower than that of the misbehaved low-performance server. Important Don't kid yourself about response times. Awarding a server a high thread priority doesn't necessarily give it a short guaranteed response time. To get short guaranteed response time, you must analyze all the RunL()s in your server's main thread. If any of them is longer than is justified by the thread priority you have awarded your server, then it's effectively a low-performance server and you're compromising the ability of any lower-priority server to deliver on its response time promise. That's antisocial behavior: don't do it. Another rule about server thread priorities can be deduced from the rule above: Important All system servers should have a priority that is higher than all applications. This is because an application might include arbitrarily long-running code. If it was allowed to run at higher priority than any server, the application would block the server from servicing any of its clients. 18.4 The Client-server APIs It's now time for a brief review of the client-server APIs. I'll highlight the main features here, and in the next chapter. I'll demonstrate how they're used in practice. The main classes are all defined in e32std.h and e32base.h. They are: Class Purpose RThread A thread RHandleBase, RSessionBase Client-side session classes CServer, CSession, CSharableSession, RMessage Server-side server, session, and message classes. TPckg<T>, TPckgC<T>, TPckgBuf<T> Type-safe buffer and pointer descriptors for any kind of data. RSubSessionBase Client-side subsession. CObject, CObjectCon, CObjectIx, CObjectConIx Server-side classes related to subsessions. We will look at the main features of the most important classes (see Figure 18.8). Figure 18.8 18.4.1 Thread Basics Threads are basic for client-server programming because the client and the server are separate threads and must be able to refer to each other. The RThread class enables one thread to create or refer to another, to manipulate the other thread, and to transfer data between itself and the other thread. RThread's default constructor is set up to create an RThread object for your own thread: RThread me; Usually, however, a program uses an RThread to refer to another thread. In the context of client-server programming, the server uses RThreadsto refer to its clients. The server's client interface may use an RThread to create an instance of the server. It isn't usually necessary for client code to use RThreads directly. The main things you can do with an RThread are as follows: Create() a new thread. You specify a function in which execution is to begin and parameters to that function. The thread is created in suspended state, and you have to Resume() it to start it executing. Open() a handle to an existing thread. Kill() and Panic() the thread. Killing is the normal way to end another thread; panicking indicates that the thread had a programming error. Servers use this to panic their client when the client passes a bad request. Set and query the thread's priority. Cause an asynchronous request issued by that thread to complete, using RequestComplete(). ReadL() data from a descriptor in the other thread's address space, or WriteL() data to a descriptor in the other thread's address space. The full RThread API is well documented in the C++ SDK. Many RThread functions are mirrored in the User class's API. Functions such as Kill(), Panic(), and RequestComplete() affect the currently running thread, so that User::Kill(KErrNone); is equivalent to RThread me; me.Kill(KErrNone); 18.4.2 Interthread Data Transfer and the Package Classes Important In the Symbian OS documentation, interthread data transfer is referred to as interthread communication or ITC. I have used 'data transfer' rather than 'communication' here, because in reality communication is about much more than just data transfer. All transfer of data between threads is based on six member functions of RThread (found in E32std.h): TInt GetDesLength(const TAny* aPtr) const; TInt GetDesMaxLength(const TAny* aPtr) const; void ReadL(const TAny* aPtr, TDes8& aDes, TInt anOffset) const; void ReadL(const TAny* aPtr, TDes16& aDes, TInt anOffset) const; void WriteL(const TAny* aPtr, const TDesC8& aDes, TInt anOffset) const; void WriteL(const TAny* aPtr, const TDesC16& aDes, TInt anOffset) const; Interthread data transfer is performed from data buffers identified by a descriptor in both the currently running thread and the 'other' thread identified by the RThread object. The descriptor in the currently running thread is identified by a conventional descriptor reference (such as const TDesC8& for an 8-bit descriptor) from which an interthread write will take data. The descriptor in the other thread is identified by an address, passed as a const TAny*, which is the address of a descriptor in the other thread's address space (the address will probably have been passed from client to server, as one of the four 32-bit message parameters). Bearing this in mind: GetDesLength(const TAny*) returns the Length() of the descriptor referred to in the other thread's address space. GetMaxDesLength(const TAny*) returns the MaxLength() of the descriptor referred to in the other thread's address space. ReadL(const TAny*, TDes8&, TInt) reads data from the other thread into a descriptor in this thread. Data is transferred from the anOffset'th byte of the source. The amount of data transferred is the smaller of the number of bytes between anOffset and Get-DesLength() of the source descriptor in the other thread, and the MaxLength() of the destination descriptor in the current thread. There is also a 16- bit version of ReadL(). WriteL(const TAny*, const TDesc8&, TInt) writes data from this thread into a descriptor in the other thread. A 16-bit variant is also provided. If any of these functions is called with a TAny* that is not the address of a valid descriptor in the other thread, then a KErrBadDescriptor error results. GetMaxDesLength() and GetDesLength() return this as their result – you can distinguish it from a true descriptor length, because all Symbian OS error codes are negative. ReadL() and WriteL() leave with this as their error code. Important A bad descriptor almost certainly indicates a bad client program. Any server detecting KErrBadDescriptor should panic the offending client. All interthread data transfer uses descriptors. This is appropriate, because descriptors contain an address and a length. If you wanted to transfer a floating-point number from a client to a server, you could use the following client code: TInt p[4]; // Message parameter array TReal x = 3.1415926535; // A 64-bit quantity TPtrC8 xPtrC(&x, sizeof(x)); // Address and length in a descriptor p[0] = &xPtrC; // Pass address of descriptor as zeroth message parameter And this code on the server side: TReal x; TPtr8 xPtr(&x, 0, sizeof(x)); // Address, length, max- length Client().ReadL(Message.Ptr0(), &xPtr, 0); // Transfer data But this code isn't very type-safe, and it's not exactly straightforward either. The package classes offer a type-safe alternative. On the client side: TInt p[4]; // Message parameter array TReal x = 3.1415926535; // A 64-bit quantity TPckgC<TReal> xPackage(x); // Package into a descriptor p[0] = &xPackage; // Address of package And the server side: TReal x; TPckg<TReal> xPackage(x); // Package it up Client().ReadL(Message.Ptr0(), &xPackage, 0); // Transfer data The TPckg<T> and TPckgC<T> classes are simply type-safe, thin template wrappers around TPtr8 and TPtrC8. A third package class, TPckgBuf<T>, performs a similar function for TBuf8<sizeof(T)>. We can picture the APIs related to interthread data transfer, and other thread functions, as shown in Figure 18.9. Figure 18.9 18.4.3 Client-side Objects The main client-side object is RSessionBase, derived from RHandle-Base. As a server provider, your client interface should include a class derived from RSessionBase that handles communications from client to server. A client-side handle for server-side (and kernel-side) objects RHandleBase, which is defined in e32std.h, is the base class for client-side objects that refer to a number of kernel-side objects, and also for RSessionBase, which refers to server-side objects. For our purposes, the only relevant aspects of the RHandleBase class declaration are: class RHandleBase { public: inline RHandleBase(); IMPORT_C void Close(); inline TInt Handle() const; protected: TInt iHandle; }; In brief, an RHandleBase has a 32-bit handle that's used by the client to refer to a particular session with the server. From the server's perspective, the client's thread ID, combined with this handle, uniquely identifies a server-side session. RSessionBase – client-side session RSessionBase is the base class for any client-side session with a server. Here are the relevant parts of its declaration: class RSessionBase : public RHandleBase { public: enum TAttachMode {EExplicitAttach,EAutoAttach}; IMPORT_C TInt Share(TAttachMode aAttachMode=EExplicitAttach); IMPORT_C TInt Attach() const; protected: IMPORT_C TInt CreateSession( const TDesC& aServer, const TVersion& aVersion) ; IMPORT_C TInt CreateSession( const TDesC& aServer, const TVersion& aVersion, TInt aMessageSlots) ; IMPORT_C void SendReceive(TInt aFunction, TAny* aPtr, TRequestStatus& aStatus) const; IMPORT_C TInt SendReceive(TInt aFunction, TAny* aPtr) const; IMPORT_C TInt Send(TInt aFunction,TAny* aPtr) const; }; You use one of the versions of CreateSession() to create a new session with the server – referring to it by name. This allocates a handle in the base RHandleBase class. Normally, your derived client class will call CreateSession() from a friendlier client function, such as Open(), Connect(), and so on, as is done with the RFsSession class. Use Close(), defined in the RHandleBase class, to close the session. After being closed, the handle is set to zero, so that the object can no longer be used. The handle is also set to zero by RHandleBase's inline constructor, prior to connecting the session. Messages are sent using SendReceive() or Send(). The synchronous form of SendReceive() (the one that returns a TInt) is expected to complete immediately by means of a synchronous ServiceL() function in the server. The asynchronous form may complete some time later. The Send() routine sends a blind message to the server and no reply is expected. Both forms of SendReceive() take a 32-bit TInt argument called aFunction that specifies the request code for the message. The TAny*aPtr argument should point to four 32-bit words containing pointers or 32-bit integers that carry the parameters of the message. The synchronous form of SendReceive() is implemented (in private Symbian OS code) as: TInt SendReceive(TInt aRequest, TAny* aPtr) { TRequestStatus status; SendReceive(aRequest, aPtr, status); User::WaitForRequest(status); return status.Int(); } The asynchronous version is the more fundamental. It causes a message to be sent containing: the request code four 32-bit parameters the client's thread ID the handle from the RHandleBase the address in the client's process of the TRequestStatus to be used to complete the message. This is all wrapped up into a single message. When the server completes the message, it posts the 32-bit result back to the client's request status. We can now explain the other parameters to the variants of Create-Session(). The TInt aMessageSlots parameter tells the kernel how many messages to reserve for this client-server session. If the session supported only synchronous function calls, only one message slot could ever be used. If the session supports asynchronous requests, then one additional message is needed per asynchronous request that could possibly be outstanding, in most cases, that is, one or two – very rarely are any more needed. If the version of CreateSession() that omits the aMessageSlots parameter is used, then the session will use message slots from a pool held by the kernel. This allows more efficient use of resources overall. If your session can use a large number of message slots (i.e. it can have a large number of concurrent asynchronous operations), then you should allocate your own message slots to avoid taking too many of the common message slots. The TVersion contains three version numbers: Major, as in 7 for Symbian OS v7.0 Minor, indicating a minor feature release Build, indicating the build number – effectively a maintenance level. The TVersion is intended to ensure that the client API and the server implementation, which may be provided in separate DLLs, are at compatible levels. Note Using TVersion is probably no more or less effective than using a host of other disciplines to make sure these programs are in sync. In the GSDP sample code, the same DLL is used to implement client interface and server, so we are happy to pass a TVersion(0, 0, 0) in our CreateSession(). The Share() and Attach() functions are used to manage shared sessions. If the server to which you are connecting does not use sharable sessions (i.e. sessions that can be shared between multiple threads in the client process), then these functions are irrelevant. If you are using a server that supports sharable sessions, then the first session is created as normal and then RSessionBase::Share() is called on that session to make it sharable. If the EExplicitAttach value is used for the aAttachMode parameter, then any other thread that wants to share the session will need to call RSessionBase::Attach() on the session. If the EAutoAttach value is used, then all threads are automatically attached to the session. [...]... Chapter 19 : The GSDP Server Overview In the previous two chapters, I've described the Symbian OS active object and client-server frameworks – the foundations for system programming I'm now in a position to describe the Game Session Datagram Protocol (GSDP) server we implemented for sharing GDP datagrams among multiple client games on a single Symbian OS phone Along the way, we will encounter all the most... Protocol (GSDP) server we implemented for sharing GDP datagrams among multiple client games on a single Symbian OS phone Along the way, we will encounter all the most important practical techniques needed to program a Symbian OS server We've already seen that the purpose of the GSDP server is to allow GDP drivers to be shared between multiple games on a single Symbian OS phone To achieve this, the GSDP... specific task to perform, but in many ways it's typical, and I'll describe it in sufficient detail here so that you can use it with confidence as a basis for implementing your own servers 19 .1 Software Structure You can find the source code for the GSDP server and its client interface in \scmp\gsdp\ Here's the structure of the server and its interfaces in Figure 19 .1: Figure 19 .1 The server and its... under the WINS emulator, or as a new process on target machines The startup code for the emulator is also contained in gsdp.dll; for MARM, it's a separate, very small, exe This design minimizes the differences between the two platforms It follows the design of the Symbian OS DBMS server, which was written for Symbian OS v5 Among the useful aspects of this single-DLL design are that it eases debugging... The exact format of the addressing information is protocol specific, for example, a telephone number for SMS and a device address for Bluetooth That's why I use an 8- bit descriptor to pass this information as a binary blob rather than as text It's then up to the protocol implementation to interpret this appropriately Coding the message-passing functions is easy enough, if a little tedious for large... encounter all the most important practical techniques needed to program a Symbian OS server We've already seen that the purpose of the GSDP server is to allow GDP drivers to be shared between multiple games on a single Symbian OS phone To achieve this, the GSDP server: runs the GDP implementations on behalf of all games on a Symbian OS phone; associates an origin address, a destination address, a destination... in the same DLL Many Symbian OS servers use a separate DLL for the client interface, and a relatively large program for the server, delivered as a exe for target machines, and a DLL for the emulator Note One reason that may compel server authors to use a exe is if they cannot do anything to eliminate writable static data in the server code, perhaps because they are porting code 19 .2 The Client Interface... specific task to perform, but in many ways it's typical, and I'll describe it in sufficient detail here so that you can use it with confidence as a basis for implementing your own servers Chapter 19 : The GSDP Server Overview In the previous two chapters, I've described the Symbian OS active object and client-server frameworks – the foundations for system programming I'm now in a position to describe... and lets the client know 19 .2.4 The Client API as a DLL The GSDP server's client API is the first code we've looked at closely that is delivered as a static interface DLL with exported functions It's worth a quick break from the server theme to look at the DLL specifics here Firstly, the mmp file is as follows: // GSDP.MMP TARGET GSDP.DLL TARGETTYPE DLL UID 0x1000008d 0x101F8B57 SOURCEPATH SOURCE gsdpclient.cpp... RMessage's Panic(), Kill(), and Terminate() functions are convenience wrappers for corresponding RThread functions on the client thread RMessage is not intended for derivation, so its protected members should really be private 18 .5 Summary outlined the basic workings of servers, given some hints and tips for optimizing their performance, reviewed the API elements associated with servers In the next chapter, . subsessions. We will look at the main features of the most important classes (see Figure 18 .8) . Figure 18 .8 18 .4 .1 Thread Basics Threads are basic for client-server programming because the client. techniques have been developed to tackle them. 18 .3 .1 Sharing Client-side Objects between Threads Many programs written for non -Symbian OS platforms are multithreaded: one thread might be used. objects for handling input events, timeouts, and so on. 18 .3.3 Time-critical Server Performance For some time-critical applications, we can ask two very specific questions about server performance: