The client() function, which encompasses the client-side functionality of the sample program, substantially differs from the client() function in the short message LPC sample. This is because a majority of the shared-section handling is performed in the client.
The client() function starts creating a shared section by calling the CreateFileMapping() API function. Note that the section is created with read+write permissions. Also note that, the file handle, passed as –1, means an unnamed section not associated with any file is created. You can create a section by mapping a disk file, but it is not necessary. The function passes the section handle, returned by the CreateFileMapping() function, to the NtConnectPort() function via the sectionInfo parameter. The NtConnectPort() function maps the shared section in the client as well as the server address space before sending a connection request to the server. The NtConnectPort() function returns after successfully establishing a communication channel with the server. Upon return, the sectionInfo structure contains the information about shared-section mapping. The function also returns the handle to the LPC port, used by the client for issuing requests.
After a successful connection establishment, the client goes in a "send request – wait for reply loop. The client asks the user for a string that it sends to the server as the parameter. (If you enter quit, the client exits.) The client also inputs the offset, within the shared section. After receiving these inputs, the client copies the given string at the specified offset in the shared section. It fills up a LPC message indicating the base address, of the shared section, in the server address space and the offset of the string within the shared section. The client sends the LPC message to the server over the port by calling the NtRequestWaitReplyPort() function. Upon receiving the message, the server reverses the string and sends a reply message. The client prints the reversed string upon return from the NtRequestWaitReplyPort() function.
main(int argc, char **argv)
{
OBJECT_ATTRIBUTES ObjectAttr;
UNICODE_STRING uString;
int rc;
/* Initializes the object attribute structure */
memset(&ObjectAttr, 0, sizeof(ObjectAttr));
ObjectAttr.Length=sizeof(ObjectAttr);
ObjectAttr.ObjectName=&uString;
RtlInitUnicodeString(&uString, PORTNAME);
if (argc == 1) {
/* If no parameters are specified for the
* program act as the server
*/
rc = server(&ObjectAttr);
} else {
/* If any command line parameter is specified it
* acts as the client
*/
rc = client(&uString);
}
return rc;
}
Similar to the short message LPC sample, the main() function in this sample program does not have any substantial code. It simply acts as a control function that calls either the server() function or the client() function depending on whether the program is invoked with command line parameters. The program also uses the PRINT.H and PRINT.C files for printing the LPC messages.
QUICK LPC
Quick LPC is the fastest form of LPC. Apart from that, Quick LPC has some peculiarities. For one, Quick LPC does not use port objects. Second, Quick LPC serves as the exclusive medium of communication for the Win32 subsystem. The Windows NT kernel supports only a single server (per client) using Quick LPC; the Win32 subsystem occupies this slot. Therefore, if you want to use Quick LPC, you need to modify the kernel a bit. (Note that until now, we presented only user-level code in this chapter.) However, talking about the peculiarities without giving details can make this concept puzzling. So, here we present details about Quick LPC.
Quick LPC is used only in Windows NT 3.51.
Advantages of Quick LPC
Let us first see why Quick LPC is faster than the regular LPC. The LPC communication using the port objects proves slow for a couple of reasons. One, there is a single server thread waiting on the port object and servicing the requests. This single server thread is naturally overloaded in anticipation of multiple clients making frequent requests. You can overcome this disadvantage by using a fleet of slave threads. The main server thread gets requests from the port and simply passes them on to one of the slave threads for servicing. The server threads run in parallel with the main thread and process requests when the main thread receives new requests.
Another problem with the regular LPC is that the context switching between the client thread and the server thread happens in an uncontrolled manner. Typically, a client sends a request on the port and waits for a response from the server (except while sending datagrams using the NtRequestPort() function). While the client thread waits on the port for a reply, the thread scheduler searches for the most eligible thread for execution. More often than not, this new thread selected for execution differs from the server thread. Essentially, the server thread is not immediately scheduled when the request comes over the port. Similarly, the client thread may not be scheduled immediately after the subsystem sends a reply.
Quick LPC overcomes both of the aforementioned disadvantages. The first disadvantage is overcome by creating a dedicated server thread per client thread. The second disadvantage is overcome by using a kernel object named an event pair, which serves as the backbone of the Quick LPC. As implied by its name, an event pair consists of a pair of event objects, named high event and low event, respectively. The NT kernel provides functions, which allow a thread to wait on one of the events in the pair and signal the second event in an atomic operation. The event pair object also guarantees that the thread waiting on the signaled event is the next thread to be scheduled.
Note: Two sets of functions operate on the event pair. One set of functions gives the regular sleep-wakeup protocol; it does not guarantee immediate thread scheduling: The NtSetHighWaitLowEventPair() function and the NtSetLowWaitHighEventPair() function. In this chapter, we discuss the other set of functions that guarantee the immediate scheduling of the signaled thread.
Quick LPC and Win32 Subsystem
To clarify, lets see how the Win32 subsystem uses the Quick LPC. When a client thread makes the first GUI call, the Win32 subsystem creates a thread dedicated to the calling client thread. The new server thread creates an event pair object and calls the KiSetLowWaitHighThread() function, with the event pair object as a parameter. The server thread waits for the high event from the pair to get signaled. Now, whenever the client thread makes a GUI call, the KiSetHighWaitLowThread() function is called. This call signals the high event in the pair and en-queues the client thread in the list of threads waiting for the low event to get signaled. In other words, the client thread sleeps while the corresponding server thread, waiting on the high event, gets woken up. After processing the request, the server thread calls the KiSetLowWaitHighThread() function that makes the server thread sleep for the high event and the client thread, which was waiting for the low event, takes over the CPU. This sequence repeats for every GUI call made by the client.
The event pair object takes care of the controlled thread switching. It provides no mechanism for passing parameters and return values. The Quick LPC achieves this with a dedicated shared section for each client thread. The Win32 subsystem also creates a dedicated section object and maps it in the address space of both the client and the subsystem processes. The client thread fills in the parameters in the shared area before passing the control to the server thread and similarly the server thread copies the results in the shared area before returning the control to the client thread.
Naturally, you may think, Why is the Quick LPC restricted to the Win32 subsystem? Why cant it operate as a general-purpose Inter-Process Communication mechanism? The reason is that you cannot call the functions KiSetLowWaitHighThread() and KiSetHighWaitLowThread() from the user-mode process directly. Windows NT reserves two software interrupts for this purpose. Interrupt 0x2C calls the function KiSetLowWaitHighThread() and interrupt 0x2B calls the function KiSetHighWaitLowThread().These two interrupt routines operate on a default event pair object. The Thread Environment Block (TEB) maintains a pointer to this default event pair. You can use the NtSetThreadInformation() function to set this pointer. Since only one event pair object can associate with every thread, only one server thread can make use of the Quick LPC; that server thread typically belongs to the Win32 subsystem for most applications. However, non-Win32 applicationsor for that matter, non-GUI applicationscan still use the Quick LPC for general-purpose communication.
Steps in Quick LPC Communication
The server application for a non-GUI program can mimic the Win32 subsystem and use the Quick LPC for communication. Lets see what the Win32 subsystem does while establishing the Quick LPC.
It creates one dedicated thread in the CSRSS process.
It creates a section object, 64K in size.
It maps the view of sections in the client thread and the subsystem.
It creates an event pair object.
It duplicates the event pair object handle in the client process.
It duplicates the section object handle in the client process.
It calls NtSetInformationThread() function, with SetEventPairThread as the information class, for the subsystem thread and for the client thread.
It returns information such as duplicated event pair handle, section handle, address in the client process where the shared section is mapped, and so on.
After this, the thread data in the client thread reflects that the Quick LPC is established.
The dedicated CSRSS thread calls INT 2CH (KiSetLowWaitHighThread). Because of this, the CSRSS thread is blocked until the client sends a request.
When the client makes a GUI call, the client fills in the parameters in the shared section and issues INT 2BH (KiSetHighWaitLowThread). Because of this, the server thread wakes up, performs the specified task, and fills in the results in the shared section. Then, the server thread issues the interrupt 0x2B, which wakes up the client thread. This 2B/2C sequence repeats until the client thread terminates.
Quick LPC Sample
Here, we present a program that mimics the Win32 subsystem and shows how you can use the Quick LPC for general-purpose communication. The program, only a demonstration, does not implement any service. As described earlier, the event pair object does not provide any parameter passing mechanism. The user of the event pair object has to implement parameter passing using shared sections. In this sample program, we do not demonstrate the use of shared section because it is straightforward and we already demonstrated it at length in the shared section LPC sample program. In this sample program, we demonstrate only how to implement controlled switching between the client thread and the server thread using the event pair object.
Following the usual practice in this chapter, the same sample program acts as the server or the client depending on whether you pass a command line parameter to the program. You should first start the program in the client mode. The client prints its own process ID and thread ID. The server needs this information to establish the event pair object. After you start the program in the server mode, it asks you for the process ID and the thread ID of the client. After initializing the thread object, the server issues INT 2CH then waits for a client request. Meantime, the client waits for a user keystroke. After getting a keystroke from the user, the client issues a INT 2BH, which switches the execution thread from the client thread to the server thread. The server prints a message indicating that it is scheduled and then waits for a keystroke. Upon receiving the keystroke, it switches the control back to the client by triggering INT 2C again. This continues until you kill the server and the client by pressing Ctrl+C or using the Task Manager.
The implementation, for both client and server, resides in a single file, QLPC.C, which we describe in detail in the next section.
Apart from the usual header inclusions, the initial portion of the QLPC.C file defines the name of the event pair used by the sample program to demonstrate controlled thread switching. We create the event pair at the root of the object directory. If you want to create several objects in the object tree, we suggest you create these objects under an application-specific directory.
Order Your SQL Fundamentals CD Today! Learn how to use SQL Server, understand Office integration techniques and dive into the essentials of SQL Express and Visual Basic with this free SQL Fundamentals CD.
You've Deployed SharePoint...Now What? This one-day free online conference delivers the technical knowledge needed to kick MOSS up a notch. In one information-packed day, independent SharePoint experts will present practical, real-world information and provide take-away, ready-to-use solutions
What Would You Do If You Ran Microsoft? ITTV's 2008 inaugural video contest, "If I Ran Microsoft..." is your chance to tell it like it is. Be goofy or be serious, but don"t miss this chance to have fun, win prizes, and go viral in a major way.
Maximize Your SharePoint Investment This web seminar discusses how true bi-directional replication of SharePoint content from one server to another enables branch offices to maintain access to current SharePoint content.