Table of Contents
by Vaughn Vernon
from the December 1987 issue of Computer Language
OS/2, Microsoft’s latest addition to its operating system line, could well become the operating system of the next decade for Intel 80286/80386 microcomputers. Its multitasking capabilities, full-featured application programming interface (API), and extendability to future hardware almost guarantee its success.
Microsoft sees microcomputing as a platform for office automation hardware and software: The office of the future (regardless of a company’s structure and line of business) is envisioned as a place of personal and group productivity. Personal productivity is to be achieved through multitasking, common graphical user interfaces, and the sharing of resources such as data and powerful hardware. Group productivity will occur through individual use of wide and local area networks (LANs).
This article introduces you to the variety of system services provided in OS/2, allowing you to investigate the opportunities offered by OS/2 without a substantial investment of time and money. First I will overview the operating system, then I will delve into the details of the system services.
OS/2 Software Development Kit
The OS/2 system architecture has three layers. The main layer is the OS/2 kernel and system services. The second layer is the Windows Presentation Manager (WPM), Microsoft’s new Windows interface specifically for OS/2. The third layer is the OS/2 LAN Manager, the control software for local and wide area networking. This article focuses on the OS/2 kernel and is based on the OS/2 Software Development Kit (SDK), first beta test release. Some features may be changed or added by the time this article is printed.
The base OS/2 kernel (low-level control process) provides the needed multitasking and related system services without requiring any other special interfaces. A user can run many applications and/or utilities concurrently.
To use OS/2, developers at most will have to learn the OS/2 API, a high-level, call-based programming interface. The API is especially useful for programming languages such as C since it is a natural function call interface. Although Microsoft is stressing the use of WPM, it is not necessary. The LAN Manager also is not a requirement. Applications can take advantage of OS/2 multitasking by simply using the plain vanilla OS/2 system.
The OS/2 SDK comes complete with an optimizing C compiler, macro assembler, and a host of other programming and operating system utilities and tools: object librarian, linker, CodeView source debugger, and full-featured window-based programmer’s editor. The WPM developer’s kit and LAN Manager software will be shipped shortly. The SDK has an array of technical manuals for system call interface specifications and device drivers.
OS/2 is based on a preemptive scheduler. Thus on given intervals the OS/2 kernel is interrupted by the real-time clock in the PC. When it is interrupted, the kernel gains total control of the CPU (even over any currently running task). The kernel reschedules all runnable tasks and executes the task that is next in line.
The scheduler uses time slicing to give a task time to run. Time slicing is based on the priority of each task. Each task is given an allotted time to run, after which the kernel does a context switch. Context switching preserves the current task’s registers and modes of operation and switches in the context of the next task in line to be executed.
The API is implemented in a set of dynamic link (dyn-link) libraries. Routine addresses are far, 32-bit addresses, and arguments are passes on the caller’s stack.
Dyn-link libraries save disk space because program file size is reduced. When the program is built by linking, OS/2 system call function addresses are not resolved by the linker. Instead, the linker knows (because of a special object header) that the routines will be resolved at run time when the program is loaded and does not generate errors.
Not only are the dyn-link routines loaded at run time, they may also be shared by as many concurrently executing tasks as require their use. This ability also saves internal real memory or RAM.
Dyn-linking plays an important part in Microsoft’s view of microcomputers. Since all applications call the API, the API or dyn-link routines may be replaced to support future hardware without having to distribute a new version of the application to fit it. Also, if a dyn-link routine has a bug, your application’s user could be directed to Microsoft for a new dyn-link library. Thus your application should never have to be changed. Code sharing is exploited not only with dyn-link libraries but also with multiple occurrences of the same application. OS/2 knows when an application has been loaded two or more times and just reuses the existing code segment, only allocating memory for the other process sections such as data, heap, and stack.
OS/2 has the ability to run in two modes: protected and real. Protected mode is for multitasking, and real mode facilitates the running of MS-DOS applications. Protected mode is used by the 80286 and 80386 microprocessor to keep one process from being affected by another, ill-behaved process. In other words, a process may not access memory outside its process environment. In real mode, an application can access all machine resources (such as the video memory map) directly.
From its inception, one requirement for OS/2 was the ability to run existing MS-DOS applications. This feature allows users to migrate to the new multitasking system and still be able to run their favorite MS-DOS applications at the same time as OS/2 multitasking applications.
MS-DOS applications are run in real mode when OS/2 dynamically changes the processor’s mode settings. OS/2 continues to execute protected mode processes in the background while the MS-DOS compatibility box runs in the foreground. If the user switches to a protected-mode application, the 286/386 processor is changed back to protected mode and the MS-DOS application is frozen from execution in the background. The MS-DOS compatibility box may be completely disabled if it is not needed, giving OS/2 more memory for protected mode.
The user loads and runs applications in screen groups. Screen groups are a logical division for nontechnical users to exploit multitasking. These groups also help manage and organize the division of completely different applications.
Screen groups are controlled by a process called the session manager. The session manager allows the user to invoke new protected mode command processors and one individual MS-DOS session. As is seen in Figure 1, the user presses a hot key to switch back to the session manager or go directly to another screen group.
Screen groups are implemented as multiple virtual terminals or sessions. Each group has a virtual screen buffer, keyboard, and mouse. When you create a screen group, the session manager invokes a new command processor for it. From there, you may run multiple processes in each group. A process may be spawned in either the foreground or background.
When a process is running in a foreground screen group, it writes directly to the video display. When a process is running in a background screen group and needs to display information, the data is written to its virtual screen buffer. Thus when you switch to a new screen group, any information that was written to the virtual display is flushed to the physical display. The focus of keyboard and mouse events is concentrated on the current foreground screen group.
If you are familiar with programming under MS-DOS, you will notice that OS/2 has a superset of the MS-DOS facilities for most device and system capabilities. For instance, the OS/2 video interface is a superset of the INT 10H ROM BIOS calls offered on IBM PCs and compatibles. The mouse and keyboard interfaces are also much easier to use. The file system interface includes all the read, write, and file search mechanisms and even implements new asynchronous file reads and writes. Thus an application can do something else while the disk drive is being accessed.
A Family API (FAPI) subset of OS/2 system calls supports MS-DOS 2.x and 3.x as well as OS/2. This subset excludes the various multitasking features and interprocess communications (IPCs) but allows programs to be completely portable between the two operating systems. Each major OS/2 system interface subsystem, such as file and video, has a set of system calls reserved for use under both operating systems. A programming utility called “binding” makes a program compatible with both operating systems.
Binding a program places a set of compatibility routine stubs at the end of the program file. If the program is loaded under MS-DOS, the stub routines are loaded with the program and used during execution. When the program is run under OS/2, the stubs are not loaded; the dyn-link libraries are used instead. Binding a program costs the application in both program size and execution speed, but FAPI is useful when you need the compatibility.
The base OS/2 system is a rich programming environment. You don’t need to use the WPM to create applications. This fact is important to the success of OS/2 because the learning curve involved in programming for WPM is tremendous. If all companies had to use WPM, many might look to another operating system forum to carry their next generation software products.
Not having to use WPM will allow existing large multitasking. applications under systems such as UNIX to be readily ported to OS/2 with minimal conceptual revisions. Not using WPM will also allow many applications to become available very soon, adding to the momentum of OS/2. Early products will give OS/2 a reasonably strong foundation and give WPM developers time to complete new software without a lot of pressure to deliver before the product’s time.
Now that I have explained the basic foundation of OS/2, let’s look at its subsystems in detail. The areas I will concentrate on are:
- Process creation and execution
- Memory management
- Device services
- File management
- Interprocess communications
- Miscellaneous OS/2 services
System routine names are referenced in this article by capitalizing the first letter of each word in the descriptive name, as in DosWrite(). The actual OS/2 symbol names are in all caps, so a program would call the DosWrite() routine as DOSWRITE().
All of the OS/2 API routines use the Pascal extended keyword for their calling convention so that arguments are pushed on the stack in the opposite order of C. The Pascal keyword does not allow a system routine to receive a variable number of arguments, but the code generated using the Pascal convention is smaller and faster than the standard C convention. Also, the stack is-restored by the called procedure rather than the caller.
Process creation and execution
Multiple processes have been discussed to some degree, but it is still unclear how they are implemented and how they execute. A process is an environment in machine memory that contains code, data, heap, and stack segments.
A major difference between OS/2 and other popular operating systems such as UNIX is the way tasks are scheduled and executed. Under OS/2, threads are executed, not processes. A process is simply an instance of program execution that owns resources such as code, data, and allocated memory. A thread is the actual dispatchable entity.
You might say, “That’s just semantics, what’s the big deal?” But the idea behind threads is not just verbal organization. A process may own several asynchronously executing threads that share the data and other resources possessed by their owning process. When a process is first created, it owns one thread, the primary thread. This thread can request the OS/2 kernel to spawn another thread to perform some task. The new thread runs on its own time-slice schedule and priorities, separate from the primary thread. Both the primary and secondary threads can themselves spawn other threads.
Threads are much faster to load and run than additional processes because OS/2 does not have to create another process environment and load another program file from disk to execute that process. When a thread finishes its job, it can terminate without affecting any other thread in the entire process.
A process is created with a call to DosExecPgm(), which is similar to the MS-DOS EXEC function but more powerful. A flag passed in the call to DosExecPgm() tells the kernel to execute the new process either synchronously or asynchronously to its parent process. If synchronous execution is selected, the parent process will be suspended until the child completes. With asynchronous execution, the parent will continue to execute while the child process runs.
Like MS-DOS and UNIX, OS/2 allows the child to inherit its parent’s environment, file descriptors, and other vital resource handles. The parent may boost its child’s priority or even terminate the child if necessary.
A new thread may be created by calling DosCreateThread(). The kernel is given a dynamically allocated stack segment for the new thread and the far address of a routine to be set up for task scheduling. The kernel returns the newly created thread’s ID code to the primary thread for use in task control.
Threads should be used instead of new processes when the task to be performed is local to the current process. Doing so allows the sharing of process data and heap, making task performance much faster than if the duties were given to a new process.
Figure 2 is an example in C of the primary thread creating a secondary thread to do some calculations for it. This example is not extremely useful (all it really does is waste CPU time), but it does demonstrate the calling convention and necessary arguments for thread creation. The example also shows that the thread has a stack to use for local (automatic) variable creation. Here, the only thing that makes thread_func() a thread is that the primary thread passes its far address to the OS/2 scheduler to create a context for it and run it asynchronously. The primary thread could call thread_func() as a synchronous C function call instead of a thread.
Imagine you are developing a spreadsheet package. The primary thread handles the user interactions but passes spreadsheet calculations off to a secondary thread. The user is now free to continue working on another spreadsheet while the calculations are being performed asynchronously on the first spreadsheet. This example should give you an idea of how threads can complement your programming effort.
Two functions handle setting thread priorities: DosSetPrty() alters priorities for the caller or another thread, and DosGetPrty() is used in conjunction with DosSetPrty() to inquire about a thread’s current priority.
Priorities have three classes: idle-time, regular, and time-critical. Within each class is a priority level ranging from 0 to 31 for each class. Idle-time, level 0 is the lowest possible priority, and time-critical, level 31 is the highest possible priority.
The user of OS/2 and your application ultimately. can control priority modification. The file CONFIG.SYS is used at boot time to set priority permissions. Three configuration keywords are used in CONFIG.SYS to control the use of priorities: PRIORITY = ABSOLUTE or DYNAMIC, MAXWAIT=x, and TIMESLICE=x|,y|.
The PRIORITY= ABSOLUTE or DYNAMIC keyword and arguments determine whether or not dynamic priority modification is allowed. If PRIORITY is equal to ABSOLUTE, OS/2 will run all threads at the same priority and refuse any request for modification. The *DYNAMIC8 configuration allows the full span of task priority usage.
MAXWAIT=x tells OS/2 not to allow a thread to sit idle for more than x seconds. If a thread is idle for more than the specified number of seconds, it will receive a boost in priority for one time slice so that the scheduler will run it. The default number of idle seconds is 3.
Finally, TIMESLICE =x|,y| allows the user to set the range of milliseconds the OS/2 scheduler will dedicate to a given thread. The x parameter is the lowest time slice any given thread will be granted, and y is the maximum allowable time slice.
TIMESLICE helps OS/2 reduce the amount of context switches necessary to execute a CPU bound thread. If a thread has executed for x milliseconds but has not completed a computation or is not blocking for I/O, then its subsequent time slices will each be incremented by one millisecond, up to y milliseconds, until the computation is done.
These configuration settings are important to users of OS/2. It is possible and very easy to boost your own processes’ threads to a time-critical, level 31 priority and stay there. But doing so will turn OS/2 into a single-tasking system, which is no less than pure abuse of this OS/2 feature.
Legitimate time-critical applications do exist. However, most tasks should be run at regular priority. If a thread must use time-critical priority, it should only do so for a few milliseconds. This area will require education and documentation to prevent users from being battered by CPU selfishness.
The last area in process tasking control to be covered is process and thread termination. If you again look at Figure 2, you can see the last OS/2 system call in each function is to DosExit(). This call takes two arguments: an action code and the thread’s result code.
An action code of 1 tells OS/2 to terminate all threads in the process. An action code of 0 only terminates the caller. The result code is a value that is returned to the parent thread waiting for the child process to terminate execution. The result code is analogous to the code that is passed to the standard C library functions exit() and _exit().
A thread can wait on a process to terminate in two ways. If the process is spawned synchronously, the thread will automatically block until its child process terminates, returning the result code. When a process is created to execute asynchronously, the system routine DosCWait() must be used to obtain the result codes of the child. By using DosCWait, a thread can wait on just its direct child’s termination or, optionally, the termination of any grandchildren and great-grandchildren, and so on.
Another extremely useful OS/2 routine is DosExitList(). This routine maintains a list of code addresses that should be called when a process terminates normally or abnormally, although the main purpose is to service abnormal terminations. Those of you who will be writing such tools as data base interface libraries will want to make use of this routine.
Suppose an application uses a data base interface for transaction processing. Any form of abnormal termination-could leave.the data base file in an undefined state. By using DosExitList(), the data base manager could be alerted to flush its cache buffers and close files before the process terminates.
Memory management under OS/2 is much more than merely dynamic memory allocation. All of the common memory allocation primitives (and then some) are present.
The most important feature of memory management is the support of virtual memory. Virtual memory allows a process to obtain more memory than physically exists. The OS/2 memory manager works in conjunction with three other OS/2 resources: the swapper, LDT (Local Descriptor Table), and GDT (Global Descriptor Table). The LDT and GDT are used to implement not only virtual memory but also memory protection and shared memory. With memory protection, a process is not allowed to use memory it doesn’t own.
Each process has an LDT. There is one system GDT. A process’s memory is mapped from virtual addresses to physical addresses using the process’s LDT. The GDT contains virtual-to-physical memory mapping for systemwide resources used by all applications. If you try to access an address not in your process’s LDT, OS/2 will abort your process because of a hardware trap. Figure 3 shows how physical memory is mapped to virtual addresses in the LDT.
A process can request the allocation and use of memory in a size that doesn’t physically exist on the machine. The OS/2 memory manager calls on the swapper to create room in physical memory for the request. The swapper operates with a least-used algorithm to yank segments of data from RAM to disk, freeing the needed memory for the requesting process.
A swapped segment is marked as such in the LDT with a special flag. If a thread attempts to access data that has been swapped to disk, OS/2 blocks the thread. The swapper is then interrupted by hardware to create room to read the segment back from disk. Swapping in a segment often requires that another nonused segment be swapped out first. When the new physical segment is allocated, the LDT virtual-to-physical mapping must be updated and the segment marked as nonswapped. The thread will then complete its time slice.
Theoretically, this swapping scheme facilitates the use of 1 gigabyte of virtual memory within the 286’s environment that is physically limited to addressing 16MB. Realistically, total memory is limited to the lump sum of physical RAM and the maximum size your hard disk can be formatted to.
Unfortunately, even with this large amount of virtual memory, processes that totally exploit the limitations of physical memory can perform sluggishly due to the swapper constantly “hitting” the disk to accommodate the needed memory. This sluggishness occurs under any virtual-memory-based operating system.
With the OS/2 memory manager, you are no longer limited to using memory in chunks restricted to 64K. A very large memory allocation routine is supplied in DosAllocHuge(). Allocating segments of memory smaller than 64K is supported, as is the reallocation of both large and small segments. DosAllocSeg() is used to allocate memory in chunks smaller than 64K.
Shared memory is arranged by calling DosAllocShrSeg(), Shared memory is memory that can be accessed by one or more processes and can technically be called an IPC facility. One process may call DosAllocShrSeg() to create the shared memory; other processes wanting to share the memory must call DosGetShrSeg().
A path identifier references the segment used for creation and sharing. Both DosAllocShrSeg() and DosGetShrSeg() are returned a segment selector to the memory that is commonly referenced by a logical path beginning with the directory \SHAREMEM\ as in \SHAREMEMNDATABASE.REC. Although this procedure is like opening a file, shared memory is not associated with the OS/2 file system.
You may share a block of memory obtained by DosAllocSeg() with another process by calling DosGiveSeg(). However, DosAllocShrSeg() and DosGetShrSeg() are easier to use and more versatile because the caller of DosGiveSeg() must pass the memory recipient the returned selector by some other means of IPC. Also, the caller of DosGiveSeg() must know the recipient’s process ID.
OS/2 maps the shared memory segment onto the LDT of one or more processes. Therefore, two or more processes may use a block of physical memory by referencing the virtual addresses found in the LDT.
Among the variety of devices supported, the most commonly used directly by an application are the video display (the terminal), keyboard, and mouse. The disk drive is also used frequently by most applications, but I will focus on disk access when I discuss file management.
The video interface to OS/2 is a refreshing change from using the IBM PC memory map and TTY device drivers of other operating systems. The video interface is a superset of the IBM PC BIOS services, but don’t let the thought of INT 10H frighten you. It, like the other OS/2 dyn-link interfaces, is implemented through high-level CALLs. You won’t find the slow performance of the BIOS in OS/2’s video subsystem. OS/2 does not call the ROM BIOS since software interrupts are not permitted in protected mode. The superset actually uses IOPL to directly access the video hardware, so it’s at least as fast as writing directly to the memory map yourself.
Listing some of the function calls an application can make will give you an idea of services available in OS/2. You can write a string with an attribute using VioWrtCharStrAtt(). Repositioning the cursor is handled by VioSetCursorPos(). And no-flicker, smooth scrolling is handled by VioScrollxx(), where xx is either Dn, Lf, Rt, or Up.
Although you can get a pointer to the physical video buffer for both character and graphics modes, doing so is highly discouraged. With the high-level interface to character-mode video, accessing the physical pointer is not really necessary. You can use the physical buffer for graphics, but doing so is strictly device dependent. Graphics is fast becoming the job of sophisticated interfaces such as WPM.
OS/2 keyboard support is a superset of the IBM ROM BIOS INT 16H interface. You can either read or peek at the keyboard. Reading one character from the keyboard is done through KbdCharIn(). The keystroke information is returned with character and scan code information and shift statuses, so you can interpret function and other special keys. Shift and press statuses are independently accessible through KbdGetStatus(). With KbdStringIn(), you can read a string of data requesting a maximum number of characters and receive the actual number input.
Mouse support is also a tremendous improvement over the MS-DOS interrupt interface. To initialize the mouse, just call MouOpen(). Instead of having to use interrupts to access mouse movement and button press information, just call MouReadEventQue(). The mouse device driver and subsystem implement an event queue that contains mouse events generated by the user. The event queue is a circular buffer and has a default length of 10 events. This default may be changed in the CONFIG.SYS system file.
CONFIG.SYS is also used to install the mouse driver of your choice. Different mouse drivers are available for the serial, bus, import, and Mouse Systems mouse device. A new keyword for the mouse driver tells which serial port the device is installed on. The default port is COM1, but by using the SERIAL= keyword, you can set up use of the mouse on COM2.
Here is how you would install the mouse driver on COM2 for the Microsoft serial mouse for use with both protected mode and real mode:
mode=b says to use both protected mode and real mode. To use only protected mode, you would enter mode=p.
All three of these device subsystems (video, keyboard, and mouse) can be replaced by application software. By using VioRegister(), KbdRegister(), or MouRegister(), you can replace one or all of the routines the subsystem uses by mapping the default routine(s) to a replacement in your application.
One of the most valuable features of these device services is that outside of the mouse routines they are primarily FAPI calls and therefore bindable to MS-DOS. The VIO interface under MS-DOS uses direct memory mapping of video character mode output for optimum speed. The mouse interface is not portable or bindable to MS-DOS because the mouse interface is interrupt driven when using the MS-DOS driver.
The file system that OS/2 uses is basically the same as that offered under MS-DOS. In fact, all normal files are completely compatible between the two systems. The file system is based on a hierarchical directory structure similar to that of UNIX. Currently file permissions are passed off to the OS/2 LAN manager, but in a future release of OS/2 they will be featured as a standard part of the operating system interface.
The hard disk size is limited under OS/2. Actually, the limitation is not directly related to disk size but to partition size. The maximum size of any partition is 32MB, but this size is not entirely restrictive to larger storage media. With a larger disk drive, you can partition the disk into multiple 32MB (or smaller) partitions and reference each partition as a logically separate disk drive. Thus you don’t completely lose the storage capacity of the disk, but each logical drive may be only as large as 32MB. The use of a non-IBM standard hard drive (or even a 32-inch floppy) requires creating a new disk device driver since OS/2 only supports standard devices.
There is good news for people that cannot tolerate the thought of copy protection. Copy protection schemes will not work under OS/2. Software cannot access absolute disk locations; therefore, OS/2 will not be able to read a file that was saved in this way. (Sounds like a good limitation!)
DosOpen() and DosClose() are used to open and close files, respectively. As under MS-DOS and UNIX, each process inherits its parents open file descriptors, including stdin, stdout, and stderr (if they are open). By default, a process may have as many as 20 open file descriptors. By calling DosSetMaxFH(), this limit may be dynamically adjusted up to a limit of 255 open handles per process. The old files=xx of MS-DOS is a no-op for CONFIG.SYS under OS/2 protected mode.
The file system API is complete with the read and write primitives DosRead() and DosWrite(). In addition, two new read and write functions, DosReadAsync() and DosWriteAsync(), are implemented through threads. These routines allow the programmer to do asynchronous reads and writes so that the application is free to continue processing in another area while the disk file is being accessed.
Directory query functions are provided by using DosFindFirst(), DosFindNext(), and DosFindClose(). The information passed to DosFindFirst() may be in wild card format. The output of both DosFindFirst() and DosFindNext() contains full file name, attribute, and status (date, time, and access) information.
You can change the size of a writable file with DosNewSize(). However, DosNewSize() is slow and thus is not used by the OS/2 swapper to shrink the swap file after a segment is yanked back from disk to memory. The swapper’s disk swap file can grow (and eventually will unless you remove it yourself) to the limit of a disk partition. Consequently, the swap file should reside in its own partition so it doesn’t steal space from your standard file system.
IPC is a familiar resource to those who have used a multitasking system before. OS/2 supplies the primary IPC mechanisms in pipes, queues, and semaphores. (Though shared memory is also a form of IPC, it is discussed in more detail in the section on memory management.) Most MS-DOS and UNIX programmers have used pipes at one time or another. The shell command:
sort customer.dat more
sorts the file customer.dat and writes the output to stdout (standard output—video display). by default. However, the | syntax tells the command interpreter to send the standard output of sort to stdin (standard input—usually the keyboard) of the more file pager. This process is known as a pipe.
The DosMakePipe() and DosDupHandle() system calls set up a pipe between two processes. A pipe has a read end and a write end. The thread that reads from the pipe cannot also write to the pipe. The very nature of how this form of pipe IPC is implemented requires that sharers of the pipe be closely related. Generally, a parent process will set itself up to read from or write to the pipe and create a child process to do the opposite. Having named pipes (provided with the OS/2 LAN manager) allows two processes to use pipes and be remotely related, similar to using files.
OS/2 automatically synchronizes a pipe’s reader and writer. If a pipe’s reader runs out of data, an attempt to read the pipe will cause the reader to block (an operating-system-enforced wait state). Likewise, if a pipe’s writer is filling faster than data can be drained, the writer will block.
OS/2 message queues facilitate the passing of messages from several servers to a client. The queue reader process creates a message queue by calling DosCreateQueue(). The queue path identifier to be used to create the queue and attach to the queue by the servers is in the form \QUEUES, as in \QUEUES\DATABASE.MSG. Message queues, like shared memory, are not truly related to the OS/2 file system.
Messages can enter and leave the queue in one of three ways: through FIFO, LIFO, or a server priority number (priority is O—15; 15 is the highest priority). A server adds messages to the queue using DosWriteQueue(), and the client reads from the queue using DosReadQueue(). DosPeekQueue() is used by the client to search for a particular type of message.
Each message has an associated data buffer: the message itself. A special request word attached to each message is to be understood by both the servers and client. Using the request word makes quéues more powerful and versatile since the request can be a message in itself. When the queue is read, the request word is accompanied by the process ID of the writer of the message. OS/2 makes no special use of the request word, it only queues the value along with the rest of the message.
The data buffer associated with message queues must be a pointer to a shared memory block if the message will be passed to a thread not in the sender’s process. You will also need to implement a queue buffer array. The queue buffer array allows multiple messages to be placed on the queue without destroying messages that haven’t been read yet. This extra work is needed because the kernel doesn’t manage a kernel buffer area for each message buffer, it only retains a far pointer to the message data.
A semaphore is used to serialize the access of reusable resources such as data, as well as physical devices between two or more asynchronously executing threads. The semaphore model asserts that only one thread can own a shared resource at any given time. A thread calls the OS/2 API to obtain ownership of a common resource-representing semaphore. The resource itself is not really owned. Also, owning a semaphore does not guarantee that another thread will refrain from accessing the shared resource. However, the ownership of the resource-representing semaphore is adequate resource ownership for all threads playing by the rules.
OS/2 has two kinds of semaphores: RAM and system. Your choice depends on how you intend to use the semaphore. RAM semaphores are typically used by threads within the same process to coordinate globally accessible data owned by the process. System semaphores are used on a systemwide basis to allow threads from two or more processes to share a common resource such as a disk file or the keyboard (if two asynchronous processes within the same screen group are contending for use of the keyboard).
RAM semaphores fit so well into the environment of one process because of how they are implemented. A RAM semaphore is just a double word of storage (C long type). The semaphore is initially set to zero, meaning that it is not owned, and used by all threads that want to share a resource. The variable, if declared as a global, can be used by all threads within a process.
System semaphores, like queues and shared memory, are created and opened by the threads that require their use. The identifying directory for system semaphores begins with the \SEM\ path as in \SEM\DATABASE.SEM. Only one process may create and own a particular system semaphore. Multiple threads may open a system semaphore. The OS/2 APIs for accessing this semaphore type are DosCreateSem() and DosOpenSem().
The DosSemRequest() call is used to get ownership of the semaphore. DosSemClear() relinquishes ownership of the semaphore. These calls work on both RAM and system semaphores.
A thread may request one of three action types if it requests semaphore ownership and another thread already owns it. The thread may block indefinitely until the semaphore is cleared. The thread may also set a time-out limit for waiting on the semaphore or request an immediate time-out. An immediate time-out tells the DosSemRequest() routine to return right away if the semaphore is already owned so that the thread can do something else in the meantime.
A thread should not retain ownership of a semaphore for an indefinite period. Instead, it should access the common resource and release the semaphore as soon as possible so that processing is efficient. Figure 4 illustrates three threads in two different processes requesting semaphore ownership for video access privileges.
OS/2 also allows threads to use semaphores as signal-triggering mechanisms. Suppose thread A sets a semaphore as a trigger to thread B, which blocks until thread A clears the semaphore. Thread A does not clear the semaphore until an event occurs. The event could be related to an auto-answer modem being called. When thread A detects that the modem has been called, it would clear the semaphore so that thread B would answer and handshake with the caller.
DosSemSet(), DosSemWait(), and DosMuxSemWait() can be added to the list of OS/2 routines that support a semaphore signaling environment. Most notably, DosMuxSemWait() allows a thread to wait on one of up to 16 events with time-out specifications.
Miscellaneous OS/2 services
Among the hundreds of OS/2 API services, some of the more commonly used routines are in the areas of task timing and process signaling. Your application may also need sound, whether for music or just to get some attention.
The OS/2 task-timing API supports an interval timer and asynchronous timer delay interface. You may start the interval timer using DosTimerStart(). The interval timer clears a set semaphore based on the interval duration specified by the caller. The semaphore should be reset before each interval is reached by the timer. The semaphore will continue to be cleared until the timer is stopped by calling DosTimerStop().
The DosTimerAsync() routine works similarly to DosTimerStop() but only waits on one elapsed timing event and then clears the semaphore. These calls mesh well with the signaling semaphore environment so that a blocking thread may be dispatched on timed intervals.
A thread may give up its time slice for a number of milliseconds by calling DosSleep(). Threads may need to wait on some kind of event. DosSleep() causes a thread to forfeit the CPU instead of wasting CPU time in an empty loop.
Signals are caused by events that occur in hardware or software that directly affect a process or group of processes. SIGTERM is a common OS/2 signal generated when another process calls DosKillProcess(), a tasking call, to terminate another process. Usually a process is aborted immediately when it receives this signal. A process may disable signal processing by using DosHoldSignal() so that the SIGTERM event would not affect the signaled process.
DosSetSigHandler() records the routine address within a process to be called by OS/2 when a given signal occurs. Supporting a signal handler allows a process to properly recover from a signal or terminate gracefully, if necessary.
To use the speaker attached to your PC, call the DosBeep() service routine. DosBeep() takes as arguments a frequency and time duration. The speaker will generate the given sound frequency (between 25H and 7FFFH) for the number of specified milliseconds.
What the future holds
Will OS/2 be the operating system of the future for 286/386 microcomputers? Nothing. is certain, but it does have the required substance and definitely enough momentum and corporate support. The investment is high, not only in dollars but also in time and complexity. OS/2 is no toy. It was designed to serve the purpose of a platform for the next generation of 286/386 software.
OS/2 will receive a few enhancements in the future. The file system will most likely become installable, and security mechanisms will be added.
And what about 80386 support? Although OS/2 runs on a 386, it doesn’t take full advantage of it. With the 386, a full 32-bit linear address space is available; 32-bit machine operations and multiple concurrent MS-DOS screen groups can run along with protected-mode applications. In addition, much of the code now in OS/2 will disappear because the 386 itself supports virtual memory and paging. The next two years may well prove to be the most important for OS/2.
Vaughn Vernon is president of Aspen Scientific, a company that specializes in programmer’s tools for cross development between UNIX/Xenix, OS/2, and MS-DOS. Vaughn is coauthor of The Advanced C Programmer’s Guide to OS/2 published by Microsoft Press.