Jump to content

CPGuide - Program Execution Control

From EDM2

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

Control Program Programming Guide and Reference
  1. Introduction to the Control Program
  2. Control Program Functions
  3. Keyboard Functions
  4. Mouse Functions
  5. Video Functions
  6. Data Types
  7. Errors
  8. Debugging
  9. Kernel Debugger Communications Protocol
  10. Device I/O
  11. Dynamic Linking
  12. Error Management
  13. Exception Management
  14. Extended Attributes
  15. File Management
  16. File Names
  17. File Systems
  18. Generic IOCtl Commands
  19. Memory Management
  20. Message Management
  21. National Language Support
  22. Pipes
  23. Program Execution Control
  24. Queues
  25. Semaphores
  26. Timers
  27. Notices
  28. Glossary

Multitasking is the ability of OS/2 to manage the execution of more than one application at a time. A multitasking operating system, such as OS/2 enables users to run many applications simultaneously.

For the programmer, OS/2 supports two types of multitasking. An application can start other programs, in separate processes, that will execute concurrently with the application. These programs can be a new copy of the application, a related program that is designed to work with the application, or an unrelated program. Running multiple processes is the first type of multitasking provided for programmers.

Running multiple threads is the second type of multitasking supported by OS/2. OS/2 enables applications to run multiple threads of execution within the same process; separate activities can be multitasked within the application. An example of multiple-thread multitasking would be for the application to dispatch a separate subroutine to load a file from the disk, and have the subroutine execute at the same time the main program continues to monitor and respond to user input.

This chapter describes processes, threads, and sessions, and the OS/2 functions used to create and manage them. Additionally, there is a section describing CPU scheduling.

The following topics are related to the information in this chapter:

  • Memory
  • Semaphores
  • Queues
  • Pipes
  • Exception handling
  • Debugging

About Program Execution Control-Thread, Processes, and Sessions

To successfully use multitasking-multiple processes and multiple threads-in your programs, you need to understand the difference between a thread, a process, and a session.

A thread is a dispatchable unit of execution that consists of a set of instructions, related CPU register values, and a stack. Each process has at least one thread, called the main thread or thread 1, and can have many threads running simultaneously. The application runs when OS/2 gives control to a thread in the process. The thread is the basic unit of execution scheduling.

A process is the code, data, and other resources-such as file handles, semaphores, pipes, queues, and so on-of an application in memory. OS/2 considers every application it loads to be a process. System resources are allocated on a per-process basis.

A session is one (or more) processes with their own virtual console. (A virtual console is a virtual screen-either a character-based, full screen or a Presentation Manager window-and buffers for keyboard and mouse input.)

OS/2 supports up to 255 concurrent sessions and up to 4095 processes. OS/2 supports a system-wide maximum of 4095 threads, but the number of threads available in a single process will be lower, and will vary, because of resource usage within the process.

Threads

Applications always have at least one thread of execution-thread 1. Using multiple threads of execution, an application can do several things at the same time.

For example, a simple Presentation Manager application consists of a single process with two threads:

  • A user interface thread that listens and responds to user requests, and that queues work for the second thread
  • A processing thread that handles lengthy processing

OS/2 creates the first thread of execution for a process when it starts the executable file. To create another thread of execution, a thread calls DosCreateThread, specifying the address within the program module where the thread begins asynchronous execution. Although a thread can execute any part of the application, including a part being executed by another thread, threads typically are used to execute separate sections of the application. By using several threads, the system can distribute the available CPU time and enable an application to carry out several tasks simultaneously. For example, an application can load a file and prompt the user for input at the same time.

Each thread in a process has a unique stack and register context. Threads shares the resources of the process with the other threads in the process. For example, threads in the same process have access to the memory spaces of other threads within the process. However, threads of one process do not have access to the data spaces of other processes.

Each thread has a priority, that determines the amount of CPU time the thread is allocated. Threads inherit the priority of the thread that creates them. The priority of a thread can be changed by the application; see Changing the Priority of a Thread for details.

An application can use DosSuspendThread and DosResumeThread to suspend and resume the execution of a given thread. When an application suspends a thread, the thread remains suspended until the application calls DosResumeThread.

When an application has more than one thread, it might be necessary to ensure that one thread is finished executing before another thread uses a shared resource, such as a disk file. DosWaitThread causes the application to wait until a specific thread has finished. DosWaitThread can also be used to determine the state of a thread; the function can return immediately with an error value if the identified thread is still running.

A thread ends when it calls DosExit.

Processes

An OS/2 application that has been loaded into memory and prepared for execution is called a process. As mentioned earlier, a process consists of the code, data, and other resources (for example, open file handles) that belong to the application. Each process has at least one thread, called the main thread or thread 1.

When OS/2 runs an application, it confirms that the process code and data are in memory and that the main thread's registers and stack are set before starting the application. Each application has access to all resources of the computer, such as memory, disk drives, screen, keyboard, and the CPU itself. The system carefully manages these resources so that applications can access them without conflict.

A process can have more than one thread. OS/2 creates the first thread of execution for a process when it starts the executable file. More threads can be created with DosCreateThread. Each thread runs independently, with its own stack and register values. Unless the application changes a thread's priority, each thread gets a slice of the CPU in a round-robin strategy. All the threads in a process share the application's globally defined variables and other resources (open file handles, and so on).

A process or thread ends when it calls DosExit. OS/2 automatically closes any files or other resources left open by the process when the process ends. When a thread ends, however, any open resources remain open until another thread closes them or the process ends. A process can direct OS/2 to carry out other actions when the process ends, by using DosExitList to create a list of termination functions. OS/2 calls the termination functions, in the order given, when the process is about to end. If the thread has registered any exception handlers, the exception handlers will also be called before the thread ends.

Creating Processes

An application can load and execute other applications by using DosExecPgm. The new application, once loaded, is called a child process. The process that starts the new application is called the parent process.

A child process is like any other process. It has its own code, data, and threads. The child process inherits the resources-such as file handles, pipes, and queues-that belong to the parent process at the time the child process is created, although not necessarily with the same access rights. The parent process can place restrictions on the access of the child process to these resources:

  • Files are inherited except for files that were opened with no inheritance indicated.
  • Pipes are inherited.

Assuming that the parent process gives the child process appropriate access rights, the child process can use the inherited resources without preparing them. For example, if the parent process opens a file for reading and then starts a child process, the child process can read from the file immediately; it does not have to open the file. However, once the child process is created additional resources that the parent process creates are not available to the child process. Similarly, resources the child process creates are not available to the parent process.

The parent process also has control over the meanings of standard input, output, and error for the child process. For example, the parent can write a series of records to a file, open the file as standard input, open a listing file as standard output, and then execute a sort program that takes its input from standard input and writes to standard output.

Note that memory is not included in the list of things that a child process can inherit from its parent process. The child process is created with its own process address space that is separate and distinct from the memory of the parent process. A new linear address space is built for the new process. The only way for a parent process and a child process to access the same memory is to set up a shared memory area.

The executable file of the child process can be started either synchronously or asynchronously to the parent process. If the parent process starts the child process running synchronously, the parent process is suspended and waits until the child process ends before continuing. A child process running asynchronously executes independently of the parent process (that is, both run at the same time). The parent process specifies how the child process is to run by setting a parameter in the call to DosExecPgm.

The OS/2 command processor, CMD.EXE, starts most child processes synchronously. The parent process waits for each child process to end before it prompts the user for the next command. The command processor also enables the user to start asynchronous applications by using the DETACH command. When the user detaches an application, the command processor starts the application asynchronously, in the background, and continues to prompt for input.

Process Termination

A parent process can use DosWaitChild to determine the termination status of a child process that is running independently. The parent process can have one of its threads call DosWaitChild to wait for completion of the child process while other threads of the parent continue processing.

If the child has started another process, DosWaitChild waits for the completion of any grandchild processes before returning, but does not report their status. If the specified child process has multiple threads, DosWaitChild returns the result code of the last DosExit request.

If there are no child processes, either active or ended with a return code, DosWaitChild returns with an error code. If no child processes have ended, DosWaitChild can optionally wait until one ends before returning to the parent.

Process Exit Lists

Because any process can end any other process for which it has a process identifier, applications might lose information if a process ends the application before it can save its work. To prevent this loss of data, you can create a list of functions to clean up data and files before OS/2 ends the process. This list is called an exit list. OS/2 maintains an exit list for each process and calls these functions whenever the application is ended, whether by another process or by itself.

You call DosExitList to add to the exit list a routine that is to be given control when a process is ended (or finishes its execution). Multiple routines can be added to the list. When the process is ending, OS/2 transfers control to each address on the list.

Exit-list functions perform clean-up operations on resources. For example, an exit-list function can be used in a dynamic link library module to free resources or clear flags and semaphores when a client program has ended.

Multitasking with Threads and Multitasking with Processes

The creation and termination of a process is relatively slow compared to the creation and termination of a thread, and is more costly in terms of system resources.

For example, sharing data and resources between processes requires shared memory and the mechanisms of interprocess communication; threads, on the other hand, have full access to the memory and other resources that belong to the process the threads are part of and can be coordinated using semaphores. For these reasons, thread-to-thread task context switches are faster than process-to-process context switches.

Because OS/2 can create and execute threads more quickly than processes, the preferred multitasking method for applications is to distribute tasks among threads in the same process instead of among processes.

Sessions

OS/2 uses sessions to help the user move from one application to the next without disrupting the screen display of an application.

A session consists of at least one process and a virtual console-buffers for keyboard and mouse input and either a character-based, full screen or a Presentation Manager window. When the system creates a session, the process in the session displays output in the screen or window. The user can view the output and supply input by moving to the session. The user moves to a session by pressing the Alt+Esc key combination, by selecting the title of the session from the Window List, or, for windowed sessions, by clicking the mouse in the session window.

A child session is under the control of the session that creates it. The session that starts the child session is called the parent session. Any process in the parent session can exercise control over a child session.

An unrelated session is not under the control of the session that started it. The process that creates the unrelated session cannot select it, make it nonselectable, bind it, or end it, nor can any other session. DosStartSession does not even return a session identifier when an unrelated session is started. Unrelated sessions are controlled entirely by the user. When OS/2 starts new sessions, it starts them as unrelated sessions.

Creating Sessions

A process creates a new session by using DosStartSession. DosStartSession enables an application to start another session and to specify the name of the application to be started in that session.

DosStartSession also specifies which of the five session types is to be started:

  • Full screen, protect mode
  • Text windowed, protect mode
  • Presentation Manager (PM)
  • Full screen DOS Session
  • Windowed DOS Session

Protect mode applications run in full screen and text windowed sessions, PM and AVIO applications run in PM windows, and DOS applications run in full screen DOS Sessions and windowed DOS Sessions.

OS/2 applications running in any of the OS/2 session types-full screen, text windowed, and PM-can start sessions of any other type, including DOS Sessions. DOS Session applications cannot start other sessions.

An application can start another process in a separate session when the application will not manage any I/O done by the process. For example, an application that starts an unrelated application could start it in a separate session.

A session can be started as a related or an unrelated session. A related session is called a child session, and the session starting the child session is called the parent session. An application can control its child sessions by using the session identifier returned by DosStartSession with the DosSetSession, DosSelectSession, and DosStopSession. If an application starts an unrelated session, the new session cannot be controlled by the application. The Related field in the STARTDATA structure specifies whether the new session is related to the session calling DosStartSession.

After a process has started a child session, no other process in its session can start a child session until all dependent sessions started by this process have ended.

When a session is created, the title specified in the function call (or the application name if no title is specified) is added to the Window List.

DosStartSession can be used to start either a foreground or a background session, but a new session can be started in the foreground only when the caller's session, or one of the caller's descendant sessions, is currently executing in the foreground. The foreground session for windowed applications is the session of the application that owns the window focus.

Termination Queues

The parent session must create a termination queue prior to specifying the queue name in a call to DosStartSession. OS/2 will continue to notify the parent session through the specified queue as long as the session calling DosStartSession remains a parent session. In other words, when all the child sessions for a particular parent session end, the termination queue is closed by OS/2. An existing queue name must be specified on the next DosStartSession call if the caller wants to continue receiving termination notification messages.

OS/2 writes a data element to the specified queue when any child session ends. The queue is posted regardless or who ends the child session (the child, the parent, or the user) and whether the termination is normal or abnormal.

A parent session calls DosReadQueue to receive notification when a child session has ended. The word that contains the request parameter, returned by DosReadQueue, will be 0. The data element has the following format:

Termination Queue Element Format
Size Description
WORD Session ID of the child session that ended
WORD Result code

The process that originally called the DosStartSession request should call DosReadQueue, with the NOWAIT parameter set to 0. This is the only process that has addressability to the notification data element. After reading and processing the data element, the caller must free the segment containing the data element by calling DosFreeMem.

When a child session ends, the result code returned in the termination queue's data element is the result code of the program specified in the DosStartSession call, providing either one of the following is true:

  • The program was run directly, with no intermediate secondary command processor
  • The program is run indirectly through a secondary command processor, and the /C parameter is specified

When a child session is running in the foreground at the time it ends, the parent session becomes the new foreground session. When a parent session ends, any child sessions are ended. When an unrelated session ends in the foreground, OS/2 selects the next foreground session.

Child Session Control

A session can be either a child session or an unrelated session. A child session is under the control of the processes in the session that creates it (the parent session). A process can select, set, or stop a child session by using DosSelectSession, DosSetSession, or DosStopSession, respectively. DosStartSession returns a unique session identifier for the child session for use in these functions.

A session can run in either the foreground or background. A process can create a foreground session only if the creating process or one of its descendant sessions is executing in the current foreground session. A process can move a child session to the foreground by selecting the child session using the session identifier and calling DosSelectSession. A process can make a child session nonselectable by using DosSetSession to change the SelectInd field in the STATUSDATA structure. This prevents the user from selecting the session from the Window List but does not prevent a process from selecting the child session by using DosSelectSession.

A process can bind a child session to its own session by using DosSetSession. Binding a session causes that session to move to the foreground whenever the user selects the parent session from the Window List.

A parent session can use a session identifier with the DosSetSession function only if the parent session created the child session associated with that identifier. It cannot use identifiers for child sessions created by other parent processes. This is true for all session management functions.

Although a child session is related to the session that started it, the processes in the child and original sessions are not related. This means that even though DosStartSession supplies the process identifier of the process in the child session, the process identifier cannot be used with OS/2 functions such as DosSetPriority.

Child Session Termination

A parent session can stop a child session by using DosStopSession. Stopping the child session ends the processes in that session. It also stops any sessions related to the child session. If a child session is in the foreground when it is stopped, the parent session becomes the foreground session. DosStopSession breaks any bond that exists between the parent session and the specified child session.

A process running in the session specified in the call to DosStopSession can ignore the request to end. If this happens, DosStopSession still returns 0 (indicating success). The only way to be certain that the child session has ended is to wait for notification through the termination queue specified in the call to DosStartSession. OS/2 writes a data element into the specified queue when the child session ends. The process in the parent session must call DosReadQueue to retrieve this data element, which contains the session identifier for the child session and the return value for the process in the child session. Only the process that created the child session can read the data element.

About CPU Scheduling

OS/2 performs prioritized, preemptive, multitasking. Prioritized means that OS/2 does not divide CPU time equally among all threads. All programs do not get equal access to the CPU. A prioritizing, time-slicing strategy is used to allocate access to the CPU among competing threads. Each thread has a priority and OS/2 runs the highest priority thread that is ready to run. Programs with higher priorities (a real-time robotic application, for example), are given access to the CPU before programs with lower priorities. If a thread with a higher priority than the currently running thread becomes ready to run, the current thread is stopped immediately, or preempted, and the higher priority thread is given the CPU. The lower priority thread does not get to complete its time slice. Threads of equal priority are given CPU time in a round-robin manner.

Preemptive means that the multitasking activity needs no cooperation from the executing programs. OS/2 maintains control over executing programs, and stops, or preempts, them when their time slice with the CPU is over or when a higher priority program is ready to run.

CPU scheduling is based on four priority classes-Time Critical, Fixed-High, Regular, and Idle-Time. Each class has 32 levels of execution ordering. Scheduling parameters are user-selectable at the time the system is started or can be varied dynamically based on system load.

Depending on a thread's priority class and level, OS/2 periodically gives each thread in each process a small slice of CPU time. Threads with higher priorities always run before threads having lower priorities. A thread runs until its time is up or until a thread with a higher priority is ready to run. At that time, OS/2 preempts the thread and starts another thread. Threads can also voluntarily relinquish the CPU (for example, by calling DosSleep).

The amount of time in each time slice is defined by the TIMESLICE command in the CONFIG.SYS file. The TIMESLICE command can be used by the user to customize the size of the time slices that a thread gets. The default is for OS/2 to dynamically vary the size of the time slice based on the activity of the thread and the overall system load.

When a thread is created (using DosCreateThread), it inherits the priority of the thread that started it. DosSetPriority enables threads to change their priority classes and levels in response to changes in their execution environments. DosSetPriority enables a thread to change its own priority, or the priority of any thread within its process. DosSetPriority also enables changing priorities for the entire process and for descendant processes. Within each class, the priority level of a thread can vary because of a DosSetPriorty request or, if dynamic priority variation is being used, because of action taken by OS/2.

Priority Classes

OS/2 uses four priority classes to determine when a thread receives a time slice:

Priority Classes
Priority Description
Time-critical Highest priority. For use when response time is critical.
Fixed-high Used by threads that provide service to other threads. This priority class is to be used when it is desirable that the thread not be too sensitive to the foreground/background boost provided by dynamic priority variation. It is meant for programs that need to execute before regular programs, but without the immediate response time requirement called for by time-critical threads.
Regular Default priority. Most threads belong in this class.
Idle-time Lowest priority. This priority only gets CPU time when there is no other work to do.

A time-critical thread always receives a time slice before a fixed-high thread, a fixed-high thread always receives a time slice before a regular thread, and a regular thread always receives a time slice before an idle-time thread.

Time-Critical Threads

Time-critical threads have the highest priority class and execute before any fixed-high, regular, or idle-time threads.

The time-critical class is for threads that must react to events outside the system. For example, in a communications application, a thread responsible for reading data from the communications device needs enough time to read all incoming data. Because more than a regular time slice might be needed, a time-critical classification ensures that the thread gets all the time required.

Time-critical threads have a static priority that is not varied by OS/2. They are scheduled among themselves in priority level order, with round-robin scheduling of threads of equal priority.

Time-critical threads must be executed quickly, then free the CPU for other work until another time-critical event occurs. This is important to maintain good interactive responsiveness to the user and enable communications and other time critical applications to run concurrently. The time-critical activity should, when possible, be in a thread separate from the rest of the application, to isolate and minimize the time spent processing at the time-critical level. A good rule of thumb is that a time-critical thread should consist of no more than about 20,000 assembly language instructions.

Fixed-High Threads

A fixed-high thread has a priority class that is lower than time-critical but executes before any regular or idle-time threads. This class of threads should be used to provide service for other threads where it is desirable that the thread not be too sensitive to the foreground/background boost provided by dynamic priority variation. A messaging thread, would be a good example of this type of thread.

OS/2 varies the priority of a fixed-high thread around a base value according to the activity of the thread and the system at any point in time. The base value can be set by the thread itself.

Regular Threads

A regular thread is the class that the majority of threads fall into. No explicit action is necessary by the application to run at this priority, it is the default.

OS/2 varies the priority level of a regular thread around a base value according to the activity of the thread and the system at any point in time. The base value can be set by the thread itself.

Idle-Time Threads

An idle-time thread is one with very low priority that executes only when there are no regular, fixed-high, or time-critical threads to execute. Idle-time threads get CPU time only when there is no other work to do. The idle-time class is for threads that need very little CPU time.

Idle-time threads have a static priority that is not varied by OS/2.

Priority Levels

Within each class, OS/2 maintains a priority level for a thread. For each of the four priority classes, there are 32 priority levels-0 to 31. A thread with priority level 31 always receives a time slice before a thread with priority level 30, and so on.

If two or more threads have the same priority level, OS/2 distributes the CPU time equally by using a round-robin scheme; that is, OS/2 gives a time slice to first one, then another, and so on, and then goes back to the first. A thread can use DosSetPriority to change its own priority or the priority of any other thread within its process.

Although an application can set the priority level of a thread at any time, only applications that use more than one thread or process should do so. The best use of priority is to speed up threads or processes on which other threads or processes depend. For example, an application might temporarily raise the priority of a thread loading a file if another thread is waiting for that file to be loaded. Because the priority of a thread is relative to all other threads in the system, raising the priority of the threads in an application merely to get the extra CPU time adversely affects the overall operation of the system.

There are other ways to affect the amount of CPU time a thread is given. A thread can define a critical section of code by using DosEnterCritSec and DosExitCritSec. While inside the critical section of code, a thread cannot be preempted by any other thread within its process (threads in other processes are still given their time slices). Using critical sections enables threads to get more CPU time, while not unduly affecting the rest of the system.

The priority class and priority level are set using DosSetPriority. The priority class is changed by passing the new priority class to the function. The priority level, however, is changed by passing a value, called the priority-delta, that is added to the existing priority level to produce the new priority level; changes to the priority level are relative to the current priority level. Specifying a positive priority-delta increases the priority level, enabling the thread to obtain more CPU time than it normally would. A negative priority-delta decreases the priority level, giving the thread less CPU time than it would normally receive. The value is restricted to the valid range, based upon the current priority class of the process.

If you change the priority level without changing the priority class, the priority-delta is relative to the current priority level. However, if you change the priority class at the same time that you change the priority level, the priority-delta value is relative to 0. Whenever DosSetPriority is called with a class specification, but no value is specified for priority-delta, the base priority level defaults to 0.

The process identifier parameter of DosSetPriority specifies which process is affected by the call. A process can change the priority of itself, of any process that is a descendant of itself, or of one of its threads.

A thread can change the priority of any thread within its current process. When a thread changes the priority of threads in a descendant process, however, only those threads running at the default priority will be changed. You cannot change the priority of a thread in a child process that has already changed its priority from the default.

The initial thread of execution for an application is given a regular class priority that varies by the system. When a thread is created, it is initially dispatched in the same class and priority as the thread that started it. A child process inherits the priority of the thread in the parent process that creates it.

Priority Guidelines

Within the two most common priority classes-time-critical and regular-there are certain broad guidelines recommended for choosing the priority level for a program.

TIME-CRITICAL CLASS
The guidelines for level within the time critical class are set to maximize the number of different applications that can successfully multitask in an OS/2 system. The guidelines are described in the following table.
Recommended Priority Levels for Time-Critical Threads
Activity Range of Recommended Priority Levels Justification/Comments
Robotics/Real time process control 20-31 OS/2 systems can be used on manufacturing lines to control equipment or in other real time process control applications. In this case a slow response from the PC could cause expensive damage to equipment or even human injury. Therefore the highest priority levels should be reserved for these applications.
Communications 10-19 In communications, the inability to get the processor could cause a loss of data or communications sessions. Therefore this class of applications is next highest.
Other 0-09 Other threads might need to preempt the foreground in special cases (for example, Control-Break). These should be set below the other 2 classes.

In general, application performance is not a good reason to make a thread time critical.

REGULAR CLASS
In cases where explicit priority levels are set, they should follow the guidelines listed below.
Recommended Priority Levels for Regular Priority Threads
Activity Range of Recommended Priority Level Justification
Communications 26-31 Communications should take priority over other background processing to increase overlap with transmission and processing on the partner PC or host. This gives the best system performance.
Other 0-25. If an application has multiple threads it might be necessary to set them to several different priorities to optimize the order in which they run. A range of priority levels is provided to facilitate this. (The default priority is regular class, level = 0.)

Dynamic Priority Alteration

OS/2 can be configured to dynamically alter the priority of a process. The PRIORITY statement in CONFIG.SYS can be set to either ABSOLUTE or DYNAMIC. If PRIORITY specifies the ABSOLUTE option, all processes receive CPU time based on the priority established by calls to DosSetPriority. If the PRIORITY command in the CONFIG.SYS file specifies the DYNAMIC option, OS/2 adjusts process priorities based on system load and process activity, and on whether the process is in the foreground. DYNAMIC is the default setting; if the PRIORITY command is not specified, the system uses DYNAMIC priority. DYNAMIC is designed to gives the best overall system performance under most conditions.

When dynamic priority variation is enabled, OS/2 grants higher priority to the foreground process than to background processes. System load and process activity are also taken into consideration. The priority of the process consists of a computed priority value that is based upon the display status of the process (foreground or background), and its recent I/O and CPU time usage history. When dynamic priority variation is enabled, I/O priority boosts are generated for keyboard input, window, foreground, processor starvation, device I/O, and DOS Session interrupts. This ensures that the foreground process-the process most likely to be in use-receives enough CPU time to provide quick response to user input.

There are times when dynamic priority variation can interfere with a multi-threaded application's execution. For example, if you are doing a lot of keyboard input on a thread, its priority will get boosted and other threads may not get enough CPU time. A communication thread is an example of a time sensitive background thread which would be one case. The solution is to either set PRIORITY = ABSOLUTE or to call DosSetPriority on a regular basis to keep the threads priority at the desired level.

Altering the Size of the Time Slice

The TIMESLICE command in CONFIG.SYS sets the minimum and maximum amount of processor time allocated to processes and programs for both OS/2 and DOS sessions.

The first parameter selects the minimum TIMESLICE value in milliseconds. This value is the minimum amount of time a thread is to be processed before yielding the processor to a thread of the same priority level. This value must be an integer greater than or equal to 32.

The second parameter selects the maximum TIMESLICE value in milliseconds. The value of this parameter is the maximum amount of time a thread can be processed before yielding processor time. This value must be an integer greater than or equal to the minimum value, and less than 65536.

The default is dynamic time slicing, which varies the size of the time slice depending on system load and paging activity. Dynamic time slicing is designed to give the best performance in most situations.

Using Processes

An OS/2 application that has been loaded into memory and prepared for execution is called a process. A process is the code, data, and other resources of the application, such as the open file handles, semaphores, pipes, queues and so on. OS/2 considers every application it loads to be a process.

Each process has at least one thread, called the main thread or thread 1. The application runs when the system scheduler gives control to a thread in the process. For more on thread management, see Using Threads.

Note
In the example code fragments that follow, error checking was left out to conserve space. Applications should always check the return code that the functions return. Control Program functions return an APIRET value. A return code of 0 indicates success. If a non-zero value is returned, an error occurred.


Starting a Child Process

You start a process by calling DosExecPgm. The process you start is a child of the calling, or parent, process and inherits many of the resources owned by the parent process, such as file handles.

DosExecPgm creates a process environment from an executable file. The target program is loaded into storage, and it begins execution.

The following code fragment starts an application named ABC:


   #define INCL_DOSPROCESS       /* Process and thread values */
   #include <os2.h>

   CHAR szFailName[CCHMAXPATH];
   RESULTCODES resc;
   DosExecPgm(szFailName,           /* Object-name buffer  */
              sizeof(szFailName),   /* Length of buffer    */
              EXEC_SYNC,            /* Sync flag           */
              (PSZ) NULL,           /* Argument string     */
              (PSZ) NULL,           /* Environment string  */
              &resc,                /* Address for result  */
              "ABC.EXE");           /* Name of application */


In this example, ABC runs synchronously (as specified by EXEC_SYNC). This means the parent process temporarily stops while the child process runs. The parent process does not continue until the child process ends.

Starting an Asynchronous Child Process

To start a child process and enable it to run asynchronously (that is, without suspending the parent process until the child process ends), you use the EXEC_ASYNC constant in a call to DosExecPgm. If you start a process in this way, the function copies the process identifier of the child process to the codeTerminate field of the RESULTCODES structure that is returned by DosExecPgm. You can use this process identifier to check the progress of the child process or to end the process.

You can also run a child process asynchronously by using DosExecPgm with the EXEC_ASYNCRESULT constant. In addition to causing DosExecPgm to return to the parent process immediately, this constant also directs OS/2 to save a copy of the termination status when the child process ends. This status specifies the reason the child process stopped. The parent process can retrieve the termination status by using DosWaitChild.

The following code fragment starts the program SIMPLE.EXE, and then waits for it to finish. It then prints the termination code and the return code.

    #define INCL_DOSPROCESS       /* Process and thread values */
    #include <os2.h>
    #include <stdio.h>

    #define START_PROGRAM "SIMPLE.EXE"

    CHAR         szLoadError[100];
    PSZ          pszArgs;
    PSZ          pszEnvs;
    RESULTCODES  rcReturnCodes;
    APIRET       ulrc;

    ulrc = DosExecPgm(szLoadError,         /* Object name buffer           */
                      sizeof(szLoadError), /* Length of object name buffer */
                      EXEC_SYNC,           /* Asynchronous/Trace flags     */
                      pszArgs,             /* Argument string              */
                      pszEnvs,             /* Environment string           */
                      &rcReturnCodes,      /* Termination codes            */
                      START_PROGRAM);      /* Program file name            */

    printf("Termination Code %d  Return Code %d \n",
           rcReturnCodes.codeTerminate,
           rcReturnCodes.codeResult);

/*----------------SIMPLE.EXE------------------*/

    #define INCL_DOSPROCESS       /* Process and thread values */
    #include <os2.h>
    #include <stdio.h>

    #define RETURN_CODE 0

    main()
    {
        printf("Hello!\n");
        DosExit(EXIT_PROCESS,     /* End the thread or process */
                RETURN_CODE);     /* Result code               */
    }

Starting a Background Process

You can start a child process in the background by specifying the EXEC_BACKGROUND constant in DosExecPgm. A background process runs independently of the parent process and is called a detached process. A detached process should not require any input from the keyboard or output to the video screen, but it can use interprocess communication, such as pipes, queues, and shared memory.

The following code fragment starts the program BATCH.EXE in the background.

    #define INCL_DOSPROCESS       /* Process and thread values */
    #include <os2.h>
    #include <stdio.h>

    #define START_PROGRAM "BATCH.EXE"

    CHAR         szLoadError[100];
    PSZ          pszArgs;
    PSZ          pszEnvs;
    RESULTCODES  rcReturnCodes;
    APIRET       ulrc;

    ulrc = DosExecPgm(szLoadError,         /* Object name buffer           */
                      sizeof(szLoadError), /* Length of object name buffer */
                      EXEC_BACKGROUND,     /* Asynchronous/Trace flags     */
                      pszArgs,             /* Argument string              */
                      pszEnvs,             /* Environment string           */
                      &rcReturnCodes,      /* Termination codes            */
                      START_PROGRAM);      /* Program file name            */

    if (ulrc != 0) {
        printf("DosExecPgm error: return code = %ld",
               ulrc);
        return;
    }


Setting the Command Line and Environment for a Child Process

When you start a process, it inherits many of the resources of the parent. This includes file handles, such as the standard input and standard output files. A child process also inherits the resources of the screen group, such as the mouse and video modes, and the environment variables of the parent process.

The call to DosExecPgm determines the command line and environment that the child process receives. The fourth and fifth parameters of the function are pointers to the command line and the environment, respectively. If these pointers are NULL, the child process receives nothing for a command line and only an exact duplicate of the environment of the parent process. The parent process can modify this information by creating a string (ending with two NULL characters) and passing the address of the string to the function. The command line string must include the name of the application, followed by a NULL character, and the command line arguments, followed by two NULL characters. Any number of arguments can be passed to the child process, as long as the argument string ends with two NULL characters.


The following code fragment passes to the child process the string "test -option1 -option2" as its command line:

    #define INCL_DOSPROCESS    /* Process and thread values */
    #include <os2.h>

    RESULTCODES resc;
    CHAR szFailName[CCHMAXPATH];

    CHAR szCommandLine[] = "test\0-option1 -option2\0";

    DosExecPgm(szFailName,          /* Object-name buffer  */
               sizeof(szFailName),  /* Length of buffer    */
               EXEC_SYNC,           /* Sync flag           */
               szCommandLine,       /* Argument string     */
               (PSZ) NULL,          /* Environment string  */
               &resc,               /* Address of result   */
               "test.exe");         /* Name of application */


Changing the Priority of a Process

Changing the priority of a process is simply a matter of changing the priority of every thread executing in the process. For the details see the section on changing thread priorities, Changing the Priority of a Thread.

Obtaining Information about Child Processes

OS/2 creates and maintains a process information block for every process. An application can use DosGetInfoBlocks to access the process information block. This function returns a pointer to a PIB data structure, which contains the information from the process information block.

The following code fragment returns the address of the process information block of the current process. The calling thread can subsequently browse either the PIB block.

    #define INCL_DOSPROCESS     /* Process and thread values */
    #include <os2.h>

    PTIB     ptib;      /* Address of pointer to thread information block  */
    PPIB     ppib;      /* Address of pointer to process information block */
    APIRET   rc;        /* Return code                                     */

    rc = DosGetInfoBlocks(&ptib, &ppib);


DosGetInfoBlocks also returns the address of the thread information block of the current thread.

Waiting for a Child Process to End

You can synchronize the execution of a process with the execution of one of its child processes by calling DosWaitChild. DosWaitChild does not return until the specified child process ends. This can be useful, for example, if the parent process needs to ensure that the child process has completed its task before the parent process continues with its own task.

In the following code fragment, the parent process starts a child process and then waits for the child process to finish:


    #define INCL_DOSPROCESS     /* Process and thread values */
    #include <os2.h>

    RESULTCODES resc;
    PID pidEnded;
    CHAR szFailName[CCHMAXPATH];

    CHAR szCommandLine[] = "APP\0test\0";

    DosExecPgm(szFailName,              /* Failed-name buffer     */
               sizeof(szFailName),      /* Length of buffer       */
               EXEC_ASYNC,              /* Sync flag              */
               szCommandLine,           /* Argument string        */
               (PSZ) NULL,              /* Environment string     */
               &resc,                   /* Address of result      */
               "APP.EXE");              /* Name of application    */

    DosWaitChild(DCWA_PROCESS,          /* Only the process       */
                 DCWW_WAIT,             /* Waits until it is done */
                 &resc,                 /* Puts the result here   */
                 &pidEnded,             /* PID of ended process   */
                 resc.codeTerminate);   /* Child to wait for      */

You can cause a process to wait for all its child processes to end by using the DCWA_PROCESSTREE constant in the call to DosWaitChild.

Ending the Current Process

You end the current process by calling DosExit. When you exit, the system stops the process and frees any existing resources the process owns.


In the following code fragment, DosExit is used to end the process if the given file does not exist:

    #define INCL_DOSPROCESS     /* Process and thread values */
    #include <os2.h>

    #define HF_STDERR 2         /* Standard error handle     */

    HFILE   hf;
    ULONG   ulAction, ulWritten;
    APIRET  rc;

    rc = DosOpen("SAMPLE.TXT",
                 &hf,
                 &ulAction,
                 0,
                 FILE_NORMAL,
                 FILE_OPEN,
                 OPEN_ACCESS_WRITEONLY |
                 OPEN_SHARE_DENYWRITE,
                 (PEAOP2) NULL);

    if (rc) {
        DosWrite(HF_STDERR,
                 "Cannot open file\r\n",
                 18,
                 &ulWritten);

        DosExit(EXIT_PROCESS,
                rc);
    }

EXIT_PROCESS directs DosExit to end all the threads in a process including the calling thread, thus ending the process. DosExit includes an error value that is returned to the parent process through the RESULTCODES structure specified in the DosExecPgm call that started the process. If you started the application from the command line, the command processor, CMD.EXE, makes this value available through the ERRORLEVEL variable. If another process started the application, that process can call DosWaitChild to determine the error value.

If you want to exit only from a given thread, you can call DosExit with the EXIT_THREAD constant. This will end only the calling thread; other threads in the process are not affected. If the thread you end is the last thread in the process, the process also ends. If the thread consists of a function, the thread ends when the function returns.

Terminating a Process

A process can end the execution of a descendant process by calling DosKillProcess. This causes OS/2 to send a XCPT_SIGNAL_KILLPROC exception to the target process. The child processes of the target process can also be ended.

The following code fragment ends the specified process and all child processes belonging to that process:


   #define INCL_DOSPROCESS       /* Process and thread values */
   #include <os2.h>
   PID pidProcess;
   DosKillProcess(DKP_PROCESSTREE,
                  pidProcess);


In this example, the pidProcess parameter specifies which descendant process to end. The process identifier is returned by DosExecPgm in the codeTerminate field of the RESULTCODES structure when you start the child process.

The parameter DKP_PROCESSTREE in the example indicates that the specified process, pidProcess, and all of its descendant processes are to be ended.

If you specify DKP_PROCESS in a call to DosKillProcess, only the specified process is ended. Its child processes, if any, continue to run.

The process to be ended must either be the current process, or it must have been directly created by the current process with DosExecPgm for asynchronous execution. That is, a process can end itself and its descendants.

The process to be ended need not still be executing. If it has started its own child processes, but has stopped executing, its children can still be flagged for termination.

Obtaining the Termination Status of a Child Process

OS/2 saves the termination status for a process if the process was started by using the EXEC_ASYNCRESULT constant in the call to DosExecPgm. You can retrieve the termination status of the most recently ended process by using the DCWW_NOWAIT constant with DosWaitChild and setting the child process identification parameter to 0. The DCWW_NOWAIT constant directs the function to return immediately, without waiting for a process to end. Instead, the function retrieves the termination status from the process that most recently ended. If you specify a child process identification with DCWW_NOWAIT, DosWaitChild returns ERROR_CHILD_NOT_COMPLETE if the child process has not ended. Once the specified process has ended, DosWaitChild returns its termination code.

The following code fragment starts a child session (the program SIMPLE.EXE), and then retrieves the termination status from the process that most recently ended.

    #define INCL_DOSPROCESS         /* Process and thread values */
    #include <os2.h>
    #include <stdio.h>

    #define START_PROGRAM "SIMPLE.EXE"

    CHAR         szLoadError[100];
    PSZ          pszArgs;
    PSZ          pszEnvs;
    RESULTCODES  rcReturnCodes;
    ULONG        ulPid;           /* Process ID (returned)             */
    ULONG        ulTarget;        /* Process ID of process to wait for */
    APIRET       ulrc;            /* Return code                       */

    strcpy(pszArgs,
           "-a2 -l");   /* Pass arguments "-a2" and "-l"                         */

    ulTarget = 0;       /* Process ID for the most recently ended process   */

    ulrc = DosExecPgm(szLoadError,            /* Object name buffer                */
                      sizeof(szLoadError),    /* Length of object name buffer      */
                      EXEC_ASYNCRESULT,       /* Asynchronous/Trace flags          */
                      pszArgs,                /* Argument string                   */
                      pszEnvs,                /* Environment string                */
                      &rcReturnCodes,         /* Termination codes                 */
                      START_PROGRAM);         /* Program file name                 */

    if (ulrc != 0) {
        printf("DosExecPgm error: return code = %ld",
               ulrc);
        return;
    }

    ulrc = DosWaitChild(DCWA_PROCESS,         /* Execution options                 */
                        DCWW_NOWAIT,          /* Wait options                      */
                        &rcReturnCodes,       /* Termination codes                 */
                        &ulPid,               /* Process ID (returned)             */
                        ulTarget);            /* Process ID of process to wait for */

    if (ulrc != 0) {
        printf("DosWaitChild error: return code = %ld",
               ulrc);
        return;
    }


Creating an Exit List

You call DosExitList to add to the exit list a routine that is to be given control when a process is ended (or finishes its execution). Multiple routines can be added to the list. When the process is ending, OS/2 transfers control to each address on the list.

If there are multiple addresses on the list, each function gets control in numerical order (with 0 being first and 0FFH being last), based on a value supplied by the application when it calls DosExitList. In case of duplicate entries for this parameter, the routines will be executed in LIFO (last in, first out) order.

DosExitList requires a function code that specifies an action and a pointer to the function that is to receive control upon termination.

The following code fragment adds the locally defined function SaveFiles to the exit list:

    #define INCL_DOSPROCESS         /* Process and thread values */
    #include <os2.h>

    #define HF_STDOUT 1             /* Standard output handle    */

    VOID main(VOID)
    {
        .
        .
        .
        DosExitList(EXLST_ADD,
                    (PFNEXITLIST) SaveFiles);
        .
        .
        .
        DosExit(EXIT_PROCESS,
                0);
    }

    VOID APIENTRY SaveFiles(ULONG ulTermCode)
    {
        ULONG ulWritten;

        switch (ulTermCode) {
            case TC_EXIT:
            case TC_KILLPROCESS:
                 DosWrite(HF_STDOUT,
                          "Goodbye\r\n",
                          10,
                          &ulWritten);
                 break;

            case TC_HARDERROR:
            case TC_TRAP:
                 break;

        }
        DosExitList(EXLST_EXIT,
                    0);
    }


Any function that you add to the list must take one parameter. The function can carry out any task, as shown in the preceding example, but as its last action it must call DosExitList, specifying the EXLST_EXIT constant. An exit-list function must not have a return value and must not call DosExit to end.

When an exit-list routine receives control, the parameter (located at ESP+4 on the stack) contains an indicator of why the process ended. The values returned are the same as those for termination codes returned by DosWaitChild or DosExecPgm requests. These values are:

TC_EXIT (0) Normal exit 
TC_HARDERROR (1) Hard-error halt 
TC_TRAP (2) Trap operation for a 16-bit child process 
TC_KILLPROCESS (3) Unintercepted DosKillProcess 
TC_EXCEPTION (4) Exception operation for a 32-bit child process 

To execute the exit-list functions, OS/2 reassigns thread 1 after ending all other threads in the process. If thread 1 has already exited (for example, if it called DosExit without ending other threads in the process), the exit-list functions cannot be executed. In general, it is poor practice to end thread 1 without ending all other threads.

Before transferring control to the termination routines, OS/2 resets the stack to its initial value. Transfer is by way of an assembly language JMP instruction. The routine must be in the address space of the ending process. The termination routine at that address takes the necessary steps and then calls DosExitList with FunctionOrder=EXLST_EXIT. Control is then transferred to the next address in the invocation order of the exit list. When all such addresses have been processed, the process completes exiting. If a routine on the list does not call DosExitList at the completion of its processing, the process waits, and OS/2 prevents termination.

During DosExitList processing, the process is in a state of partial termination. All threads of the process are ended, except for the one executing the exit-list routines. To ensure good response to a user request to end a program, there should be minimal delay in completing termination. Termination routines should be short and fail-safe.

You can use DosExitList with the EXLST_REMOVE constant to remove a function from the list.

The designer of an exit-list routine must carefully consider which functions will be used by the routine. In general, calls to most OS/2 functions are valid in a DosExitList routine, but certain functions, such as DosCreateThread and DosExecPgm, are not.

Using Threads

Creating a Thread

Obtaining Information about a Thread

Changing the Priority of a Thread

Suspending the Current Thread

Suspending and Resuming Execution of a Thread

Entering Critical Sections

Waiting for a Thread to End

Ending the Current Thread

Ending a Thread

Using Sessions

Starting a Session

Controlling the Execution of Child Sessions

Setting User Selectability of a Child Session

Binding Child Sessions to Parent Sessions

Switching a Session to the Foreground

Terminating a Session