[comp.lang.modula2] Coroutines vs. Operating Systems

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
******************************************************************************