crb@SUN.COM (Chuck Bilbe) (11/20/86)
In the course of implementing the Modula-2 language on the Sun workstation under the Unix (TM) operating system, I have been faced with the problem of how to deal with the coroutine mechanism (Wirth's PROCESS type). There are a couple of aspects of the mechanism as I understand it which just don't lend themselves to an obvious interpretation in an environment like this. Specifically, I am looking for your input on the meaning of process priorities ( syntax "MODULE" <ident> "[" <ConstExpression> "]" ";" ) and the IOTRANSFER procedure on a system like Unix where programs outside the kernel cannot access hardware, prevent interrupts, or change machine interrupt response priority levels. The following approaches suggested themselves to me, but undoubtedly there are many others: A . Go ahead and generate code for the bare-bones machine, force the user to go through the process of linking kernel-mode code, and make NEWPROCESS, TRANSFER, and IOTRANSFER part of a nonstandard Unix kernel. Although to the uninitiated, this might look like just what the doctor ordered, it's much easier said than done, and the risks are immense. It becomes very difficult to manage the asynchronism between new releases of the operating system and any updates to the compiler. B . Take the other extreme and forbid process priorities and the IOTRANSFER procedure altogether; Implement NEWPROCESS and TRANSFER only, don't let Unix know that the stack pointer of the process is being played with, and let the code run entirely in user mode. It is now easy to share an address space between coroutines, because all coroutines in the user's Modula-2 program are a single Unix process. I get the feeling that this is what Dr. Wirth would recommend, as he states (ed. 1 & 2 PIM2) that the IOTRANSFER procedure is PDP-11 specific. It has the disadvantage that Modula-2 programs cannot do explicit rescheduling of other coroutines based on asynchronous external events. C . Use IOTRANSFER as a mechanism for waking up a coroutine on a Unix signal, treating Unix signals as if they represented the hardware interrupts implied by IOTRANSFER. Mask and unmask these signals as implied by coroutine priorities, assigning to each of the huge multitude of Unix signals its own priority level, in some arbitrary order. Note that with this implementation, the entire family of coroutines will still go "to sleep" if one of them performs a system call like "read", for example. Another difficulty is the time-dependent and non-atomic interactions between signals and Unix processes. Finally, Modula-2 programs wouldn't work well in harmony with C functions that play with signals outside the domain of the Modula-2 compiler. D . Assert that the coroutine abstraction isn't very useful if you already have a more powerful operating system, and instead let the users use the ordinary "fork/exec" mechanism to create "real" Unix processes. Leave NEWPROCESS, TRANSFER, and IOTRANSFER out altogether, or let users build them themselves. In the 1.0 implementation of Modula-2 on Sun, recently introduced, I have chosen approach B because it involves the least risk and provides reasonable functionality while allowing migration to the other alternatives later if that looks advisable. If I get significant levels of consensus from you users out there, it is probable that our implementation can be enhanced in the future. Fire away, and thanks in advance! Chuck Bilbe cbilbe@Sun.COM Project Leader, Modula-2 Sun Microsystems, Inc. Mountain View, CA.
pederch@daimi.UUCP (Peder Chr. N|rgaard) (11/24/86)
At out site we have used a Modula-2 implementation for about a year. Based on this experience we will urge you to stick to approach B, that is, implement NEWPROCESS and TRANSFER only, and forget everything about IOTRANSFER, which doesn't fit into the UNIX environment at all. The ar- gument in your point D just does not hold: UNIX processes have too much overhead to be used extensively as a control structure tool. Actually, we use the NEWPROCESS/TRANSFER mechanism not only as a part of Modula-2 system. Personally, I have written two systems in C, one of those a SunTool, both centered around a scheduling select(2) call, using the coroutine mechanism to simplify control structures which would otherwise have to be implemented using a lot of global state variables. Furthermore we have implemented a simple light-weight process kernel in Modula-2 based on the coroutine concept. The only serious problem we have run into is this: how do you estimate the size of the stack needed for a co- routine? and, worse, how do you ensure that your estimate was large enough? To the best of my knowledge, the 68000 processsor has no built-in stack overflow test. This is no problem when you have only a few coroutines - in a virtual memory you can just give them each a Megabyte. But problem arises when you wish to use, say, a 1000 coroutines in the same process. Peder Chr. N|rgaard Computer Science Department Aarhus University Denmark
paul@hslrswi.UUCP (Paul Breslaw) (11/27/86)
In article <8611201656.AA00169@odysseus.sun.uucp> crb@SUN.COM (Chuck Bilbe) writes: > The following approaches suggested >themselves to me, but undoubtedly there are many others: >A . ..... make NEWPROCESS, TRANSFER, and IOTRANSFER > part of a nonstandard Unix kernel. >B . ... forbid process priorities and the IOTRANSFER procedure > altogether; Implement NEWPROCESS and TRANSFER only. >C . Use IOTRANSFER as a mechanism for waking up a > coroutine on a Unix signal, treating Unix signals as > if they represented the hardware interrupts implied > by IOTRANSFER. Mask and unmask these signals as > implied by coroutine priorities >D . Assert that the coroutine abstraction isn't very > useful. Well I would choose C. In fact I already have, and it seems to work nicely. But then one asks 'nicely for what?'. Our use of Modula-2 is primarily for embedded real-time systems, which use real-time kernels designed specifically for each application. Naturally the kernels are themselves written in Modula-2. Our development environment is 4.2, on which we have both host and cross development tools. Our goal is to develop as much as possible of the cross software on the host, before committing it to target hardware. To this end we want to be able execute all the concurrency facilities of Modula-2 on the host with a semantics that is a close as possible to that which is found on the target. This excludes options D (co-routines are VERY useful) and B (we need to execute the target software as closely as possible on Unix). This is our primary aim. But since Modula-2 is also the language in which all the tools are written (compilers, linkers, debuggers, configuration management, etc), it must also be useable for implementing Unix applications. We view Modula-2 as a system executing within a given environment. This can mean 'no environment' in the case of a naked machine, or the Unix environment in the case of Unix. These we take as given, hence there is no need or wish to change them. If you like, the environment is the virtual machine. This excludes option A. Now naked machines have interrupts as their form of external (asynchronous) event. Unix (or to be more precise 4.[23]bsd) has signals as its external events. Hence IOTRANSFER is interpreted as IOTRANSFER (VAR from, to : PROCESS; externalevent : CARDINAL) where 'externalevent' is an interrupt vector on an Intel 80xxx, an interrupt vector address on a PDP/LSI-11, or a signal number on 4.x. Priorities are interpreted according to the dictates of the environment, exactly as Wirth originally specified they should be. So on an Intel machine, the priorities are what you do with your interrupt controllers - this is why Logitech provide their customers with a target run-time system in source form. On a PDP-11 it's the priority that you set on each physical interface. On an LSI-11 all devices have the same hardware priority. On 4.x all signals have the same priority - see sigvec(2), so I do not see the need to assign > the huge multitude of Unix signals its own priority > level, in some arbitrary order. The semantics I use only implicates those signals which use or have used IOTRANSFER. These are blocked/unblocked as the flow of control enters/exits high priority code, irrespective of the priority. Other signals are completely unaffected. This reduces the objection > Modula-2 programs wouldn't work well in > harmony with C functions that play with signals > outside the domain of the Modula-2 compiler. to something of a straw man. Who, for example, would write a conventional real-time program where separate parts play independent games with the same interrupt vector? Caveat programmer ! The next comment seems to reveal more a mixing of programming styles than a genuine difficulty. > Note that with this > implementation, the entire family of coroutines > will still go "to sleep" if one of them performs > a system call like "read", for example. Again take an example from 'classical' real-time programming. If one part of the program polls, say, a serial input device, then the rest of the program 'goes to sleep' during the poll. If that's what you want, that's what you get. To implement the serial input driver as a concurrent entity one must first of all arrange for the external event (serial device) to notify the virtual machine (cpu) that an event has occurred (interrupt), and to write the device driver as a device process, where it waits FIRSTLY for the event and only THEN reads the device. Now a Unix system call, like read(2), is the equivalent operation of the poll. If we genuinely want the section of code that does the reading to be a quasi-concurrent process, then we must make the same arrangements as we do in the example above. The sender must arrange to notify the device process that data is available. This wakes up the device process, which then calls read(2), but now it will be virtually non-blocking. Enter SIGIO - on tty's in 4.2, and in 4.3 (bless them !) on sockets too. Without SIGIO a fairly good second choice is for a child process to do the blocking read, write to a pipe which has the Modula-2 Unix process on the other end, and then send an agreed-upon signal to Modula-2. This wakes up the device process as desribed above. One other nice thing about this method is that hardware device drivers for target machines can be emulated very easily on the Unix host. For example, an algorithm for a process delay timer destined for a target machine, was completely implemented and debugged on Unix using SIGVTALRM and {set,get}itimer(2). Only the system dependent parts, starting and reading the clock, needed to be altered from one system to another. You may ask how efficient this all is. Not particularly, but then signal driven I/O was not provided on 4.x for efficiency, but to solve a certain class of problem. If your problem isn't one of them, then don't use it. The saving grace of the present implementation, is that a given application can use blocking system calls, signals as device processes, and conventional signal handlers together - as long as the designer is aware of their different effects. > Another difficulty is the time-dependent and non-atomic > interactions between signals and Unix processes. I'm not quite sure what this means. Can someone enlighten me ? Finally, and apart from all of the above, I chose this option because it is implemented in 60 lines ..... of assembler. Paul Breslaw ****************************************************************************** Paul Breslaw Hasler AG, Belpstrasse 23, CH-3000 Berne 14, Switzerland Tel.: +41 31 632932 X.400: paul@hslrswi.hasler Uucp: ... {seismo,decvax,ukc, ... }!mcvax!cernvax!hslrswi!paul Bitnet: paul%hslrswi.UUCP@cernvax.BITNET Arpa: paul%hslrswi.UUCP%cernvax.BITNET@wiscvm.ARPA Edu: paul%hslrswi.UUCP%cernvax.BITNET@ucbjade.Berkeley.EDU ******************************************************************************