[ Next ] [ Previous ] | Chapter 3 |
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 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 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.
|
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.
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.
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
|
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.
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.
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.
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. |
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
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.
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 |