Workplace Shell Processes and Threads

From EDM2
Jump to: navigation, search

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.

hab = WinInitialize(0);
if (hab)
   hmq = WinCreateMsgQueue(hab,0);
   if (hmq)
             WinCancelShutdown(hmq, TRUE);        

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)

typedef struct _THREADSTRUCT
  TID         ThreadID;      // Thread identifier of the current thread     //
  PFNTHREAD   ppThreadAddr;  // Address of the code to be executed          //
  ULONG       ulThreadArg;   // Argument passed to the target thread routine//
                             // as a parameter                              //
  BOOL        ThreadIdle;    // Thread is idle or in use                    //
  HEV         hevRun;        // Handle of the event semaphore to wait for   //
  HAB         hab;           // Anchor block for this thread                //
  HMQ         hmq;           // Message queue for this thread               //
  struct      _THREADSTRUCT  *NextThread; // Pointer to the next thread     //
  struct      _THREADSTRUCT  *PrevThread; // Pointer to the previous thread //

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

##dd FirstThread l10
%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.

##dd 00af599c  l10
%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).

##ln 1bedce9c
0170 1bedce9c pmwp CODE32 VacateFolderThread

This is a pointer to the next thread in the pool of threads, which is the PopulateThread.

##dd 0051598c  l10
%0051598c  0000000b 1bec7ad4 004ea078 00000001
%0051599c  00010069 000a000b 12dc6274 005153cc
%005159ac  00af599c 1beacbe9 2050534d 00000148
%005159bc  45455246 1bec7e0b 0000012c 000028bb
##ln 1bec7ad4
0170 1bec7ad4 pmwp CODE32 PopulateThread

This is a pointer to the previous thread of PopulateThread, in this case the VacateFolderThread.

##dd 00af599c  l10
%00af599c  0000000a 1bedce9c 00af5f64 00000001
%00af59ac  00010168 000a000a 12dc0c90 0051598c
%00af59bc  00000000 1beacbe9 2050534d 00000148
%00af59cc  45455246 1bec7e0b 0000012c 000040db
##ln 1bedce9c
0170 1bedce9c pwmp CODE32 VacateFolderThread

This is a pointer to the next thread of PopulateThread, in this case the ProcessQueue thread.

##dd 005153cc  l10
%005153cc  0000000d 1bed0e58 00000000 00000000
%005153dc  0001006d 000a000d 12dc5f78 005343ac
%005153ec  0051598c 1beacbe9 2050534d 00000138
%005153fc  4d454d53 1bec690a 0000011c 000028c0
##ln 1bed0e58
0170 1bed0e58 pmwp CODE32 ProcessQueue

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

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

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:


Then start the shell:


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.


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.

Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation