[Next] [Previous]      Chapter 3 
    [Contents]  [Chapter 2: Memory Management] [Chapter 4: File I/O and Extended Attributes ]


Multitasking

The session and task management facilities in OS/2 give the programmer an exceptional opportunity to fully exploit the multitasking features in the operating system. Threads or processes can provide applications with a tremendous performance boost. OS/2 provides a special brand of multitasking, preemptive multitasking, which is different from the multitasking found in either Windows or the Macintosh System 7. Preemptive multitasking is controlled by the operating system. Each process is interrupted when its time to run is over, and the process will never realize it has been interrupted the next time it is running. In other words, OS/2 lets your computer walk and chew gum at the same time. With either the Mac or Windows, your computer takes a step, chews the gum, takes a step. chews the gum. And so on.
The task management of OS/2 is divided into three separate entities: A thread is the only unit to get its own time slice of the CPU. All threads belonging to a process are contained within that process, and each thread has its own stack and registers. There is a systemwide limit of 4,096 threads; however, CONFIG.SYS contains a THREADS parameter that is usually set at a significantly smaller number-256 is the default. The base operating system uses approximately 40 threads, so most applications are limited to 216 threads unless the THREADS parameter is changed. Typically, a thread should have one distinct function: for example, file I/O, asynch communications, or heavy number crunching. Each thread has a thread identifier-a TID. Each thread also has a priority. The higher the priority, the more CPU time slices are given to the thread. A thread is much quicker to create than a process or session and has less system overhead. All threads within a process run in the same virtual address space; therefore, global resources, such as file handles, and global variables are accessible from all threads in the process. Threads are created using DosCreateThread, with the first thread created automatically by the operating system. When a thread is created is is assigned the same priority class as the thread that created it.

A process is a collection of threads and resources that are owned by those threads. Each process occupies a unique address space in memory that cannot be accessed by other processes in the system. Two processes can access  the same area in memory only by using shared memory. A process also contains file handles, semaphores, and other resources. All processes contain at least one thread, the main thread. A process also contains a unique identifier-a PID. A process contains its own set of memory pages that can be swapped in and out as the kernel switches from one process to the other. A process can create other processes; however, these must be of the same session type. For instance, a full-screen process can only create other full-screen processes. The live types of processes are OS/2 Full Screen, OS/2 windowed, DOS Full Screen,.DOS windowed, and Presentation Manager.

A session is similar to a process except a session also contains ownership of the mouse, keyboard, and video. A session can contain either one process or multiple processes. The task list (accessed by Ctrl-Esc) contains a list of all running sessions. When a process or session creases a new session using DosStartSession, the keyboard, screen, and mouse are responsive only to the session in the foreground.
The session chosen as the background can gain control of the three resources only by switching to the foreground.

The Scheduler

The OS/2 Scheduler runs on a round-robin type of disbursement of CPU time. The Scheduler deals only with threads, not processes or sessions. Threads have four different priority levels: time-critical, server class or fixed high, regular, and idle time. The first threads to run are the time-critical threads. All time-critical threads will run until there are no more time-critical threads waiting to be run. After all time-critical threads are finished, the server-class threads are run. After server-class, the regular class of threads are run. After the regular class of threads are run, idle-time threads are run. Within each class of priorities are 32 sublevels. A thread that is not running is called a "blocked" thread.
The OS/2 Scheduler does a lot of monkeying around with thread priorities. Threads are given "boosts" by the scheduler to make OS/2's multitasking smarter. Three types of artificial priority boosts are given to threads: The foreground boost is given to the user interface thread that is in the foreground. This is usually the main thread. The foreground process is the process with which the user is currently interacting. This makes the system respond quickly when the user clicks a mouse button or types in characters at a keyboard. This boost is a full boost in priority. Also, a Presentation Manager thread has a boost applied to it while it is processing a message.
We'll take this opportunity to get up on our soapbox. Do not throw away all the work the operating system does to provide the end user with a crisp response time. Any operation that takes any amount of time should be in its own thread. A well-written. multithreaded program running on a 20 MHz 386SX will be blazingly fast to an end user used to a single-threaded program running on a 486 DX2. Well, maybe that's a little bit of an exaggeration, but you get the idea. Any time you see an hourglass on the screen for more than a second or two, and the user cannot size a window or select a menu item, that program should be put through a serious design review. Okay, off the soapbox, and on to our regularly scheduled programming.
An I/O boost is given after an I/O operation is completed. An I/O boost does not change a thread's priority but will bump it up to level 31 (the highest level) within its own priority class.
A starvation boost is given to a thread in the regular class that has not been able to run. The  MAXWAIT parameter in CONFIG.SYS is used to define how long, in seconds, a thread must nor run before it is given a starvation boost. The default value is 3 seconds.

The time slices for threads that are given a starvation boost or an I/O boost are different from a normal time slice. Because of she tinkering the scheduler does with their priorities, they do not get to run as long as a nonadjusted thread would run. The length of time for the "short" and normal time slices is controlled by the TIMESLICE parameter in CONFIG.SYS. The first value represents the "shots" time slice length; the default amount of time is set to 32 ms. The second value represents the normal time slice length; the default amount of time is set to 65536 ms.
A programmer can refine the way the threads in a program are run in four ways:

APIRET APIENTRY DosSetPriority(
            ULONG scope,
            ULONG ulClass,
            LONG delta,
            ULONG PorTid);


DosSetPriority  has four parameters. The first indicates to what extent the priority is to be changed. The priority can be changed at the process or thread level. The ulClass  parameter indicates at what class to set the priority. The delta parameter indicates at what level within the class to set the priority. The last parameter is the process ID of the process to be affected .A value of 0 indicates the current process. Note that a process can change just the priority of a child process.  DosSetPriority can be called anytime in the threads lifetime. It is used to adjust the class and/or the priority level within that class. DosSetPriority should be used to adjust threads whose tasks need special timing considerations. For instance, a thread handling communications would probably want to run at a server class. A thread that backs up files in the background should be set at idletime priority, so that it would run when no other tasks were running. You can change the priority of threads in another process. but only if they were not changed explicitly from the regular class.

APIRET DosResumeThread(TID tid);
APIRET DosSuspendThread(TID tid);
The only parameter to each of these functions is the thread ID of the thread. DosResumeThread and  DosSuspendThread are used to change a thread's locked stare. DosSuspendThread  will cause a thread to be set to a blocked state. DosResumeThread is used to cause a suspended thread to be put  back in the list of ready-to-run threads.
DosEnterCritSec  is used to suspend all other threads in a process. This function should be used when it is vitally important that the running thread not be interrupted until it is good and ready. DosExitCritSec  will cause all the suspended threads to be put back in a ready-to-run state A program can nest critical sections within critical sections. A counter is incremented by DosEnterCritSec calls and decremented by DosExitCritSec calls. Only when this counter is 0 will the critical section exit. You probably should avoid nesting critical sections unless you absolutely need this functionality. One final note on critical sections: If a thread exits while in a critical section, the critical section automatically ends.
Gotcha!

DosEnterCritSec  can be a very dangerous function. If  for any reason the single thread  running is put in a blocked state and needs some other thread to cause it to be unblocked, your program will go out to lunch and will not return. For example. DosWait...Sem are major no-nos in a critical section, because the required DosPost...Sem calls probably will exist in a thread that will be put in a suspended slate.
Also, be very careful calling a function that resides in a .DLL when inside a critical section. The function may use semaphores to manage resources, and it may be put in a suspended state while waiting for those resources to be freed.

DosSleep  is the most practical function of the group. Using this function you can put a thread in a suspended state until a specified amount of time has passed. DosSleep has only one argument, the amount of time to "sleep". This value is specified in milliseconds. A thread cannot suspend other threads using DosSleep, only itself. When DosSleep  is called with an argument of 0, the thread gives up the rest of its time slice. This does not change the thread's priorities or affect its position in the list of ready-to-run threads.
 

The Subtleties of Creating a Thread

DosCreateThread is used to create a thread. The following code illustrates this:

DosCreateThread (&tidThread,        /*  thread TID      */
                 pfnThreadFunction, /* pointer to fn    */
                 ulThreadParameter, /* parameter passed */
                 ulThreadState,     /* 0 to run, 1 to suspend */
                 ulStackSize );     /*  4096 at a minimum */

The first parameter contains the address of the threads TID, or Thread ID. The next parameter is .a pointer to  the function that the operating system will call when the thread is running.. When using
DosCreateThread , a typical function prototype of a thread function looks something like this:

VOID APIENTRY fnThread( ULONG ulThreadArgs)

Notice the APIENTRY keyword. This is used to indicate that this is a function that will be called by the operating system. The ulThreadArgs is 4 bytes of data, in the form of a ULONG, that are passed as an argument to the thread function. If you need to pass more than one value, you need to create a structure that contains all the values you want to pass. The first bytes of the structure should contain the size of the structure that is being passed. Also, if you use a structure, make sure you pass the address of the structure as the data. The ulThreadState parameter indicates whether the thread is started in a running state (with a value of 0) or in a suspended stare (with a value of 1). If the thread is started suspended, somebody needs  to call DosResumeThread to get the thread going. The last parameter is the stack size. The threads stack is located in memory when the thread is blocked and is loaded into registers when the thread becomes ready to run. In OS/2 2.0, the programmer no longer needs to mess with allocating and freeing the memory for the stack. However, the programmer does need to know the maximum amount of memory that the stack will use. This is the value passed as the last parameter. This memory is not committed until it is absolutely necessary. The thread stack uses guard pages to commit a new page as necessary. Also, you may notice that a thread stack grows downward rather than upward as normal memory grows.

Threads and the C Runtime

Tire C runtime library can cause problems when used within a thread other than the main thread. Because the C runtime uses many internal variables, multiple threads using the C runtime can cause problems unless the runtime library is notified of the other threads. C-Set/++ has provided a separate function, _beginthread, to fix this situation.. This  function should be used to create threads in which you want to use the C library. The parameters for _beginthread are very similar to the parameters for DosCreateThread

 _beginthread ( pfnThreadFunction,
      /* void pointer to thread function */
                pNull,
     /* this is NOP parameter, used for migration */
                ulStackSize, /*  stack size */
                pArgList);   /*  pointer to argument list */
The prototype for a thread function changes a little here. The typical thread function prototype looks something like this:
void fnThread( void  *pArgList);
 
Gotcha!

When using the C Set/++ compiler, make sure you specify the multithreaded option, Gm+. Also, either let the compiler link in the proper library for you, or make sure  you specify DDE4M*.LIB
 

A Thread Example

The following example creates threads  with  different priorities.  Each thread writes its priority to the screen. In this example, we avoided using  _beginthread and printf  but instead used DosCreateThread and DosWrite. This gives us the opportunity to start the threads in a suspended state.
THREADS.C
THREADS.MAK
THREADS.DEF
The first part of the program is the actual creation of the threads. We'll create five almost identical threads. Each thread is started in suspended state by specifying 1 (THREAD_SUSPEND)  as ulThreadFlags. The thread function, MyThread, is assigned to pfnThreadFuncrion. Since the thread function itself is fairly small, the minimum stack size of 4.096 is specified.

The one difference between the five threats is their priority. Each thread priority is passed to MyThread in the ulThreadArgsr variable. An array,ulThreadPrioriries[], holds all the possible thread priority classes.
DosSetPriority  is used actually to change the priority of the threads from regular priority to the respective priority in the ulThreadPrioriries[] array. The first parameter, PRTY_THREAD, specifics that only one thread, not all the threads in the process, will have its priority affected. The second parameter is the priority class to use. The third parameter is the delta of the priority level. Within each class ate 32 levels that can be used to refine a thread's priority even further. Threads at level 31 of a class will execute before threads at level 0 of the same class. This parameter, specifies the change to make to the current level, not the absolute level value itself. Values are from -31 to +31. A value of 0 indicates no change, and this is what we use in this example. The last parameter, tidThreadID[], is the thread ID of the thread whose priority is to be changed.
Once the thread is created and its priority has been changed, DosResumeThread  is called to wake the thread up and have it begin running.
These steps are repeated for all five threads in a FOR loop. DosSleep  is used to delay the main thread from ending for 2 seconds. This gives all the threads a chance to complete.

The Thread Output

Each thread will print out its priority 200 times. Although this example is an elementary program, it will give you some insight into how threads are scheduled. The screen output you see should show the "3" thread (PRTYC_TIMECRITICAL) running first, followed by the "4" thread (PRTYC_FORGROUNDSERVER). The "2" thread (PRTYC_REGULAR) and the "0" thread (PRTYC_NOCHANGE) actually are running at the same priority and should appear somewhat intermingled. A 0 in the priority class means no change from the existing class. The "1" thread PRTYC_IDLETIME) should always run after the other priority threads.

Executing a Program

The function DosExecPgm  is used to execute a child process from within a parent process. A child process is a very special kind of process. Normally all resources are private to each process; however. because of the parent/child relationship, a child can inherit some of the resources owned by the parent. Most handles can be inherited; however, memory cannot, unless it is shared memory. This protects one process (even if it is a child process) from destroying another process.
The following examples uses DosExecPgm to create a new command process session. The command process executes a "dir *.*"
PROG.C
PROG.MAK
PROG.DEF
The first parameter or DosExecPgm  is a buffer that is used to store information if the application being started fails. The size of the buffer is the next parameter.
The third parameter indicates how you wont to the child process to run. A child process can run simultaneously with the parent process (EXEC_ASYNC), or the parent can wait to run until the child has finished(EXEC_SYNC). There are other options, but these are the two most commonly used.
 
Gotcha!

The parameter string conforms to regular C parameter conventions, where argv[0] is the name of the executing program. After the program name, you must insert one null character. Following the null is the regular string of program arguments. These arguments must be terminated by two null characters. This is accomplished easily by manually inserting one null as the end of the argument string and letting the normal C string null termination insert the other.

 

The argument string for this example is:
"CMD.EXE\0 /C dir *.*\0"

CMD.EXE will execute a new command processor session. The "\0" is the first null character. The argument string "/C dir *.*\0" indicates the session will be cloned when it finishes executing the dir *.* command. The "\0" at the end is the first of the last two nulls. The second null is inserted automatically at the end of the string.

The fifth parameter is the environment string to pass to the new program. This is formatted:
variable = text \0 variable = text \0\0

Each environment variable you want to set must be ended with a null character. The end of the string must be terminated with two null characters. A null value in the environment string variable indicates that the child process will inherit its parent's environment.

The next parameter is a RESULTCODES structure. This structure contains two values, a termination code and a result code. The operating system provides a termination code to indicate whether the program ended normally or whether some error, for example, a trap, ended the program abruptly. The result code is what is returned by the program itself, either through DosExitProcess or through return.
The last parameter is the actual name of the program to be executed. A fully qualified pathname is necessary only if the executable file is not found in the current directory or in any of the directories specified in the path.
There are several ways to tell whether a child process has terminated, but the easiest by far is DosCwait. This function either will wait indefinitely until a child process has ended, or will return immediately with an error, ERROR_CHILD_NOT_COMPLETE.

Sessions

A session is a process with its own input/output devices (i.e.. Presentation Manager / non-Presentation Manager output, keyboard, and mouse). There are several different types of sessions:


All are started the same way, using DosStartSession.
 
Gotcha!

There is a little bit of a trick to determine whether to use DosExecPgm or DosStartSession. The difference lies in whether she newly created process is going to perform any input or output. Table 3.1 outlines the guidelines. If you need to determine the type of an application (or .DLL). DosQueryAppType can be used. 

Table 3.1 Starting  Session Guidelines
Parent Type Child Type Child does I/O ? Use
PM PM  - DosExecPgm or DosStartSession
Non-PM PM  -  DosStartSession
PM Non-PM yes DosStartSession
PM Non-PM no DosExecPgm or DosStartSession

The following example program starts a seamless Windows session using DosStartSession

STARTWIN.C
STARTWIN.MAK
STARTWIN.DEF
The  DosStartSession function itself is actually very small. Most of the preparatory work is done by setting up the STARTDATA structure. The structure looks like this:

 Start session data structure.

typedef struct _STARTDATA
{
   USHORT     Length;  /* The length of the data structure, in bytes, including Length itself. */
   USHORT     Related; /* An indicator which specifies whether the session created is related to the calling session. */
   USHORT     FgBg;   /* An indicator which specifies whether the new session should be started in the foreground or background. */
   USHORT     TraceOpt; /* An indicator which specifies whether the program started in the new session should be executed under conditions for tracing. */
   PSZ        PgmTitle; /* Address of an ASCIIZ string that contains the program title. */
   PSZ        PgmName; /* The address of an ASCIIZ string that contains the file specification of the program to be loaded. */
   PBYTE      PgmInputs;/* Either 0 or the address of an ASCIIZ string that contains the input arguments to be passed to the program. */
   PBYTE      TermQ;    /* Either 0 or the address of an ASCIIZ string that contains the file specification of a system queue. */
   PBYTE      Environment;/* The address of an environment string to be passed to the program started in the new session. */
   USHORT     InheritOpt; /* Specifies whether the program started in the new session should inherit the calling program's environment and open file handles. */
   USHORT     SessionType;/* The type of session that should be created for this program. */
   PSZ        IconFile; /*  Either 0 or the address of an ASCIIZ string that contains the file specification of an icon definition. */
   ULONG      PgmHandle; /* Either 0 or the program handle. */
   USHORT     PgmControl;/* An indicator which specifies the initial state for a windowed application. */
   USHORT     InitXPos;  /* The initial x-coordinate, in pels, for the initial session window. */
   USHORT     InitYPos;  /* The initial y-coordinate, in pels, for the initial session window. */
   USHORT     InitXSize; /*  The initial x extent, in pels, for the initial session window. */
   USHORT     InitYSize; /* The initial y extent, in pels, for the initial session window. */
   USHORT     Reserved; /* Reserved; must be zero. */
   PSZ        ObjectBuffer; /* Buffer in which the name of the object that contributed to the failure of DosExecPgm is returned. */
   ULONG      ObjectBuffLen;/* The length, in bytes, of the buffer pointed to by ObjectBuffer. */
 } STARTDATA;

 typedef STARTDATA *PSTARTDATA;

Length  is the length of the structure in bytes.
FgBg  specifies whether the new session will be a child session (field is TRUE) or nit independent session (field is FALSE).
FgSg  defines whether the session is to be sinned in the foreground (field is FALSE) or in the background (field is TRUE).
TraceOpt   specifies whether there is to be any debugging (tracing) of the new session. TRUE indicates debug on; FALSE indicates debug off.
PgmTitle  is the name that the program is to be called. This is not the name of the executable, only the title for any windows or task list. If a NULL is used, the executable name is used for the title.
PgmName  is the fully qualified pathname of the program to load.
PgmInputs  is a pointer to a string of program arguments (see page 23 for argument formatting.)
TermQ  is a pointer to a string that specifies the name of a system queue that will be notified when the session terminates.
Environment  is a pointer to a string of environment variables (see page 2.3 for environment variable formatting.)
InherritOpt   indicates whether the new session will inherit open file handles and an environment from the calling process. TRUE in this field will cause the session to inherit the patent's environment; FALSE will cause the session to inherit the shell's environment.
SessionType  specifies the type of session to start. Possible values are listed in Table 3.2
 

Table 3.2 Descriptions of Session Types
Value  Description
SSF_TYPE_DEFAULT  Uses the program's type as the session type
SSF_TYPE_FULLSCREAN OS/2 full screen
SSF_TYPE_WINDOWABLEVIO  OS/2 window
SSF_TYPE_PM  Presentation Manager program
SSF_TYPE_VDM  DOS full screen
SSF_TYPE_WINDOWEDVDM  DOS window

In addition. Table 3.3 lists the values that are also valid for Windows programs.
 

Table 3.3 Valid Windows Session Types
Value Description
PROG_31_STDSEAMLESSVDM  Windows 3.1 program that will execute in its own windowed
PROG_31_STDSEAMLESSCOMMON Windows 3.1 program that will execute  windowed session.
PROG_31_ENHSEAMLESSVDM Windows 3.1 program that will execute in enhanced compatibility mode in its own windowed session.
PROG_31_ENHSEAMLESSCOMMON Windows 3.1 program that will execute in enhanced compatibility mode in a common windowed session
PROG_31_ENH Windows 3.1 program that will execute in enhanced compatibility mode in a full screen session.
PROG_31_STD Windows 3.1 program that will execute in a full screen session.

IconFile  is a pointer to a fully qualified pathname of an  .ICO file to associate with the new session.
PgmName i s a program handle that Is returned from either WinAddProgram or WinQueryProrgamHandle A 0 can be used if these functions are not used.
PgmControl   specifies the initial attributes for either the OS/2 window or DOS window sessions. The following values can be used:

SSF_CONTROL_VISIBLE
SSF_CONTROL_INVISIBLE
SSF_CONTROL_MAXIMIZE
SSF_CONTROL_MINIMIZE
SSF_CONTROL_NOAUTOCLOSE
5SF_CONTROL_SETPOS
Except for SSF_CONTROL_NOAUTOCLOSE and SSF_CONTROLSETPOS, the values are pretty self-explanatory. SSF_CONTROL_NOOAUTOCLOSE is used only for the OS/2 windowed sessions and will keep the sessions open after the program has completed. The SSF_CONTROL_SETPOS value indicates that the operating system will use the InitXPos, InitYPos, InitXSise. and InitYSize for the size and placement of the windowed sessions.

The second parameter to DosStartSession is the address of a ULONG  that will contain the session ID after the function has completed. The last parameter is the address of a PID (process ID) that will contain the new process's PID after the session has started.


[Next] [Previous]      Chapter 3 
[Contents]  [Chapter 2: Memory Management] [Chapter 4: File I/O and Extended Attributes ]