[comp.sys.apple2] pipes

reeder@reed.UUCP (Doug Reeder) (03/11/90)

    IMHO, the best way to implement pipes is as follows: start the last
program, and let it run along until it requests a character from the
standard input from the OS.  The OS then starts up the next to last program,
and lets it run untill it sends a character to standard output (by making an
OS call) The OS then switches back to the last program.  If the next to last
program requests a character from the standard input, the OS switches to the
previous program, and runs it until it gets a character, and so on back
through the chain.  The advantage of this system is that you never have to
interrupt a program (an important considerations on IIe's and +'s, which
lack vertical blanking interrpts).  The disadavantage is that you have to
make a context shift for every character, unless you have multi-character
reads and writes, which would be an excellent thing to have anyway.  You
ought to be able to make syscalls like the ProDOS READ and WRITE and NEWLINE
anyway.

    Most often, pipes are used with filter programs, i.e. ones that take
the input and transform it in some way (e.g. break lines longer than 80
characters, format it for a particular kind of printer, pass on only lines
that contain a pattern).  Filter programs can be written which run under
present shells, by inserting themselves in the output stream, as in the
following (hypothetical) example:

PRINT CHR$(4);"PR#1"
PRINT CHR$(4);"FORMAT,F3,A9" :REM BASIC.SYSTEM external command
PRINT "Ladies and gentlemen, I stand before you to stand behind you, to tell
you something I know nothing of..."
...

PRINT CHR$(4);"NOFORMAT" :REM BASIC.SYSTEM external command
PRINT CHR$(4);"PR#0"

    If your shell suppports I/O redirection in an easy-to-use fashion, you
can manually pipe things:

fold infile >midfile       (send output to midfile)
print <midfile             (get input from midfile)

-- 
Doug Reeder                                   USENET: ...!tektronix!reed!reeder
from ARPA: tektronix!reed!reeder@berkeley.EDU BITNET: reeder@reed.BITNET
the Little Mermaid on materialism:
I just don't see how a world that makes such wonderful things ... could be bad!

fadden@cory.Berkeley.EDU (Andy McFadden) (03/12/90)

In article <14398@reed.UUCP> reeder@reed.UUCP (Doug Reeder) writes:
>
>    IMHO, the best way to implement pipes is as follows: start the last
>program, and let it run along until it requests a character from the
>standard input from the OS.  The OS then starts up the next to last program,
>and lets it run untill it sends a character to standard output (by making an
[snip]
>lack vertical blanking interrpts).  The disadavantage is that you have to
>make a context shift for every character, unless you have multi-character
>reads and writes, which would be an excellent thing to have anyway.  You
>ought to be able to make syscalls like the ProDOS READ and WRITE and NEWLINE
>anyway.

There's an easy way around this, called buffer hysteresis.

If a reader outpaces a writer, then a buffer will never get full.  If a writer
outpaces a reader, then the buffer will always be full.  This means that we
will be performing needless context switches to processes which can't do
anything.

An easy thing way to deal with this is to wait until the buffer is full, and
then leave the writer sleeping until it is only half full (the reverse
condition leaves the reader sleeping from the time the buffer is empty to
the time it is half full).

The biggest problem is handling situations where a writer sends a few bytes
and then stops.  The buffer must be periodically flushed because of this.
A more insidious problem involves a circular pipe, but that is rather
uncommon.

>Doug Reeder                                   USENET: ...!tektronix!reed!reeder

-- 
fadden@cory.berkeley.edu (Andy McFadden)
...!ucbvax!cory!fadden

madd@world.std.com (jim frost) (03/13/90)

reeder@reed.UUCP (Doug Reeder) writes:
>    IMHO, the best way to implement pipes is as follows: start the last
>program, and let it run along until it requests a character from the
>standard input from the OS.  The OS then starts up the next to last program,
>and lets it run untill it sends a character to standard output (by making an
>OS call) The OS then switches back to the last program.  If the next to last
>program requests a character from the standard input, the OS switches to the
>previous program, and runs it until it gets a character, and so on back
>through the chain.  The advantage of this system is that you never have to
>interrupt a program

This is a poorer technique as it degrades immediately into the
traditional buffer-output-until-buffer-full-or-done technique, and
additionally requires all processes to be active at one time for at
least some of the processing.

Consider that in a pipe not much is going to be done until there is
input from somewhere.  Each of the processes along the pipe depends on
its supplier for that input, back to the head process of that pipe.
Your technique would require backtracking to that head process before
meaningful processing could commence, at which time the data would
flow back along the pipe in exactly the same manner in which it does
in traditional piping schemes.

You've gained nothing, since your technique degraded immediately to
the traditional.  What have you lost?

At the start, all processes along the pipe *must* be active before any
data goes through it.  For a long pipe this will be a lot of
resources.  In the traditional method, adjusting the buffer size can
reduce the number of processes which must be active.  In the
degenerate case, where the buffer size is infinite, only one process
at a time needs to be active.  This degenerate case is what's used in
MS-DOS and some other single-tasking OS's -- you write all the output
to disk then start the next process in the pipe using the disk file as
input.

Happy hacking,

jim frost
saber software
jimf@saber.com

reeder@reed.UUCP (Doug Reeder) (03/20/90)

In article <1990Mar13.022324.19402@world.std.com> madd@world.std.com (jim frost) writes:
_reeder@reed.UUCP (Doug Reeder) writes:
__    IMHO, the best way to implement pipes is as follows: 
_
_This is a poorer technique as it degrades immediately into the
_traditional buffer-output-until-buffer-full-or-done technique, and
_additionally requires all processes to be active at one time for at
_least some of the processing.

    Apaarantly it was not clear that when the first process outputs to the
second, the OS immediately switches to the second process, and when the
second sends output, the OS switches to the third, so you only need to go
back through the chain untill you get some output.  This technique gets
information to the end as quickly as possible, where you often have a human
waiting around to see what comes out.  Also, if a process does not read
until end-of-file, the information it doesn't use is never generated.
    It does require all processes be active, but if you can't have all of
them active, you're almost better off running the first all the way through,
sending its output to a file, then running the second, and so on.


_You've gained nothing, since your technique degraded immediately to
_the traditional.  
    On the contrary, you gain something quite important: you never need to
interrupt a process, which is quite important on the IIe and other machines
which don't have regularly scheduled interrupts.  Furthermore, you needn't
worry about processes which take a long time to generate their next output,
as all final output that could possible be generated already has been, i.e.
all the processes after the time-consuming one cannot proceed without the
information the time-consuming one is generating.
    Thus you can write a ProDOS shell that does real pipes, if you can get
all the appropriate commands in memory at once.  (You'll need relocatable
external commands.)  Pipes and few programs that run in the background, such
as print spoolers and file transfer would provide most of the features,
IMHO, that people want from an Apple II Unix.

-- 
Doug Reeder                                   USENET: ...!tektronix!reed!reeder
from ARPA: tektronix!reed!reeder@berkeley.EDU BITNET: reeder@reed.BITNET
the Little Mermaid on materialism:
I just don't see how a world that makes such wonderful things ... could be bad!

toth@tellab5.tellabs.com (Joseph G. Toth Jr.) (03/23/90)

First of all - in order to provide multi-tasking (which is refered to in
   the articles), the OS/Shell would have to work using interrupts.  Most
   owners of Apples (][, ][+ //e - The //c includes a mouse interface which
   can generate timing interrupts) don't have clocks or mouse controllers
   to provide the timing interrupts required to provide multi-tasking.

Second - in order to make any gains provided by task switching, all I/O to
   disk, etc. would require new hardware to allow interrupting I/O control
   and/or DMA.  Since there has been no uniformity to restrictions regarding
   the use of interrupts in the past (DMA requires use of interrupt lines)
   almost all current I/O device drivers (hardware and software) would have
   to be scrapped.

References: <6673@hydra.gatech.EDU> <1990Mar5.234421.396@world.std.com> <14461@reed.UUCP>

In article <14461@reed.UUCP>, reeder@reed.UUCP (Doug Reeder) writes:
> In article <1990Mar13.022324.19402@world.std.com> madd@world.std.com (jim frost) writes:
> _reeder@reed.UUCP (Doug Reeder) writes:
> __    IMHO, the best way to implement pipes is as follows: 
> _
> _This is a poorer technique as it degrades immediately into the
> _traditional buffer-output-until-buffer-full-or-done technique, and
> _additionally requires all processes to be active at one time for at
> _least some of the processing.
> 
>     Apaarantly it was not clear that when the first process outputs to the
> second, the OS immediately switches to the second process, and when the
> second sends output, the OS switches to the third, so you only need to go
> back through the chain untill you get some output.  This technique gets
> information to the end as quickly as possible, where you often have a human
> waiting around to see what comes out.  Also, if a process does not read
> until end-of-file, the information it doesn't use is never generated.

In the ProDOS environment, and with all currently available hardware, the
switching of tasks and the OS/Shell determining which level has output
for the next program will not get the output to the user faster...
All that overhead in the OS/Shell is simply that; added OVERHEAD.
It would actually take longer than the simple method stated next.

>     It does require all processes be active, but if you can't have all of
> them active, you're almost better off running the first all the way through,
> sending its output to a file, then running the second, and so on.

> _You've gained nothing, since your technique degraded immediately to
> _the traditional.  
>     On the contrary, you gain something quite important: you never need to
> interrupt a process, which is quite important on the IIe and other machines
> which don't have regularly scheduled interrupts.  Furthermore, you needn't
> worry about processes which take a long time to generate their next output,
> as all final output that could possible be generated already has been, i.e.
> all the processes after the time-consuming one cannot proceed without the
> information the time-consuming one is generating.

The point of pipes is to get an output from a sequence of operations..

On a single user system the amount of code executed by the series of
programs remains constant.  Therefore, any added overhead that is
performed in selecting which task executes simply because some output
is availabe is simply that: ADDED OVERHEAD.
And that overhead will ADD to the time it takes to complete the desired
series of operations used to obtain an output, not decrease it.
Simply using a RAMdisk for intermediate storage of information would
make piping data at least tolerable.

>     Thus you can write a ProDOS shell that does real pipes, if you can get
> all the appropriate commands in memory at once.  (You'll need relocatable
> external commands.)  Pipes and few programs that run in the background, such
> as print spoolers and file transfer would provide most of the features,
> IMHO, that people want from an Apple II Unix.

On this last point; I Agree, whole-heartedly.

> -- 
> Doug Reeder                         USENET: ...!tektronix!reed!reeder


-- 
------------------------------------------------+---------------------
Maybe I shouldn't have done it, sarcasm is so   | Joseph G. Toth Jr.
seldom understood.  Don't FLAME on me, please.  | uunet!tellab5!toth

gt4662b@prism.gatech.EDU (BRANHAM,JOSEPH FRANKLIN) (03/24/90)

In article <2284@tellab5.tellabs.com>, toth@tellab5.tellabs.com (Joseph G. Toth Jr.) writes:
> First of all - in order to provide multi-tasking (which is refered to in
>    the articles), the OS/Shell would have to work using interrupts.  Most
>    owners of Apples (][, ][+ //e - The //c includes a mouse interface which
>    can generate timing interrupts) don't have clocks or mouse controllers
>    to provide the timing interrupts required to provide multi-tasking.
> 

This is probably pretty irrelevant to the discussion at hand, but I recently
bought my father a copy of Geos for the Apple //e //+. I didn't look at the
documentation overly much, but the program requires the use of interrupts.
In fact, for the //e user without a mouse card, a tiny little interrupt
handler which plugs into slot 7 is provided. It had about 4 generic LS
chips on it and looks like it could be built for about $20 from scratch.



-- 
<------------------------------------------------------------------------------>
<  FRANK BRANHAM                      | "I exist; therefore I am."             >
<  Georgia Institute of Technology    |       -The Patchwork Quilt             >
<  Internet: gt4662b@prism.gatech.edu |   	by August Derleth              >

toddpw@tybalt.caltech.edu (Todd P. Whitesel) (03/24/90)

toth@tellab5.tellabs.com (Joseph G. Toth Jr.) writes:

>First of all - in order to provide multi-tasking (which is refered to in
>   the articles), the OS/Shell would have to work using interrupts.  Most
>   owners of Apples (][, ][+ //e - The //c includes a mouse interface which
>   can generate timing interrupts) don't have clocks or mouse controllers
>   to provide the timing interrupts required to provide multi-tasking.

1. Only true pre-emptive multitasking requires interrupts. Cooperative
	multitasking (a la Multifinder on the Mac) does not use interrupts to
	decide when to change contexts. It would not be that hard to write a
	cooperative multitasking system on ANY machine provided the O/S is
	smart enough to provide the necessary intercommunication (pipes) and
	to be able to load more than one program at once.

2. Cards which many people have that could be pressed into interrupt service:
	Mockingboard (two 6522's) and compatibles (AE Phasor, etc.)
	Apple Cat II modem (30 hz interrupt, also see Serial Cards below)
	Serial Cards or any card which allows transmit interrupts

Note that the serial card needs to be dedicated to interrupt generation. If
you have one but aren't using it then you can just leave the serial port
connected to nothing and it will transmit characters at 300 baud out into the
void while your interrupt driver transmits another character and goes on about
its business.

(I know, it's a kludge. But it would work if it were really that important. On
6502's it's much easier to use something that does _not_ do a full CPU context
switch because the stack and ZP are fixed. With a 65802/65816 your entire CPU
state is in the registers, and life is much easier. Except nobody's bothered
to write one yet that I know of.)

>Second - in order to make any gains provided by task switching, all I/O to
>   disk, etc. would require new hardware to allow interrupting I/O control
>   and/or DMA.  Since there has been no uniformity to restrictions regarding
>   the use of interrupts in the past (DMA requires use of interrupt lines)
>   almost all current I/O device drivers (hardware and software) would have
>   to be scrapped.

To clarify here: what you're trying to get across is that all currently
existing drivers were designed without the possibility of multitasking in
mind. So what? When a process makes an I/O request it is satisfied even if
it effectively halts the system for a second or two. Nobody said anything
about full real-time performance here. (Granted it'd be nice.)

DMA does not require interrupts -- the driver could and probably does just
poll a bit until DMA is finished -- but interrupts do simplify things.
They prevent your driver from having to wait for the DMA to finish which is
what I believe you were referring to. This could and should be exploited by
a multitasking system but as you said the drivers would have to be rewritten --
which I would do anyway. Queuing of I/O requests and all that. (Which might
gobble some memory but come on! how many people who'd want these features still
have only 128K?)

Todd Whitesel
toddpw @ tybalt.caltech.edu

toth@tellab5.tellabs.com (Joseph G. Toth Jr.) (03/24/90)

In article <2284@tellab5.tellabs.com>, toth@tellab5.tellabs.com (Joseph G. Toth Jr.) writes:
> > 
> >     Apaarantly it was not clear that when the first process outputs to the
> > second, the OS immediately switches to the second process, and when the
> > second sends output, the OS switches to the third, so you only need to go
> > back through the chain untill you get some output.  This technique gets
> > information to the end as quickly as possible, where you often have a human
> > waiting around to see what comes out.  Also, if a process does not read
> > until end-of-file, the information it doesn't use is never generated.
> 
> In the ProDOS environment, and with all currently available hardware, the
> switching of tasks and the OS/Shell determining which level has output
> for the next program will not get the output to the user faster...
> All that overhead in the OS/Shell is simply that; added OVERHEAD.
> It would actually take longer than the simple method stated next.

After getting home, I thought about what I wrote here..

While what I stated true in the cases of output dumped to a file; output
dumped to a printer would begin sooner than by waiting till all processes
before the printer are completer by passing intermediate files as RAMdisk
files.

I am assuming that the last process that received output from its
predecessor initiates the backward search for output by its request for
more input.

Practicality must still be issues (usable memory, processor speed,
managability, implementation effort, and time).

The feature would be very nice; but how valuable is it; how much time should
be spent in developing the fastest feature vs. how much time does it really
save for the user (and, are those savings really meaningful).
Most users have slow DMP devices for hardcopy (if they had high speed printer,
they would probably also have a more powerful computer), so saving a couple
minutes on a 20 minute printing job (a few seconds on a 3 minute print, etc.)
would seem to make the effort impractical.

I feel that the feature is nice, but, considering the user market and 
target for implementation; IMHO the simplest solution is best.

-- 
------------------------------------------------+---------------------
Maybe I shouldn't have done it, sarcasm is so   | Joseph G. Toth Jr.
seldom understood.  Don't FLAME on me, please.  | uunet!tellab5!toth

m.tiernan@pro-angmar.UUCP (Michael Tiernan) (03/25/90)

In-Reply-To: message from alphalpha!toddpw@tybalt.caltech.edu

> DMA does not require interrupts ......

Literally no, but yes it does require an interrupt of sorts.  The DMA request
is handled (inside the CPU) in the same frame of mind as an interrupt is, when
the CPU sees it, it finishes what it's doing (6502's wait till the end of a
read or a write and 65c02's go in the middle of a write) and relenquishes the
data, address, and control buss to the requester and waits for the DMA line to
return to continue processing.


<< MCT >>

GEnie       : M.Tiernan
AppleLinkPE : M Tiernan, BCS Mike, or MichaelT34
Internet    : pro-angmar!m.tiernan@alphalpha.com
UUCP        : ...!uunet!alphalpha!pro-angmar!m.tiernan

"And the sweating of their souls will not wash the blood from their hands."
                                                                  - Phil Ochs

madd@world.std.com (jim frost) (03/25/90)

reeder@reed.UUCP (Doug Reeder) writes:
>    Apaarantly it was not clear that when the first process outputs to the
>second, the OS immediately switches to the second process, and when the
>second sends output, the OS switches to the third, so you only need to go
>back through the chain untill you get some output.

Typically this is all the way to the first process.

>This technique gets
>information to the end as quickly as possible, where you often have a human
>waiting around to see what comes out.

So make the buffer size smaller and get the same results with
"traditional" techniques.

>    On the contrary, you gain something quite important: you never need to
>interrupt a process, which is quite important on the IIe and other machines
>which don't have regularly scheduled interrupts.

You interrupt processes a LOT, once for every task switch.  It's not a
preemptive interrupt, so you don't need a timer, but it's an interrupt
nonetheless.  Nonpreemptive switching is pretty common.

>Furthermore, you needn't
>worry about processes which take a long time to generate their next output,

There ain't no such thing as a free lunch.  That program is going to
take a long time with your scheme too.

jim frost
saber software
jimf@saber.com

madd@world.std.com (jim frost) (03/25/90)

toth@tellab5.tellabs.com (Joseph G. Toth Jr.) writes:
>First of all - in order to provide multi-tasking (which is refered to in
>   the articles), the OS/Shell would have to work using interrupts.

No it wouldn't.  Using a timer to reschedule processes is called
"preemptive scheduling".  When a process explicitly or implicitly
gives up control so that a task switch can take place it's called
"non-preemptive scheduling".  The Mac multifinder uses the latter
(it's not the first example of non-preemptive scheduling, but it's a
familiar one that works pretty well).

Most of the time non-preemptive OS's do task switching during system
calls, which are very frequent in most interactive systems (such as
the Mac).

>Second - in order to make any gains provided by task switching, all I/O to
>   disk, etc. would require new hardware to allow interrupting I/O control
>   and/or DMA.

No, although it certainly would help if you didn't have to sit around
waiting for I/O all the time.  Again, look at the Macintosh
multifinder.  Smarter hardware improves throughput (that's why the IBM
3090 has such fast I/O -- whole computers are used as periferals).

All of these things are well covered in most operating systems texts;
this stuff has been around for awhile.

Also note that with some compiler help you can do protected memory on
the 6502 by emulating indirect memory references and non-relative
fetch, store, and jump functions.  Slow, but possible.

Happy hacking,

jim frost
saber software
jimf@saber.com

johnte@microsoft.UUCP (John TERRANOVA) (03/25/90)

In article <1990Mar23.200426.28446@spectre.ccsf.caltech.edu> toddpw@tybalt.caltech.edu (Todd P. Whitesel) writes:
>toth@tellab5.tellabs.com (Joseph G. Toth Jr.) writes:
>
>>First of all - in order to provide multi-tasking (which is refered to in
>>   the articles), the OS/Shell would have to work using interrupts.  Most
>>   owners of Apples (][, ][+ //e - The //c includes a mouse interface which
>>   can generate timing interrupts) don't have clocks or mouse controllers
>>   to provide the timing interrupts required to provide multi-tasking.
>
>1. Only true pre-emptive multitasking requires interrupts. Cooperative
>	multitasking (a la Multifinder on the Mac) does not use interrupts to
>	decide when to change contexts. It would not be that hard to write a
>	cooperative multitasking system on ANY machine provided the O/S is
>	smart enough to provide the necessary intercommunication (pipes) and
>	to be able to load more than one program at once.

Let me offer this definition of pre-emptive multitasking:
    The ability to execute more than one program at a time such
that all programs get CPU time and no process(es) can prevent any
other process(es) from getting control of the processor.  The 
processes take turns controlling the processor. When the processes
start ending, each remaining process gets more CPU time per unit
real time.

By this definition MultiFinder is not a pre-emptive multitasking system.
Also by this definition interrupts are NOT required.  All you need is
some way for the OS/Shell to be able to decide it is time to task-switch
and do the task-switch.

A p-code system would be ideal for this.  The 'time-slice' that a
process has to run before being switched out would be a count of
p-code instructions to execute.  After the interpreter interprets
some number of instructions, it switches tasks.

This p-code system can also provide memory protection.  If processes
are allocated memory on a page basis then any memory access (read/
write and code or data) can be checked by the interpreter against a
page table for validity.

Another virtue of this hypothetical p-code system would be page-
swapping.  Since the code needs to be relocatable start it at memory
location $0000.  Which would be virtual page $00, byte $00.  Using
a page table, look up the physical page number for virtual page $00.
If the physical page number is invalid, then load those 256 bytes from
disk.

I'll even give some code for the main loop of the interpreter
(this code is not meant to be final code, but rather an idea
of what the real code may look like):

topOfLoop: lda timeLeft	;some zero-page location
           bne 1	;keep going if time not expired
	   jmp NextJob	;task-switch and jmp back to topOfLoop
@1	   dea
	   sta timeLeft
	   ldy vpage	;get virtual page number
	   lda (currPageTable),y	; get phys page num from page table
	   bne 2
	   jsr LoadPage	;page not in mem, so go get it
@2	   sta pc+1	;store page number part of pc
	   lda (pc)	;load the opcode
	   inc pc	;bump up pc (using the vpage, not phys page)
	   bne 3
	   inc vpage
@3	   asl
	   tax
	   jmp (jmpTable),x	;proc must end with jmp topOfLoop

The last three instructions group the opcodes into 128 pairs.
The pairs differ only in the high bit. The asl allows us to index
into a jump table of words (2 bytes) rather than bytes.  It is up
to the code jmp'ed to to discern between the two opcodes by
checking the carry flag.

I have found that for simplicity, generation of the pcode may want
to ensure that an entire instruction (1 byte opcode, >= 0 args) is
on the same page so that checking for page faults during execution
of instructions is not necessary - the eternal time-space trade off.

So.  Waddaya think?  Is this pcode-multitasking-virtual memory
system feasible?  I think the key is a good pcode language.
Anybody wanna write it?

-----------------------+----------------------------+-------------------------
    John Terranova     |  What the Hell do I know?  |  I speak/type for me
johnte@microsoft.uucp  |  I come from Waunakee!     |  and no one else.
-----------------------+----------------------------+-------------------------
"You look so good; you feel good, too.  When they see you shake it, baby
 everybody's gonna pay attention to you and you and you." --Gerard, Shake It