Workplace Shell Processes and Threads

by Deepa Desai

In OS/2 Warp Version 3, improvements have been made to increase the operating system's overall performance, including the execution of threads In OS/2 Warp, threads are no longer destroyed after they have run their course, but instead are returned to a pool of threads maintained by the Workplace Shell. In this article, I will explain how the Workplace Shell deals with multithreading, describe each thread in summary, and take you through the Workplace Shell two-process architecture and the relationship between various threads.

Multithreading in the Workplace Shell
A thread is a dispatchable unit of execution that consists of a set of instructions, related central processing unit (CPU) registers, and a stack. Every process in the system has at least one thread called the main thread and can have multiple threads running at the same time. The Workplace process's first thread becomes the main thread or the user-interface thread. The main thread monitors the Workplace Shell's Presentation Manager (PM) message queue and is also responsible for the user response time. Hence, only a minimum amount of processing should be done on the main thread, since this affects performance.

All Workplace Shell objects running under the Workplace process can be accessed by any thread belonging to that process. Some methods in the Workplace Shell require that a message queue be created for the thread in which they were invoked. When creating your own thread, make sure you create a message queue for that thread, even if your thread does not have a requirement for it. The following code is required for each thread. Sample Code 1

In the above implementation, it takes longer to create a thread, since every thread in the Workplace process has to create a message queue. Instead of creating a new thread (by calling DosCreateThread) every time you do multiple operations, you could create a general set of routines that will create and manage a pool of threads, each thread being properly initialized so that code executing on that thread s context is capable of executing PM calls. In the Workplace Shell, a new function called RunOnThread creates a pool of threads to avoid calling DosCreateThread every time you do multiple operations. The Shell uses these threads by calling the RunOnThread function, passing the name of the function to be executed on the thread and a single parameter that will be passed to the function when it is called. These threads are maintained in such a way that the last thread that completed executing a function will be the next thread chosen when a new request is received. This function also checks for any idle threads, and the idle thread is chosen when a new request is received Otherwise it creates a new thread by calling DosCreateThread. This minimizes system paging, and this allocation scheme generally results in the stack segment for the thread already being present. This function improves system response time by eliminating expensive thread creation, initialization, and termination.

RunOnThread uses a doubly-linked list structure called THREADSTRUCT. The purpose of using a linked list with arrays is to place the most current thread that becomes idle as the first thread in the pool of threads. The advantage is that next time there is a request, the function looks for an idle thread. Most of the time the first thread will be the idle one and its thread structure will already be in the stack. This reduces memory swapping.

Thread Structure
The first thread in the pool of threads is declared as a global FirstThread, which is a pointer to the linked list structure (THREADSTRUCT) Sample Code 2

You can see the chain of threads running in the Shell process and Workplace process using the kernel debugger.

In the kernel debugger, dump the FirstThread  %13f60034 00af599c 100b0000 00000000 00000000 %13f60044 00000000 80010059 00000000 17cc0b8c %13f60054 17cc0be9 17cc0bf2 13f8428c 00000000 %13f60064 00010154 00000000 00010043 800000ab  The first word is a pointer to the FirstThread structure.  %00af599c 0000000a 1bedce9c 00af5f64 00000001 %00af59ac 00010168 000a000a 12dc0c90 0051598c %00af59bc 00000000 1beacbe9 2050534d 00000148 %00af59cc 45455246 1bec7e0b 0000012c 000040db  The first word is the Thread ID of the FirstThread. The second word is the Address of the code to be executed (VacateFolderThread).  0170 1bedce9c pmwp CODE32 VacateFolderThread  This is a pointer to the next thread in the pool of threads, which is the PopulateThread.  %0051598c 0000000b 1bec7ad4 004ea078 00000001 %0051599c 00010069 000a000b 12dc6274 005153cc %005159ac 00af599c 1beacbe9 2050534d 00000148 %005159bc 45455246 1bec7e0b 0000012c 000028bb 0170 1bec7ad4 pmwp CODE32 PopulateThread  This is a pointer to the previous thread of PopulateThread, in this case the VacateFolderThread.  %00af599c 0000000a 1bedce9c 00af5f64 00000001 %00af59ac 00010168 000a000a 12dc0c90 0051598c %00af59bc 00000000 1beacbe9 2050534d 00000148 %00af59cc 45455246 1bec7e0b 0000012c 000040db 0170 1bedce9c pwmp CODE32 VacateFolderThread  This is a pointer to the next thread of PopulateThread, in this case the ProcessQueue thread.  %005153cc 0000000d 1bed0e58 00000000 00000000 %005153dc 0001006d 000a000d 12dc5f78 005343ac %005153ec 0051598c 1beacbe9 2050534d 00000138 %005153fc 4d454d53 1bec690a 0000011c 000028c0 0170 1bed0e58 pmwp CODE32 ProcessQueue
 * 1) dd FirstThread l10
 * 1) dd 00af599c l10
 * 1) ln 1bedce9c
 * 1) dd 0051598c l10
 * 1) ln 1bec7ad4
 * 1) dd 00af599c l10
 * 1) ln 1bedce9c
 * 1) dd 005153cc l10
 * 1) ln 1bed0e58

%005343ac 00000009 1bed06d4 00000000 00000000 %005343bc 00010042 000a0009 12dc6f5c 00556240 %005343cc 005153cc 1beacbe9 2050534d 0000004c %005343dc 4d454d53 17c6f308 0000002d 000022da 0170 1bed06d4 pmwp CODE32 AgerThread
 * 1) dd 005343ac l10
 * 1) ln 1bed06d4

%00556240 00000008 1bed08dc 00000000 00000000 %00556250 00010041 000a0008 12dc6c60 00000000 %00556260 005343ac 1beacbe9 2050534d 00000028 %00556270 4d454d53 1b9a4022 0000000c 00001b02 0170 1bed08dc pmwp CODE32 SleepyTimeThread 
 * 1) dd 00556240 l10
 * 1) ln 1bed08dc

The Two-Process Architecture
There are two processes in the Workplace Shell.
 * The Shell process can be any application xxx.EXE, not just the default PMSHELL.EXE. For the default PMSHELL.EXE, the features of the Shell process are as follows:
 * The Starter thread is always running on the main Shell process
 * If the Workplace process has died or not started, the Starter thread will automatically restart it

The PMSHELL.EXE program can be run as a debug process using the RUNWORKPLACE= line in the CONFIG.SYS file as follows: SET RUNWORKPLACE=C:\OS2\CMD.EXE Then start the shell: IPMD PMSHELL.EXE If RUNWORKPLACE= is not in the environment, then assume that the user does not want to start the Workplace process.


 * The Workplace process is the one in which SOM is initialized, all the Workplace Shell classes are loaded and initialized, the Desktop is created, the restart list is processed, and so on. All objects that represent the Workplace Shell classes and their subclasses should run on this process. The Shell process is responsible for restarting the Workplace process in the event that it has ended as a result of a trap.

The PROTSHELL= statement in the CONFIG.SYS file indicates which file is to be started as the Shell process; the RUNWORKPLACE= statement in the CONFIG.SYS file indicates which file has to be started as the Workplace process The default configuration sets both the PROTSHELL and RUNWORKPLACE variables to PMSHELL.EXE.

The following diagram illustrates the initialization sequence



Figure 1: Initialization sequence diagram

Threads in the Workplace Shell
The following threads are initialized from the Shell process and are closely tied to the functioning of the Workplace process.


 * Starter Thread:This thread is responsible for starting the Workplace process It automatically restarts the Workplace process in the unlikely event that it traps or the user code running on the Workplace process traps.
 * The global tid for the Starter thread is vtidStarterThread.


 * Shutdown Thread:This thread is created on the Shell process when a shutdown is initiated. There is only one instance of this thread at any particular time. It closes all running applications in the system.
 * The global tid for the Shutdown thread is vtidShutdownThread.

The following threads run under the Workplace process.


 * Object Thread:This thread represents the workplace object window that does not have any visible windows. Most of the I/O-intensive operations are done on this thread This thread utilizes an event semaphore to signal the Sleepy Time thread when it is idle.
 * The global tid for the Object thread is vtidObjectThread.


 * Wheel Watcher Thread:This thread is responsible for catching notifications from the file system regarding moves, copies, renames, and so on, from the command line. File system notifications are then added to a system queue, which is monitored by the Process Queue thread.
 * The global tid for the Wheel Watcher thread is vtidWheelWatcherThread.


 * Process Queue Thread:This thread takes file system notification from a queue, and finds the folder and object, if they are awake If they are not awake, the file change notification is thrown away. If they are awake, then the notification is handed off to the Ager thread.
 * The global tid for the Process Queue thread is vtidProcessQueueThread.


 * Ager Thread:This thread is responsible for aging the file system notification events for an appropriate amount of time, to eliminate duplicate refreshes and unnecessary repainting of the file system objects that are being changed. When a notification has aged appropriately, this thread notifies the Object thread that a notification is ready.
 * Sleepy Time Thread:This thread maintains a list of sleepy objects, which might need to be made dormant, and progressively ages them until they are either removed from the sleepy object list or need to be made dormant.
 * Lazy Writer Thread:This thread is responsible for writing an object s state in its persistent form. It waits for an event semaphore that is signaled from various other threads.
 * The global tid for the Lazy Writer thread is vtidLazyWriterThread.


 * Add First Child Thread:This thread is initiated when you request to populate a folder in tree view. This thread is also created when folders are added/removed to the open tree view or to one of the subfolders in an open tree view. This thread uses a round-robin queue to fairly service folders that need processing.
 * The global tid for the Add First Child thread is vtidAddFirstChildThread.


 * Populate Thread:This thread is created when a folder is opened, and it is from this thread the population of a folder takes place. This thread calls _wpPopulate, which is responsible for populating the folder with the contents of the given directory.
 * Vacate Folder Thread:This thread is invoked when a folder is closed It saves the folder's icon position and window position, and then sends a message to the container to destroy its window and its child windows.
 * The global tid for the last Vacate Folder thread is vtidLastVacateFolderThread.


 * Async Refresh Thread:This thread refreshes a folder that is already populated, in case anything has been altered. This thread invokes _wpRefresh on a specified folder. This thread is also initiated when Refresh is chosen from the context menu.
 * Finder Thread:This thread is created when a Find operation is initiated from the user interface. This thread does the actual finding of objects.
 * Tasking Thread:This thread is invoked from user-initiated Copy, Move, and Link operations on a separate thread to reduce the response time on the main user-interface thread.

Summary
This article covered all the basics about multithreading within the Workplace Shell, how the Workplace Shell is initialized, and the relationship between various threads in the Shell. I hope this article helps you understand why and how the Workplace Shell functions as it does. Check out the Workplace Shell Programming Guide for more details on the threads running under the Shell process and Workplace process.