PMOS Reference Manual P.J. Moylan Department of Electrical and Computer Engineering peter@ee.newcastle.edu.au The University of Newcastle NSW 2308, Australia NOTE This is a plain-text version of the main section of the PMOS reference manual, version 2. The original version, which is available only in Interleaf or Postscript forms, contains some additional material: the listings of all definition modules, and a procedure index. If you are restricted to using this plain-text version, you can obtain the listings of the definition modules by going directly to the PMOS source files. 1. Introduction This is a new edition of the manual previously published as "The PMOS Definition Modules", Technical Report EE9107, which described PMOS version 1. There have been some significant changes to PMOS from version 1 to version 2. The PMOS software system is a set of modules, written in Modula-2, for use in real-time applications such as control systems. At the user level, it does not appear as an operating system in the conventional sense of having a command interpreter and facilities for running multiple independent programs in parallel. Rather, it is a set of library routines which can be incorporated into an applications program to allow for multitasking within that program. PMOS is designed to run on IBM-PC/AT and similar personal computers. One would normally use the computer's original operating system, usually MS-DOS, while performing the program development task of editing, compiling, and linking. Then, when the applications program is run, PMOS takes over the computer by substituting its own device drivers, etc., for those which were originally present. The keyboard controller is designed in such a way that the Ctrl/Alt/Del combination aborts the program and returns control to the original operating system. (Incidentally, a module called TerminationControl is provided to allow you to provide for graceful program termination, for cases where abrupt program abortion could be disastrous.) When PMOS is run under OS/2, it runs in a virtual DOS session rather than taking over the whole machine. (A complete takeover is neither sensible nor feasible without first shutting down OS/2.) In that case the Ctrl/Alt/Ins keyboard combination should be used, instead of Ctrl/Alt/Del, to terminate the program. When running under OS/2 there are also some restrictions on what the PMOS device drivers can do. In particular the hard disk driver will not work - it will act as if there were no hard disk available - although the floppy disk driver does work. The purpose of the present document is to act as a reference manual, by listing all of the procedures within PMOS which may be called. A complete source code listing would be unreasonably long, so only the definition modules of PMOS are listed. This is, in principle, all that a user needs to know about the PMOS code. For ease of reference, the listings are arranged alphabetically by module name. The index gives an alternative ordering, by listing procedure names rather than module names. Finally, the discussion in the following sections is organised by functional groupings. 2. Multiprogramming features The kernel of the PMOS system is contained in modules InnerKernel, TaskControl, Semaphores, and Timer. Many of the procedures in those modules may be considered as internal system details which are irrelevant to the general user. In this section, we focus attention on the procedures which are intended to be user-callable. When your program first starts running, there are just two tasks in the system: the task which is executing your program, and a "null task" which does nothing and whose only function is to give the processor something to do while no other task is runnable. Your program may then create a new parallel thread of execution by calling procedure CreateTask, in module TaskControl. (Meanwhile, PMOS has itself created a few more tasks, to look after device drivers and the like.) Further tasks may then be created, either by the original task or by any of the newly created tasks. When you call CreateTask, you give a procedure name, which becomes the starting address for the new task; a priority; and an abbreviated task name which is used only for testing. As a general guideline priority levels in the range 1 to 7 should be used for user-level tasks, and levels 8 to 15 should be reserved for system-level tasks such as device drivers. Typically most tasks should have priority 1 (the lowest level). There may occasionally be a good reason for giving a high priority to a user-defined task of exceptional urgency, but this facility should be used with care. A careless use of high priorities can make your software run inefficiently, by interfering with the ability of the system to do work on your behalf. It is important to understand the distinction between a task and a procedure. A task is the actual thread of execution, which may pass through many procedures during its lifetime. More importantly, it is quite possible for several tasks to be executing the same procedure simultaneously. In this connection, it should be noted that the variables declared inside a procedure belong to an individual task, in the sense that a separate copy of those variables is created for each caller of the procedure. The variables declared at module level, on the other hand, are shared by all tasks which enter that module. Thus, you should avoid declaring global variables unless you intend to make them shared variables, in which case you must of course arrange for suitable critical section protection when accessing those variables. A task normally terminates by reaching the last statement in the procedure in which it started execution. If you want the task to terminate before that, you can call procedure TaskExit in module TaskControl. When the "main program" part of your Modula-2 program completes its execution, the entire program terminates, even if there are tasks still in execution. This might or might not correspond to what you want; so it may be necessary to insert tests at the end of the main program which wait until all subsidiary tasks have completed their work. 3. Semaphores Module Semaphores contains procedures CreateSemaphore, Wait, and Signal, whose use will be obvious to anyone familiar with semaphores. Those who are unfamiliar with the concept should consult an operating systems textbook, since an adequate description would take more space than is available here. Given that semaphores are used for inter-task communication and protection, some programmers become confused about who should be the "owner" of a semaphore, and how a semaphore created by one task can be passed to another. In fact, this is the wrong way to look at the idea of ownership. It makes more sense to say that a semaphore belongs to a module (not to a task), that it is created in the initialisation code of that module, and that it is then available for use by tasks which call procedures in that module. Procedure TimedWaitT in module Semaphores is not intended for general use. A better alternative exists in module Timer. 4. Locks and priority inheritance Module TaskControl also defines a data type Lock. A Lock is like a binary semaphore, and can be used for protecting critical sections. That is, you can use code of the form Obtain (L); protected section; Release (L); to ensure that, while the statements in the protected section are being executed, no other task can gain entry to any section of code which is also protected by the Lock called L. This is a little more efficient than using a semaphore, but is more restricted: a Lock may only be used for critical section protection (unlike a general semaphore, which may be used for things like intertask synchronisation); and the Obtain/Release pairs must be used in a properly nested fashion. In addition, any section protected by a Lock must be free of any operations - except for a nested Obtain/Release on another Lock - which could block the task. That is, it is not legal to have a Sleep or a semaphore Wait inside such a protected section. (Watch out in particular for procedure calls, where the called procedure might perform one of the forbidden operations.) If this condition cannot be satisfied, then you must use a semaphore rather than a Lock to protect your critical section. A special feature of the Obtain/Release mechanism is that it provides for priority inheritance. If a high-priority task is blocked while trying to obtain a Lock, then the holder of that Lock is promoted in priority, to ensure that it quickly reaches the point where it releases the Lock. In the case where a task holds several Locks, its priority becomes the maximum of the priorities of the tasks it is blocking. This promotion is temporary; the task's priority is dropped again when it is no longer blocking a higher-priority task. 5. Timed Operations Most of the work done by module Timer is invisible to the user, since the main function of this module is to look after internal system functions such as time-slicing. There are, however, two useful user-callable procedures. Procedure Sleep provides a timed delay, putting the caller to sleep (and making the processor available to other tasks) for a specified number of milliseconds. Procedure TimedWait adds a time-out facility to semaphore operations, to allow for recovery in the case that an expected event never happened. Inside the implementation module, there is a constant MillisecondsPerTick which controls the frequency of timer interrupts. Its value can be decreased if it is necessary to increase the precision of timed operations. You are warned, however, that decreasing the value will increase the time spent by the processor on system overheads. To avoid confusion, it should be mentioned that the hardware provides more than one timer. Module Timer uses one specific hardware timer which simply interrupts at a fixed frequency. There is a separate time-of-day clock, which keeps track of the date and time even when the computer is powered down, and that is looked after by module TimeOfDay. (A module called Directories uses the time-of-day clock to know when a file is created; and the time-of-day clock is also useful in software testing, when you want to know how long a program section takes to execute. At present, there is no facility for timing one specific task.) Depending on the hardware configuration, there may be other timers in the system which are used for more specialised purposes - see, for example, module AnalogueIO - but these are of no concern to the Timer module. 6. Screen Output In the majority of applications, the best procedures to use for screen output are those in module Windows. These procedures provide for character and string output and input, and a variety of cursor-positioning and similar operations. At a lower level, module GlassTTY provides low-overhead but very crude output procedures. It is used by some system procedures for error message output, but is probably unsuitable for other applications. At the very lowest level, module TextVideo performs some of the most primitive hardware operations. For numeric input and output, use modules NumericIO and RealIO. The module called Conversions might also be of use here. It is sometimes useful to have a program produce diagnostic information which does not normally appear on the screen, but which can be viewed by the user on demand. This facility is provided by a module called MaintenancePages, which demonstrates one way to use multiple virtual screens. Similar applications can be designed around the features provided by MultiScreen. For graphical rather than text modes, use module GWindows. Utility procedures to do graph-plotting may be found in module WGraph. The low-level graphics functions are in the modules called Screen and Graphics. There is also a module called ScreenGeometry whose primary function is to define data types "Point" and "Rectangle". Module Tiles, which looks after the issues associated with overlapping graphics windows, is not intended to be called by the end user. 7. Keyboard Input In "normal" applications, the best keyboard input routines are those, such as ReadChar, supplied in module Windows. However, module Keyboard provides a way to read a character without going through the Windows module. The essential difference is this: the input procedures in Windows prompt the user, e.g. with a blinking cursor, and may echo the input on the screen if specified. By going directly to module Keyboard, you avoid the prompt. There are some cases where this is a more desirable behaviour. Both Windows and Keyboard provide a method for reading a character without consuming it - a highly desirable facility, since there are many applications where one has to overshoot by one character before detecting the end of an input string. The Keyboard module does this by providing an "un-read" operation called PutBack. The solution in Windows is slightly different, see function LookaheadCharacter. The reason for using different approaches in the two modules is somewhat technical and not easily explained in an overview such as this, but you will soon find that the conventions are well adapted to a "natural" style of programming. The keyboard is actually handled by two modules. Module Keyboard, already mentioned, provides the higher-level interface which most users will find appropriate. Those who need more detailed control over keyboard operations can use the lower-level module KBdriver, which allows access to such information as whether several keys have been pressed simultaneously. 8. Dealing with a mouse The module called Mouse provides functions for things like getting the current mouse position, and it also provides for installing a user-supplied procedure to be called on any of a defined set of mouse events. Internally, this module does its job by importing from either SerialMouse or Mouse33. The reason for having two distinct mouse drivers is that not all mice are the same, and there are situations where one driver will work and the other will not. Mouse33 works by using INT 33H calls to a pre-loaded driver, so this is potentially the more portable version; but the vendor-supplied mouse driver for at least one popular mouse is incompatible with PMOS because of the way it takes over the timer interrupt. Module SerialMouse talks directly to the mouse via a serial port; this makes it less portable in principle, but it does at least bypass the shortcomings of vendor-supplied drivers. To specify which mouse driver to use, edit the definition module for the module called ConfigurationOptions. There is also a module called UserInterface which lets you use a mouse to move windows around on the screen. 9. Screen Editing The design and implementation of operator interfaces can be a tedious and time-consuming part of software development, so PMOS contains some features to ease this problem. Module Menus displays user-defined menus on the screen, and looks after displaying a menu, scrolling when necessary, handling the keystrokes by which the keyboard user selects an option, and returning the result to the caller. Module ScreenEditor does a similar job where a display of variable values, of a mixture of types, must be created with an option for the keyboard user to modify some or all of the values. For more complex applications, it might be necessary to use the procedures in modules FieldEditor and ListEditor. The procedures in module RowEditor, which works in collaboration with this group, will not normally be needed to be called directly. There is a module Calculator which, when invoked, displays a calculator window on the screen and allows the user to perform simple calculations. (The calculator procedure returns when the user types the Esc key or clicks on the "close" button with the mouse.) Use this when you want the user to be able to drop out of your program temporarily to perform some calculations. 10. Miscellaneous utilities The module called LowLevel provides a variety of low-level functions: bitwise logic operations, shifts and rotations, pointer decomposition, and the like. More low-level procedures can be found in MiscPMOS. Random numbers are available from modules RandCard (for a cardinal-valued result) and Random (for a real-valued result). To implement queues, see modules CircularBuffers, Queues, and LossyQueues. Module Queues handles the most general case, but CircularBuffers can be more efficient when it is applicable. LossyQueues is for the situation, very common in real-time applications, where it is better to lose data than to have to wait for buffer space to become available. Queue-handling operations are also supplied in Mailboxes. This module is intended as a convenient mechanism for message-passing between tasks. Module Conversions deals with things like string-to-numeric conversions. A date and time procedure may be found in module TimeOfDay. 11. Sound output Module SoundEffects deals, at a low level, with output to the speaker. You can, for example, call procedure Beep to produce an audible alarm. For more ambitious sound effects, see modules Music and Piano. The module called MusicDemonstration has as its only function the playing of a demonstration piece of music. If you import it into your program, the music will play in parallel with whatever your program is doing. Sorting is provided in QuickSortModule and FileSort. QuickSortModule supplies a procedure for in-memory sorting, and FileSort does in-place sorting of a file. 12. File I/O The standard operations of opening and closing a file, and of reading and writing, are in module Files. Note: in many operating systems, you have to specify when opening a file whether you are opening it for reading or for writing. PMOS takes a different approach: you can mix read and write operations, but you have to specify when opening a file whether it is supposed to be a new file or an existing file. Unlike MS-DOS, PMOS does not allow you to open a new file with the same name as an existing file. This helps to avoid accidental file deletions. The modules called Directories, FileNames, IOErrorCodes, and Devices are parts of the file system which are not normally called by the end user. When using the Files module to access disk files, you must explicitly import modules HardDisk and/or Floppy (and the import declarations should come before the importation of Files), even though you will not explicitly call procedures in those modules. The file system does not have direct access to the device drivers; it simply makes use of whatever device drivers have (in their initialisation code) called module Devices to declare their presence. Although the PMOS file system works well with floppy disks, it does not always handle hard disk files satisfactorily. The problem areas are: (a) The HardDisk driver does not support all known disk interfaces. It does not handle SCSI disks, for example. (b) OS/2 will not permit hard disk operations, so under OS/2 the HardDisk module acts as if there is no hard disk present. (c) At program termination, MS-DOS does not detect the fact that it is holding an obsolete copy of the directories and file allocation table, so it will not see newly created files - and it will attempt to write over them - unless the machine is rebooted. To circumvent these problems, an alternative module FileSys provides the same features as the Files module, but it implements them via system calls rather than by dealing directly with the device drivers. This loses some of the advantages of multitasking, but provides greater program portability. 13. Other device drivers Module Printer supports a dot matrix printer connected to the parallel port. At present, this module is not part of the file system, so the PrintChar procedure must be called directly. For I/O through the serial ports, use module SerialIO. Module AnalogueIO provides an interface to analogue-to-digital hardware. Normally the A/D software will run in a "periodic sampling" mode, which is explained in the comments in the definition module. Note that the support is for one specific type of A/D interface board, and will have to be rewritten if the hardware is changed. Due to the special nature of A/D operations, there is no intention to support analogue operations within the file system. 14. Writing new device drivers To add a new device driver to the system, it is necessary to understand how to use procedures CreateInterruptTask and WaitForInterrupt in module TaskControl. It will probably also be necessary to use the procedures in modules LowLevel, DMA, and MiscPMOS. In this connection, it should be noted that the ROM routines in the computer's BIOS (Basic Input/Output System) are sometimes incompatible with multitasking and may have to be avoided. It is advisable to study the listings of existing device drivers, to see what techniques are needed. To incorporate a new device driver into the file system, use module Devices. The file system does not in itself have access to any device driver, but indirectly it can access any driver which makes itself known to module Devices. 15. Other modules Module Trace is an aid to program testing and debugging; its use will be obvious after reading the module listing. Module KTrace is a lower-level version, which will rarely be needed by the average programmer. The module called TerminationControl provides a means by which any user-written module can declare a "clean-up" routine to be invoked when the program terminates (either normally, or on an error). Multiple calls to this module are possible, so it is quite possible for each module to have its own separate termination procedure. If a termination handler itself installs another termination handler, this forces an extra pass through the termination handling. Module Bounce is for demonstration and testing: it shows a text-mode bouncing ball on the screen. The "module" called Skeleton is not in fact a module. It is simply a file which may be copied to simplify the typing effort when creating a new module. 16. Disclaimer The PMOS system is an experimental package which is under active development, and there is no guarantee of upward compatibility. The information herein is accurate at the time of writing, but changes are likely to occur in future versions. If you run into version compatibility problems, check the source file of the current version in the PMOS library. While all care has been taken to avoid software errors, no responsibility can be accepted for reliability of the software. It would be appreciated if any errors found could be reported to the author of this document. 17. Distribution information PMOS is available for anonymous ftp at address ee.newcastle.edu.au, in directory pub/PMOS. For a list of alternative sites, contact the author. PMOS is free for non-profit use. Further information may be found in the README file which is distributed with the software.