Writing your own PSTAT

By Roger Orr

Introduction
One of the programs provided with OS/2 2.0 is called PSTAT. It provides a simple command line interface and displays some extremely useful information about the programs and resources in use.

Unfortunately the OS/2 API which it uses, DosQProcStatus, is not documented and so there is no official way of writing your own program to obtain the information provided by PSTAT.

However the information provided by PSTAT is useful to enable you to:
 * Map program names understood by you and your users to process IDs understood by the system
 * Obtain lists of active programs, and monitor their status
 * Investigate problems with performance or response time often related to thread priority or unanticipated contention over semaphores
 * Kill programs which are 'invisible' (for example running detached or not in the task list).

Possible solutions to providing the information
So what solutions are possible to get this information?

One solution, and the simplest to do, is to pipe the output of PSTAT and extract the information from this text output. The main drawbacks with this solution are:
 * The method relies on the exact format of the output from PSTAT, which is not guaranteed to remain consistent.
 * You can only obtain information which PSTAT displays and not the full range available.
 * It is inefficient to perform so much text processing.

A second solution is to provide a partial solution - for your own applications only - by writing a shared library of functions (probably packaged as a DLL) which manipulate some known named resource such as a shared segment.

Your program would register its own name and process ID with the DLL, and when the program ends the OS/2 exit list processing can be used to remove the process information. The DLL would provide an API to allow other programs to query the information being maintained.

This method is quite attractive because you can write whatever information is of use to you (for example statistics or configuration information); but the method does suffer the restriction that only your own programs are covered.

A third solution is to use something like BIGBRO (see Pointers Sep/Oct 1991 "Monitoring the System with the OS/2 Debugging Interface"). BigBro as described there used the OS/2 1.x DosPTrace API to run programs under its control - for OS/2 2.0 one would replace this with the new (but similar) DosDebug API.

The method allows items such process ID, program name, modules in use and thread status to be obtained using the OS/2 debugging interface.

This can provide some of the information from PSTAT, and also allows some things which PSTAT cannot do, in particular the ability to affect programs; but is not a general solution to obtaining the PSTAT information since:
 * It requires the system be started with BIGBRO.
 * Use of debuggers is a problem since the debug interface is already in use.
 * There is no way to obtain information about shared memory names and semaphores from the debugging interface.

A further solution is to unravel the internal structures of OS/2 and then write a device driver to obtain this information.

The SNOOP/2 program produced by Tim Snape did this. It does produce a lot of information about the system and is a nice solution provided you have the time, skill and energy to unravel the internal undocumented structures of an operating system. These structures also tend to change between releases, and unfortunately any faults in the device driver may crash the machine.

It also suffers from the problem that the device driver must be installed before you want the information, and so the method cannot be used on a 'standard' OS/2 configuration.

The solution I will investigate in this article is that of decoding the output of DosQProcStatus.

This removes the problems with the methods described above - the data arrives partly processed by OS/2 which makes the program relatively simple, and the API is available on all machines without further software being installed.

The disadvantages of using such an undocumented API are the usual ones with such interfaces:
 * There is no guarantee of support for the API
 * Since it is an undocumented API the decoding is a 'best guess' rather than based on actual knowledge
 * The API is likely to change with future releases

For these reasons I do not usually recommend using undocumented interfaces except where there appears to be no other way to do the job. To my mind DosQProcStatus meets this criterion.

In addition, where possible, the dependence upon the API should be modularised and the rest of your program be presented with your own API to the module which can remain constant between versions of operating system.

Overview of the interface and the sample program
The function is declared (using IBM C Set/2 Version 1.0) as: USHORT APIENTRY16 DosQProcStatus( PVOID pBuffer, USHORT buflen ); The function fills in a user-supplied buffer of the given length. Possible error return codes include: The buffer returned starts with a fixed size header containing pointers to the information of each category. The main categories of data are:
 * ERROR_BUFFER_OVERFLOW (0x6f) if the buffer length is too small
 * ERROR_PROTECTION_VIOLATION (0x73) if the buffer address is not valid.
 * process - eg. process ID, parent ID, thread status
 * module - eg. module name and handle
 * semaphore - eg. semaphore name and use count
 * memory - eg. shared memory name and selector

Each data type is formatted differently - this article will concentrate on the process and module information which is sufficient to provide a list of active programs by name (the most useful information available from PSTAT).

Note that the module information describes several types of module - such as dynamic link modules (DLLs), executable programs (EXEs) and device drivers.

Process information
The starting and ending offsets for the process information are in the header - the ending offset doubles as the starting offset for the semaphore information.

There is one block of information for each process; each block contains a fixed size header, then some variable length lists such as modules and semaphores in use by this process and finally a list of information about each active thread in the process.

This thread information is an array of fixed sized items, one per thread.

Module information
The module list is a simple linked list of module blocks with the first pointer in the header and ending with a NULL pointer.

Each block consists of a fixed sized header followed by a variable length list of modules to which this module refers, and finally the file name of the module (for example C:\OS2\DLL\PMWIN.DLL or C:\OS2\PMSHELL.EXE).

Semaphore information
The semaphore information consists of a fixed size block pointed to by the header and then a singly linked chain of items, one for each semaphore. Each item contains a fixed size header (containing such things as the owning thread ID) followed by the semaphore name.

Memory information
The memory information is a singly linked list of all the named shared segments in the system with the start of the chain being pointed to directly from the header. Each item consists of a fixed size header (which contains items such as the segment's selector value and use count), followed by the segment name.

The code presented in this article does not make use of these last two types of information but I have included the description of the structures for those who are merely curious or else wish to make use of it.

Sample program
As will be apparent from the header file I do not have all the fields sorted out yet - the meaning of several of the fields is completely unknown to me and are named as 'pad' something in the header file. In addition it is possible I have got other fields incomplete or incorrect.

The sample program displays a list of active processes by name. It does this by scanning the process information to obtain information about each process, including the module handle of the process executable. This module handle is then searched for in the list of module information to obtain the name of the executable program.

Note that some programs (notably Dos or Win-OS2 programs) have a module handle of NULL and so neither PSTAT nor the sample program can identify the name of the program.

The sample code is written for IBM C Set/2 Version 1.0, but since the API is a 16bit one the program could be modified to run in 16bit mode with suitable changes to the code - especially the pointer manipulation since the buffer returned contains 'flat' offsets.

The program consists of a header file with definitions of the interface and buffer formats, a source file and a linker definition file. (The linker definition file defines the ordinal number for DosQProcStatus since it does not appear in the standard OS/2 library.)

You may omit the 'not required' structures from the header file if you merely wish to compile the sample program (ie. DQP_SEM, DQP_SEMITEM and DQP_MEM).

Header file: proglist.h Source file: proglist.c Linker definition file: proglist.def Make file: makefile Sample output Process  Parent    Session   Thread   Process ID       ID        ID       Count    Name 0128     0000        1          5    C:\OS2\MDOS\WINOS2\VDMSRVR.EXE 012a     0128        1          2    C:\OS2\PMDDE.EXE 0129     0128        1          1    C:\OS2\APPS\CLIPOS2.EXE 000a     0000       16          8    C:\C6\BIN\QH.EXE 0002     0001        1         20    C:\OS2\PMSHELL.EXE 0127     0002       18          1     (DOS or Win-OS2 program) 002b     0002        5          1    C:\OS2\CMD.EXE 0145     002b        5          1    C:\ROGER\ARTICLES\PROGLIST.EXE 0005     0002       17         11    C:\OS2\PMSHELL.EXE 0003     0002        0          3    C:\OS2\SYSTEM\HARDERR.EXE

Roger Orr 05-Aug-1992