Support for software engineering using MODULA-2 With its high level of problem decomposition and abstraction, MODULA-2 was designed to be used not just to generate code but also at the software design stage. Joe Gallacher explains how the language can enhance the programmer's craft
Continuing a series on modem high-level languages the paper describes the features of MOOULA-2 which are important in industrial systems programmin~ These features include the creation of libraries of reusable software, the ability to perform low-level programming and support for concurrency. MODULA-2supports good software engineering practice by providing facilities for program decomposition, separate compilation and data abstraction. The role of the module concept and the export/import mechanism in providing these features is examined. Program modules, the definition and implementation parts of library modules and local modules are described. high-level languages
MODULA-2 software engineering
MODULA-2 is now an important industrial systems programming language. It was designed by Professor Niklaus Wirth at the ETH (Swiss Federal Institute of Technology)in Zurich, and the ETH compilerwas released in March 1981. Since then MODULA-2 has become available on a number of mini- and microcomputers. MODULA-2 draws on the insights Wirth gained in designing PASCALand MODULA, tWO earlier programming languages. The features of MODULA-2that are important for industrial systems use are the module concept, the ability to perform low-level programming and the support for concurrency. The module concept extends PASCAL upwards away from the machine, and supports modern software engineering principles. MODULA-2 also extends PASCALdownwards towards the machine, with low-level features similar to those found in c. It is important to put MODULA-2 in perspective. Most of the principles of software engineering may be applied in any high-level language. The language itself is a tool that can assist or hinder the programmer, that can be used well or poorly. MODULA-2 is a well designed language that Microprocessor Systems Engineering Ltd., Craigavon, Boltachan, Aberfeldy, Perthshire PH15 2LB, UK Next paper in the series: John Barnes on ADA (June 1987)
supports a methodology which enhances the programmer's craft. It is not 'just another programming language' and should not be studied as such. It promotes a certain way of thinking about software development, and may be used at the design level. The architecture of a software system may be defined using MODULA-2 as a program design language. The MODULA-2 design may then serve as the framework which supports the final software implementation. MODULA-2 supports a high level of problem decomposition and abstraction, which leads to more efficient and reliable problem solving. Decomposition is the process of reducing a large complex problem to a set of smaller, more manageable problems; this ability to separate the specification of an abstraction from its implementation is one of the key features of MODULA-2. The definition of MODULA-2 is set out in Wirth 1.
PROGRAMS, PROCEDURES AND MODULES A MODULA-2program consists of one or more modules, the feature which gives the language its name. Modules are classified as program, internal, definition or implementation modules. An example of a MODULA-2 program module is given in Figure I. Note that the first word is MODULE, and that the identifier at the end of the module is the module's name repeated. This simple example uses a number of basic programming constructs and also illustrates some more advanced features such as the function procedure 'Max' and the importing of procedures from a library module (in this case the module 'InOut' which is provided by the compiler implementers).
Input/output Module 'lnOut' is provided as a result of the MODULA-2 I/O philosophy. An objective of MODULA-2 is t o provide an efficient high-level systems programming language that is essentially machine independent. Input and output of
0141-9331/87/04197-08 $03.00 © 1987 Butterworth & Co. (Publishers) Ltd
Vol 11 No 4 May 1987
197
MODULE FindMax; (*Accepts a stream of integers, terminated in zero, and outputs the one which is the greatest*) FROM InOut IMPORT Readlnt, Writelnt, WriteLn, WriteString; VAR NextNum, PrevHigh:1NTEGER; PROCEDURE Max (x, y:INTEGER):INTEGER; BEGIN IFx>=y THEN RETURN x ELSERETURN y END (*IF*) END Max; BEGIN (*Main program*) WriteString ('Enter a stream of integers, ending in a zero'); WriteLn; PrevHigh:= 0; NextNum:= -1; WHILE NextNum < > 0 DO Readlnt (NextNum); WriteLn; PrevHigh:= Max (NextNum, PrevHigh) END; (*WHILE*) WriteString ('The largest number in the stream is'); Writelnt (PrevHigh, 6); Write Ln END FindMax.
Figure 7.
Example of a MODULA-2 program
data have long been a problem for programming language designers, as the input and output processes are heavily machine and I/O device dependent. MODULA-2 meets its objective by having no I/O statements as part of the language. Instead, library routines for handling input and output are provided with each implementation of MODULA-2. These routines are written for each specific machine and operating system on which the implementation will run. In this way, I/O functions are available to MODULA-2although they are not part of the basic language. Wirth 1 has recommended a number of standard library modules to be provided as part of a MODULA-2implementation. However, most implementations include additional library modules designed to enhance the particular implementation, particularly with regard to screen and file handling. These implementation-dependent library modules cause considerable difficulties with regard to the portability of programs. Work is under way, by such bodies as the International Standards Organization and the British Standards Institute, to produce a standard library of modules.
PROCEDURES There are three problem areas related to the production of large complex programs. These are • program size • level of detail • adverse side effects on program variables by actions in various parts of the program The solutions to these three problem areas are decomposition of the program into a number of smaller and more manageable pieces, the concept of data abstraction to omit unnecessary detail at a particular level, and control
198
of the visibility oi program elements. MODUi k-.~ s 3trengtf~ lies in the features of the language which suppor~ !he above capabilities. These features are the procedure af3(:[ the module, and their ability to control the visibility ~Jt program parameters. Procedures are a very important pan of a programming language, and programming without them is an almost unthinkable process.
MODULA-2 procedures MODULA-2provides two types of procedures: the procedure and the function procedure. With a few minor differences the structure of a procedure is identical to that of a program. Some of the main differences are as follows~ • The procedure begins with the word PROCEDURE instead of MODULE and ends with a semicolon rather than a full stop. • A procedure cannot import library routines. • Procedures can be nested. • A procedure can have a set of formal parameters. By declaring a procedure, a new statement is defined in the language that consists of the procedure followed by a parameter list (if required). The statements are called 'procedure calls' and may appear anywhere that another MODULA-2 statement could appear. Procedure calls stand for a sequence of statements which are to take the values given and perform some actions with them. Function procedures, on the other hand, return values. It will be seen that procedures allow the process of decomposition, i.e. the program is divided into manageable pieces. They also support the concept of data abstraction, i.e. using a procedure call need not involve knowledge of how the procedure carries out its function. The following are some examples of procedure and function procedure headings. PROCEDURE Readlnt (VAR x:INTEGER); (*This procedure has one variable parameter*) PROCEDURE Twolnt (VAR x, y: INTEGER); (*This procedure has two variable parameters*) PROCEDURE NoPar; (*This procedure has no parameters*) PROCEDUREOneEach (VARx :CARDINAL; y:CARDINAL); (*This procedure has one variable parameter x, and one value parameter y*) PROCEDURE UnCertain (VAR x:CARDINAL; y:CARDINAL):CARDINAL; (*This is the heading of a function procedure which may cause side effects because it uses a variable parameter*) Function procedures always return a single value. Therefore it is generally accepted practice never to allow a function procedure to change the value of one of its parameters or to change the value of a nonlocal variable. Thus, function procedure parameters are normally restricted to value parameters.
Visibility As previously stated, program side effects must be controlled. In practice, an adverse side effect is caused by changing the value of a variable in one part of a program without realizing that the same variable is used in another
Microprocessors and Microsystems
program location. The variable in the second location is then used without realizing that its value has been altered. The ability to divide a program using procedures is one step in guarding against side effects. However, the real point in using procedures to guard against these problems is in the ability to control the scope of variable validity or visibility. MODULA-2 has rules that govern which variables are visible in which part of a program. If a variable cannot be seen, then it cannot be changed. Therefore one guard against side effects is to restrict the scope of the program in which the variables can be accessed. A variable's visibility is governed by where the variable is declared. A variable exists only within the procedure in which it is declared. The procedure in which the variable is declared is called the variables scope. The variable is said to be local to the procedure. In contrast to local variables there are global variables, which are declared in the outermost level of the program. Since a local variable exists only within the procedure in which it is declared, no other part of the program can change the value of this local variable. The compiler will catch all invalid references to a local variable. A global variable, on the other hand, may be used throughout the program. One last important point concerns the period of existence of local variables. Local variables do not exist except when their surrounding procedure exists. Thus the value of a local variable disappears when the procedure is finished. This means that if it is required that certain variables within a procedure retain their values from one call of the procedure to the next, then a local variable cannot be used. The only alternative is to use a global variable. This topic will be returned to in the discussion on modules.
Procedure types A distinctive feature of MODULA-2 is the procedure type which provides the ability to declare variables whose values are subprograms. Procedure types make procedure variables possible. Procedure variables take procedures and functions as their values. Procedure types are useful because certain programs and procedures depend upon other procedures, but the specific dependencies are not known until run time. Procedure variables may be used as independent variables and as formal subprogram parameters. The following example, although trivial, illustrates the use of a procedure type as an independent variable. It assumes that functions SIN and COS have been defined. TYPE RealFunc = PROCEDURE (REAL): REAL; VAR trig: RealFunc; x, y:REAL; trig:= SIN; y:= trig(x); trig:= COS; y:= y + trig(x) The following headings illustrate the use of a procedure as a formal procedure parameter. TYPE ReaIFunc= PROCEDURE (REAL):REAL; PROCEDURE Integrate (f: RealFunc; lower, upper: REAL);
Vol 77 No 4 May 1987
MODULES In the previous section on procedures it was considered how procedures assist in mitigating the problems of program size, level of detail and adverse side effects. Procedures allow the program to be divided into small and easily verifiable pieces, thus easing the programmer's intellectual task. However, procedures are not the complete answer to the problem. The procedures are still contained within the body of the program and contribute to its overall size. Also, since the procedure remains integral to the program, extreme care must be taken in the partitioning of programming assignments to a team. Furthermore, a change to a procedure requires the recompilation of the complete program. Modules contribute to the solution of these remaining program size problems by allowing the separate development of program components which may be kept in libraries. The adverse side effects may be reduced by the use of procedure local variables. However, as was discussed in the previous section, there are instances where global variables, a major contributor to side effects, cannot be eliminated. Modules provide the capability to eliminate almost all factors which lead to side effects. This is done by supporting the concept of data hiding, which is one step beyond data abstraction. Abstraction allows the programmer to ignore details. Hiding prevents the programmer from knowingthe details. The problems with procedures that required continued use of global variables arose from the fact that the life of a procedure's local variable is tied to the life of a procedure. In contrast, variables that are local to a module have an existence that is separate from the execution of the module. There are three basic types of modules: program modules, library modules and local modules. Program modules are complete program units that may import software resources from library modules. Program modules were discussed above. Library modules have two parts that must be compiled separately: a definition module and an implementation module. Library modules are created with the intention of exporting reusable software components to other modules. They also usually import software components. Local modules may be defined within the body of either program modules or implementation modules. They are usually used to control the scope and visibility of objects in the compilation unit in which they are defined.
Imports and exports The program module FindMax (Figure 1) gives an example of an import list, i.e. FROM InOut IMPORT Readlnt, Writelnt, WriteLn, WriteString; The import list is the mechanism by which a module can access elements from other modules. It assists with the problems of visibility because modules cannot see anything outside the module unless they explicitly import it. Before an item from one module can be imported by another module, the item to be imported must appear on the first module's export list. Program modules can import only. Library and local modules can export and import. Imports are accomplished in two ways: one as already
199
illustrated where an explicit list of the identifiers to be imported is given, the other as IMPORT InOut; In this case every item on the InOut export list is implicitly imported. These are Done, EOL, term CH, Closelnput, CIoseOutput, Openlnput, OpenOutput, Read, ReadCard, Readlnt, ReadString, Write, WriteCard, WriteHex, Writelnt, WriteLn, WriteOct, WriteString. When this second import option is used the identifier name must be qualified. For example, in the program FindMax (Figure 1), 'WriteString' would now be written 'lnOut.WriteString'. The other imports would be similarly qualified.
Library modules A major feature of MODULA-2 is the ability to store separately compiled routines in library modules. Commonly used procedures need only be written and compiled once. Thereafter they are available for use by any module. This contributes to the efficiency of the development process and the reliability of the software. The library module is used at two different times. When a client program of a library routine is compiled, the compiler requires only the precompiled declarations for the exported items. When the client program is executed, the precompiled object code from the library module is linked to the object code of the client program. The important point to note here is that compilation of the client program does not require access to the library module's source or object code. Therefore the client program is totally decoupled and independent of the implementation of the library module. Library modules fall into two categories, definition modules and implementation modules. An important point must now be made before going on to discuss definition modules. In the third, corrected edition (1985) of Wirth 1, a number of alterations are made to the language definition. One of these is that 'EXPORT QUALIFIED' has been eliminated. Under this new definition, definition modules imply the use of qualified exports and an export list is not required. All the declared objects are exported. For compatibility with existing programs, an export list will be treated as a comment. However, the changes have not yet been implemented in all compilers and most of the literature on the language is based on Wirth's 1983 second edition of the language definition 1. Therefore, in this paper, EXPORT QUALIFIED lists are shown but are marked as comments as a reminder of the change in the third edition. The following comments on definition modules should be read in the context of the foregoing remarks.
Definition modules The mechanism that makes possible the decoupling of client and library modules, and hence supports data abstraction and data hiding, is the fact that each library module consists of two parts. These are a definition module and an implementation module. An example of a definition module is
200
DEFINITION MODULE Libl I~ FROMLib0 IMPORT SHORTSTR, Shrtstrglen; (*EXPORT QUALIFIED MakeHyptList;*) PROCEDURE MakeHyptList (symboI:SHORTSTR): END Lib11. Module Lib11 imports from Lib0, the definition module of which is DEFINITION MODULE Lib0; (*EXPORT QUALIFIED SHORTSTR, Shrtstrglen, HYPTPTR, HYPTTYPE, hypthdr, hypttail;*) CONST Shrtstrglen = 60; TYPE SHORTSTR = ARRAY [0.. Shrtstrglen] OF CHAR; HYPTPTR = POINTER TO HYPTTYPE; HYPTTYPE = RECORD symboI:SHORTSTR; next : HYPTPTR; END; VAR hypthdr, hypttail: HYPTPTR; END Lib0. The definition module is made up of exported objects and the declarations for these objects. The word DEFINITION precedes the definition module heading. A definition module can have import lists. In the interest of minimizing interdependencies, the definition module should import only those objects which it requires. Definition module Lib11 must import from Lib0 in order to support the PROCEDURE MakeHyptList declaration. The use of QUALIFIED provides a way to handle identifier name conflicts when more than one module exports an object with the same name. This conflict can arise where programmers do not know what identifiers are likely to be used in other programmer's modules. Because of this danger of identifier clashes when using library modules, QUALIFIED exports are mandatory in a definition module. Similarly, modules importing objects from library modules must use qualifying identifiers.
Implementation modules Implementatio~ module Lib11 is as follows. IMPLEMENTATION MODULE Lib11; FROM Lib0 IMPORT SHORTSTR, Shrtstrglen, HYPTPTR, HYPTTYPE, hypthdr, hypttail; FROM Storage IMPORT ALLOCATE PROCEDURE MakeHyptList (symbol: SHORTSTR); VAR hypt: HYPTPTR; BEGIN NEW (hyp0: IF hypthdr = NIL THEN hypthdr:= hypt; hypttail:= hypt; ELSE hypttail ".next:= hypt; hypttail:= hypt; END; (*IF*) hypttail'.symbol := symbol; hypttail ".next:= hypthdr; END MakeHyptList; END Lib11.
Microprocessors and Microsystems
An implementation module is one half of a library pair. It is identified as such by using the same module identifier as the corresponding definition module, but the word MODULE is preceded by IMPLEMENTATION. Every procedure heading that is in the definition module must appear in the implementation module; this includes the repetition of the formal parameter list exactly as it appears in the definition module. None of the other definition module declarations are allowed to be repeated in the implementation module and are regarded as automatically imported.
Opaque types MODULA-2 provides both transparent and opaque types. Both may be used to define an abstract data type. The opaque export stands in contrast to the transparent export. A type definition may consist of a full specification of the type, in which case its export is said to be transparent. Alternatively it may consist only of the type identifier, in which case its export is said to be opaque. In the latter case the full specification must appear in the corresponding implementation module. The type is known to the importing client modules by its name only, and all its properties are hidden. Opaque export is restricted to pointers. In the DEFINITION MODULE Lib0 above, an example of a transparent export is given by TYPE HYPTPTR= POINTERTO HYPTTYPE; HYPTTYPE = RECORD symbol :SHORTSTR; next: HYPTPTR, END; The corresponding IMPLEMENTATION MODULE Lib0 does not, and may not, contain detail of the TYPE HYPTPTR, i.e. IMPLEMENTATION MODULE LibO; END Lib0. However it would be possible to declare HYPTPTR as an opaque type in the DEFINITION MODULE Lib0 as TYPE HYPTPTR; In this case the implementation module would have to contain the complete specification of the opaque type HYPTPTR, e.g. IMPLEMENTATION MODULE LibO; TYPE HYPTPTR = POINTERTO HYPTTYPE; HYPTTYPE = RECORD symbol :SHORTSTR; next: HYPTPTR; END: END LibO.
Local modules The question of the visibility of identifiers has already been discussed. Modules offer advantages over procedures in controlling the visibility of program elements. The advantage of a module is due to the fact that the
Vol 17 No 4 May /987
existence of a module and its local elements is tied to the existence of the module's surrounding environment. Therefore, if a module is nested within another module such as the program module, its variables exist throughout the life of the program. The separation of a variable's existence from the existence of its scope is important in the case of procedures that depend upon the value of a variable being preserved between procedure calls. The reason for the existence of local modules is to deal with the identifier visibility problem within either program or library modules. Outside items must be imported by the local module before they can be seen inside the module. Similarly, inside objects must be exported before they can be seen outside the module. Figure 2 gives a simple illustration of the principles of using local modules. Although qualified exports are used in this illustration, they should not normally be needed for local modules because the programmer knows the environment and can therefore choose identifiers which avoid name clashes. VAR a in Locl exists for all the time that the surrounding environment, e.g. the main program, exists. During this time its value is presewed between calls of Procl. It is necessary to export VAR c in the nested module Loc3 for itto be visible in Loc2. The export VAR c from Loc4 must be qualified to avoid a name clash with VAR c in Loc2. The other qualifications are not strictly necessary and are merely for the purposes of illustration.
MODULE LocModDem; MODULE Locl; EXPORT QUALIFIED Procl; VAR a:INTEGER; (*Hidden*) PROCEDURE Procl ():INTEGER; BEGIN END Procl; END Locl ; MODULE Loc2; EXPORT b, c; VAR b: INTEGER; MODULE Loc3; EXPORT c; VAR c:INTEGER; END Loc3; END Loc2; MODULE Loc4; EXPORT QUALIFIED c, d; VAR c, d:INTEGER; END Loc4; MODULE Loc5; EXPORT QUALIFIED e, f; VAR e, f: INTEGER; END Loc5; MODULE Loc6; IMPORT Loc2; (*unqualified import of b and c*) IMPORT Loc4; (*qualified import of c and d*) FROM Loc5 IMPORT e: (*qualified import of e*) BEGIN e:= b + c; (*qualification unnecessary*) b:= c + Loc4.c; (*qualification required*) END Loc6; END LocModDem. Figure 2.
Example of a local module
20I
SOFTWARE ENGINEERING AND MODULA-2 It was stated in the introduction to this paper that supports good software engineering. A brief discussion of this would seem to be in order.
MODULA-2
Software engineering Software engineering is a term which came into being in the late 1960s. There is, to date, no generally accepted definition of the concept, but it can be defined as follows. 'Software engineering is the practical application of scientific understanding to the economical production and use of reliable software.' Like other large projects, software projects can be split up into phases. These phases can collectively be described as the software life cycle and consist basically of • • • • • •
user requirement specification functional specification design implementation test maintenance
Software design Software quality is strongly influenced by the quality of the design. Producing good software requires the mastering of complexity. One of the most important principles for mastering complexity is the principle of abstraction. In software engineering the purpose of designing is to develop an algorithmic solution for a specified problem. One method of proceeding with this development, advanced by Wirth 2, is to decompose the task into subtasks, and then consider each subtask and decompose it into subtasks again, this process continuing until the subtasks become so simple that they can be formulated algorithmically. Wirth calls this principle the method of stepwise refinement. The next concept to be considered in software design is the structuring of flow control. A well accepted method for the construction of flow control is structured programming. The basic idea is to ensure a correspondence between the static notation of an algorithm and its dynamic execution. The most important point is to avoid unbounded flow structures. Unbounded flow structures lead to a quadratic growth of paths in the flow control of an algorithm. In this process, the correspondence between static and dynamic control structures is lost. The algorithmic solution of any arbitrary task can be described by a combination of sequence, selection and repetition. Each of these functions has only one entry and only one exit. The statements available in programming languages which support these functions are • • • • • •
assignment procedure call IF-THEN-ELSE WHILE LOOP FOR
Modularization is one of the important principles of the design phase. The process of stepwise refinement divides
202
a large program into a number ot smaller units, it i.~ nuv, necessary to consider the criteria which should be met b.~ give satisfactory decomposition of the ~ystem, .~ satisfactory interfaces between the units. The criteria are as follows. • A module is a program segment that communica[es only through a clearly defined interface. • Every module should realize a task which forms a closed entity. • Each module should be concise. • Each module should be constructed so that its correctness can be determined simply by consideration of its interface definition. • The decomposition should guarantee that modules do not exert any internal influence upon one another, and that a module may be replaced by another so long as the original interface is respected. The remaining important concept for good software engineering is data abstraction. In the design of large programs it is important not to consider any particular representation of data prematurely. It is therefore useful to apply the principle of stepwise refinement to the design of the data structures, i.e. to use abstract data structures. An abstract data structure is a set of objects and a set of operations which can be applied to the objects or to the data structure as a whole. In many cases it is desirable to generate a number of instances of a particular abstract data structure. This leads to the concept of an abstract data type which can be defined as follows. "An abstract data type defines a set of objects, which all have the same abstract data structure, by the operations applicable to them.'
LOW-LEVEL FACILITIES Certain types of software, e.g. operating system software, need to manipulate data as uninterpreted bit patterns, to manage memory, and to deal with the physical addresses of I/O devices. MODULA-2 provides a number of features for low-level machine access. Foremost is the facility to suspend MODULA-2'S strong type checking. This may be done using the type transfer function. Type identifiers can be used as function identifiers to denote a type transfer function. Such a function is considered as transferring a value of the type given by the parameter into the corresponding value of the type specified by the function identifier, e.g. the value of an expression c of the type CARDINAL is mapped into its corresponding value of the type BITSET by the function BITSET(c). A type transfer function can be used with any data types which occupy identical amounts of memory, including those defined by a programmer. The data type BITSET in MODULA-2provides a means to name and access individual bits. By using type transfers, data can be transferred from other types to the type BITSET. Many of the low-level features of MODULA-2 are exported from the pseudomodule SYSTEM, which is not actually a separate pair of library modules but is effectively part of the compiler. Any module which imports something from SYSTEM may be system dependent. SYSTEM may be considered as if it was defined as follows.
Microprocessors and Microsystems
DEFINITION MODULE SYSTEM; (*EXPORT QUALIFIED WORD, ADDRESS, PROCESS, ADR, SIZE, TSIZE, NEWPROCESS,TRANSFER;*) TYPE WORD; ADDRESS; PROCESS; PROCEDUREADR (x:AnyType):ADDRESS; PROCEDURESIZE (VAR y:AnyType):CARDINAL; PROCEDURETSIZE (AnyType):CARDINAL; PROCEDURENEWPROCESS(P:PROC; A:ADDRESS; n:CARDINAL; VAR q: PROCESS); PROCEDURETRANSFER(VAR p, q: PROCESS); END SYSTEM. The data type WORD provides a way to define formal parameters that may be bound to actual parameters of any data type. This capability allows the development of generic data and functional abstractions. The use of this low-level abstraction is another way of circumventing MODULA-2type checking. The data type ADDRESS and the procedure ADR permit manipulation of machine memory addresses, facilitating the implementation of all kinds of dynamic data structures. The procedures SIZE and TSIZE provide a way of determining the size of data objects. PROCESS, N EWPROCESS and TRANSFERwill be discussed in the next section.
CONCURRENCY An important feature of MODULA-2 is its support for multiprogramming or multitasking, i.e. where several programs may be present in the computer at the same time and each is allowed to run for a short period of time either in sequence or under some form of priority control. Multitasking gives the appearance of simultaneous execution of the programs, because of the speed of computers, so that, although multiprogramming is actually quasiconcurrent, it is normally spoken of as concurrent programming. MODULA-2 provides a small set of primitives for concurrent programming. These are defined in the module SYSTEM and are the type PROCESS and the procedures NEWPROCESS and TRANSFER.The MODULA-2 tasks that operate concurrently are called coroutines. Each coroutine that acts independently of the remainder of the program is called a process. The NEWPROCESS procedure creates in a process variable the appropriate status for that process to begin. A subsequent transfer to that process starts execution at the first statement. Thereafter, each transfer to that process resumes where it relinquished control. The TRANSFER procedure is implemented so that the execution status of the current routine p is assigned only after the current value of q is accessed. Thus the statement TRANSFER(x, x) is legal. Wirth 1 recommends the provision of the module Processes to synchronize concurrent processes. This module is defined in Figure 3. Given these routines, a more sophisticated form of concurrent processing than is available through just the use of the procedure TRANSFER can be programmed. An interesting example of a small nucleus suitable for a multitasking realtime control application is given by Thomas 3.
Vol 11 No 4 May 1987
DEFINITION MODULE Processes; (*EXPORT QUALIFIED SIGNAL StartProcess, SEND, WAIT, Awaited, Init;*) TYPE SIGNAL; PROCEDURE StartProcess (P: PROC; n: CARDINAL); (*Starts a concurrent process with procedure p and workspace of size n. PROC is a standard type defined as PROC = PROCEDURE*) PROCEDURESEND (VAR s:SlGNAL); (*Sends a signal; causes one of the processes waiting for signal s to resume execution*) PROCEDUREWAIT (VAR s:SIGNAL); (*Suspends execution of the process until an s signal is received*) PROCEDUREAwaited (s:SIGNAL):BOOLEAN; (*Returns a TRUE if at least one process is waiting for signal s*) PROCEDURE Init (VAR s:SIGNAL); (*Initializes the signal variable s. Every signal variable must be initialized with this procedure before it is used*) Figure 3.
Processes module
DEVICE HANDLING A N D INTERRUPTS The handling of hardware interrupts from peripheral devices such as keyboards depends upon the computer interrupt system and the peripheral device and its interface. The proper use of a monitor, or task scheduler, requires a detailed knowledge of the computer interrupt system. However, it is useful to look at the facilities which are available for handling interrupts in MODULA-2. The first feature is absolute memory addressing. In many modern computers an interrupt causes a transfer of control to a fixed memory location, or to the address contained in a fixed memory location. Therefore it must be possible to put an instruction or address into a specified memory location. The absolute addressing feature permits this to be done. At a higher level of abstraction, MODULA-2 provides another way of accessing interrupt handlers. The interrupt handler is viewed as a process. When an interrupt occurs, tl~e system behaves as if the currently executing program had performed a process transfer to the interrupt handler. When the interrupt handler finishes its task, it transfers back to the program which was interrupted. An external signal from a device initiates the transfer to the interrupt handler. For this to be possible, there must be away of associating a process with a particular interrupt address, and a way to allow that process to transfer back after the interrupt has been handled. This capability is provided in MODULA-2 by the procedure IOTRANSFER in the module SYSTEM.This is an extension of the procedure TRANSFER and is declared as PROCEDURE IOTRANSFER(VAR p, q: PROCESS; VAR vector:CARDINAL); As in TRANSFER this procedure suspends the current process p and transfers to process q. In addition, the specified interrupt vector is set up so that, when an interrupt occurs, q is suspended and p resumed. As already discussed, MODULA-2 represents processes as coroutines sharing a single processor, and provides a type PROCESSto which can be assigned the current state of a process when transfer to another process is required.
203
To use IOTRANSFER, the main program makes a call to NEWPROCESS to create a process for the interrupt handler, e.g.
Joe Gallacher is a director oi Microprocessor System> Engineering Lid, a company that consults in the design and production of software. His interests are in operating systems, systems software, the high-level languages PASCAL, MODULA-2 and ADA, and the control of software projects. He is ~ past member of a number of tEE specialist committees on control engineering and microcomputer application.
NEWPROCESS (KBDriver, ADR (keybdWSP), SIZE (keybdWSP), keyboard); By this call the process keyboard is set up but not executed. The process is activated by a call to TRANSFER, e.g. TRANSFER (main, keyboard); This call interrupts the current process and activates the process 'keyboard'. The state of the current process is stored in 'main'. The interrupt handler keyboard, having completed whatever initialization is necessary, relinquishes control back to the main process with the statement IOTRANSFER (keyboard, main, vector); This procedure suspends the current process, assigns it to keyboard, and transfers to the main process. In addition, the specified interrupt vector is set up so that when an interrupt occurs, main is suspended and keyboard resumed. Thus an interrupt behaves as if the statement TRANSFER (main, keyboard) is suddenly executed in the current program. Using the concept of process abstraction, IOTRANSFER can be thought of as performing a wait operation on a signal that can be sent only by an external device. The fact that an interrupt can occur at any time can cause problems in a software system. The problem is that an interrupt can occur when a process is in its critical section, e.g. before the vector is set up. Therefore it is necessary for a process to have some means of preventing interrupts while it is in its critical section. This is done by enclosing the process within a module with a priority level, specified in its heading, such that hardware interrupts are postponed until the priority is lowered. This shutting out of interrupts is a simple technique for achieving mutual exclusion.
CONCLUSION The cost of software systems has been steadily increasing. whilst the cost of hardware has been decreasing. Important goals of software engineering are the reduction in the life cycle cost associated with software development and the improvement of software reliability. Programming languages have a prime role to play in supporting more cost-effective development of reliable' software. MODULA-2and ADA have been created specifically to meet the above stated goals.
204
MODULA-2 promotes a good approach to problem solving and software development. It can be used as a program design language, allowing the architectural frame of a software system to be created before the program met becomes concerned with the implementation details. This separation of specification and implementation supports the achievement of good quality software.
REFERENCES 1
2 3
Wirlh, N Programming in Modula-2, 2nd edition (1983); 3rd corrected edition (1985) Springer-Verlag. Heidelberg, FRG Wirth, N 'Program development stepwise refinement' Commun. ACM Vol 14 No 4 (1971) Thomas, H W 'Design and performance of a simple nucleus for real-time control applications' Software Microsyst. Vol 1 No 6 (1982) pp 160-166
BIBLIOGRAPHY Gleaves, R Modula-2 for Pascal programmers SpringerVerlag, Heidelberg, FRG (1984) Fordr G A and Wienert R Modula-2: a software development approach John Wiley, New York, USA (1985) Knepleyt E and Plait, R Modula-2 programming Reston Publishing, Reston, VA, USA (1985) Ogilvie, J W L Modula-2 programming McGraw-Hill, New York, USA (1985) Wiener, R and Sincovec, R Software engineering with Modula-2 and Ada John Wiley, New York, USA (1984) Pomberger r G Software engineering and Modula-2 Prentice-Hall, Englewood Cliffs, N J, USA (1984)
Microprocessors and Microsystems