Writing your own PSTAT

From EDM2
Jump to: navigation, search

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:

  • ERROR_BUFFER_OVERFLOW (0x6f) if the buffer length is too small
  • ERROR_PROTECTION_VIOLATION (0x73) if the buffer address is not valid.

The buffer returned starts with a fixed size header containing pointers to the information of each category. The main categories of data are:

process - e.g. process ID, parent ID, thread status
module - e.g. module name and handle
semaphore - e.g. semaphore name and use count
memory - e.g. 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 16-bit one the program could be modified to run in 16-bit 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

/*****************************************************************************/
/* Function prototype                                                        */
/*****************************************************************************/

USHORT APIENTRY16 DosQProcStatus( PVOID pBuffer, USHORT buflen );

/*****************************************************************************/
/* header format                                                             */
/*****************************************************************************/

typedef struct _dqp_hdr
  {
  long             pad1;
  struct _dqp_proc *pProcess;    /* pointer to 'process' info               */
  struct _dqp_sem  *pSem;        /* pointer to 'semaphore' info             */
  long             pad2;
  struct _dqp_mem  *pMem;        /* pointer to 'memory' info                */
  struct _dqp_mod  *pMod;        /* pointer to 'module' info                */
  } DQP_HDR, *PDQP_HDR;

/*****************************************************************************/
/* Process information                                                       */
/*****************************************************************************/

typedef struct _dqp_proc
  {
  long pad1;
  struct _dqp_thrd *pThread;     /* pointer to thread list                  */
  SHORT pid;                     /* process ID                              */
  SHORT ppid;                    /* parent process ID                       */
  long pad2;
  long pad3;
  long lSession;                 /* screen group                            */
  SHORT hMod;                    /* module handle                           */
  SHORT cThreads;                /* number of threads                       */
  long pad4;
  long pad5;
  short cSemaphore;              /* number of semaphores                    */
  SHORT cModules;                /* number of modules                       */
  SHORT cMemory;                 /* number of shared memory segments        */
  short cUnknown;                /* number of ???                           */
  SHORT *pSemaphore;             /* pointer to semaphores                   */
  SHORT *pMod;                   /* pointer to modules                      */
  SHORT *pMem;                   /* pointer to memory segments              */
  SHORT *pUnknown;               /* pointer to ???                          */
  } DQP_PROC, *PDQP_PROC;

/*****************************************************************************/
/* Thread information (pointed to by process information)                    */
/*****************************************************************************/

typedef struct _dqp_thrd
  {
  long  pad1;
  SHORT tid;                     /* thread ID within process                */
  SHORT realtid;                 /* unique thread ID                        */
  LONG lBlockid;                 /* ID of object blocking on                */
  LONG sPriority;                /* priority class of thread                */
  long pad2;
  long pad3;
  LONG lStatus;                  /* thread status                           */
  } DQP_THRD, *PDQP_THRD;

/*****************************************************************************/
/* Module information                                                        */
/*****************************************************************************/

typedef struct _dqp_mod
  {
  struct _dqp_mod *next;         /* pointer to next module, or NULL         */
  SHORT hmod;                    /* module handle                           */
  short fNewexe;                 /* 1 for OS/2 2.0 specific program         */
  long lMods;                    /* count of dependent modules              */
  long cSegments;                /* number of segments                      */
  long pad1;
  PSZ  pszName;                  /* name of module                          */
  } DQP_MOD, *PDQP_MOD;


/*****************************************************************************/
/* Semaphore information (NOT required for sample program)                   */
/*****************************************************************************/

typedef struct _dqp_sem
  {
  long smunknown1;
  long smunknown2;
  long smunknown3;
  LONG lRootID;                  /* root number for idx field below         */
  } DQP_SEM,
  *PDQP_SEM;

/*****************************************************************************/
/* Item for single semaphore (first one follows DQP_SEM structure)           */
/*****************************************************************************/

typedef struct _dqp_semitem
  {
  struct _dqp_semitem *next;     /* pointer to next semitem, or NULL        */
  SHORT ownerTid;                /* realTid of the owning thread, if any    */
  CHAR flag;                     /* flag byte                               */
  char numref;                   /* number of references to semaphore       */
  long numreq;                   /* number of semaphore request pending     */
  short idx;                     /* index (identifies semaphore)            */
  } DQP_SEMITEM,
  *PDQP_SEMITEM;
                                 /* name follows here                       */

/*****************************************************************************/
/* Memory information (NOT required for sample program)                      */
/*****************************************************************************/

typedef struct _dqp_mem
  {
  struct _dqp_mem *next;         /* Pointer to next mem item, or NULL       */
  USHORT sHandle;                /* memory handle                           */
  SEL    sel;                    /* selector value for memory segment       */
  USHORT sRef;                   /* number of references                    */
  } DQP_MEM,
  *PDQP_MEM;
                                 /* name follows here                       */

Source file: proglist.c

/*****************************************************************************/
/* Include files                                                             */
/*****************************************************************************/

#include        <os2.h>

#include        <stdio.h>
#include        <stdlib.h>

#include        <malloc.h>
#include        <memory.h>
#include        <string.h>

#include        "proglist.h" 


/*****************************************************************************/
/* find_module: find module by number in the module list                     */
/*****************************************************************************/

PDQP_MOD find_module( PDQP_HDR pHdr, SHORT hMod )
  {
  PDQP_MOD pMod = NULL;

  for (pMod = pHdr->pMod; pMod != NULL; pMod = pMod->next)
     if ( pMod->hmod == hMod )
        break;

  return pMod;
  }


/*****************************************************************************/
/* process: dump out formatted data from the buffer                          */
/*****************************************************************************/

USHORT process( PDQP_HDR pHdr )
  {
  PDQP_PROC pProc = NULL;
  PDQP_MOD  pMod = NULL;


  printf( "\n" );
  printf( "  Process   Parent    Session   Thread   Process\n" );
  printf( "    ID        ID        ID       Count    Name\n" );
  printf( "\n" );

  /* Loop for each process */
  for (pProc = pHdr->pProcess; pProc < (PDQP_PROC) (PVOID) pHdr->pSem; )
     {
     printf( "   " );
     printf( "%4.4x      ",  (int) pProc->pid );
     printf( "%4.4x     ",   (int) pProc->ppid );
     printf( "%4i       ",   (int) pProc->lSession );
     printf( "%4i    ",      (int) pProc->cThreads );

     /* Find process name from module handle if we can */
     if ( pProc->hMod == 0 )
        printf( " (DOS or Win-OS2 program)" );
     else
        {
        pMod = find_module( pHdr, pProc->hMod );
        if ( pMod != NULL )
           printf( "%s", pMod->pszName );
        else
           printf( "-- unknown --" );
        }
     printf( "\n" );

     /* The next process begins after our thread information array */
     pProc = (PDQP_PROC) (PVOID) (pProc->pThread + pProc->cThreads);
     }

  return 0;
  } 

/*****************************************************************************/
/* M A I N   P R O G R A M                                                   */
/*****************************************************************************/

int main ( void )
  {
  USHORT rc = 0;
  PDQP_HDR pBuffer = NULL;

  pBuffer = malloc( 0x4000 );
  rc = DosQProcStatus( pBuffer, 0x4000 );
  if ( rc != 0 )
     {
     printf( "Error %u from DosQProcStatus\n", rc );
     }
  else
     rc = process( pBuffer );

  return rc;
  }

Linker definition file: proglist.def

NAME    PROGLIST
DESCRIPTION 'Proglist - display list of active programs'
IMPORTS
DOSQPROCSTATUS = DOSCALLS.154           ; not in os2386.lib

Make file: makefile

PICKY = /Kf+t-                          # catch even minor quirks
LINKOPTS = /nologo /batch /pm:vio

target : proglist.exe

proglist.obj : proglist.c proglist.h
       icc $(PICKY) proglist.c /c

proglist.exe : proglist.obj proglist.def
       link386 $(LINKOPTS) proglist,,,os2386,proglist;

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