home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Crawly Crypt Collection 1
/
crawlyvol1.bin
/
utility
/
disk
/
pdos_112
/
pipe_use.txt
< prev
next >
Wrap
Text File
|
1993-07-30
|
22KB
|
491 lines
To install pipes:
1. copy the following four lines to the end of the CONFIGUR file:
Install pipes. The first line installs the pipe file manager, the
second creates a pipe device named "PIPE".
#pipe.dev
#makepipe.ttp $pipe
2. Copy the files PIPE.DEV and MAKEPIPE.TTP into the POWERDOS folder.
3. Reboot the machine.
You will now have a new device in the system, called PIPE: which is
used to create pipes.
There are two kinds of pipes, named and unnamed. Unnamed pipes are
used primarily by command line shells to set up a pipeline of
commands using processes called filters. Filters are simple programs
that read data from their standard in, modify it in some way, and
write it to their standard out. Unnamed pipes allow you to tie the
standard out of one process to the standard in of another. The pipe
file manager synchronizes the two processes to keep the data moving
between them. Following is an example sequence that would launch two
tasks concurrently with an unnamed pipe between them:
************************************************************************
*
* Test program using pipes to tie two processes together. First process
* gets its standard in from a text file, converting all lowercase
* characters to uppercase; it then wrties to its standard out, which is
* tied (through an unnamed pipe) to the standard in of a process which
* converts all uppercase characters to lowercase.
*
* Note that under normal GemDOS, an error is NOT returned when the
* end of a file is reached, thus a program reading from its standard in
* has no way of knowing if the EOF has occured, without some added
* overhead. Under PowerDOS, any open disk file can be made to report EOF
* unsing an Fstatus call to change an option bit. Once this is done, the
* process can read from its standard in until the EOF error is returned,
* without any other work necessary.
*
************************************************************************
OPT C-
include dosmacro.s
include dos_defs.s
tos_init
Start c_conws clearsc(pc) Clear the screen
f_open #0,text_file(pc) Open the text file
move.w d0,d7 Save its handle
* +++
*
* The following Fstatus operation has the open file report EOF as an error,
* instead of just reporting zero bytes read.
*
* ---
f_status do_eof(pc),#st_wrtopt,d7
* +++
*
* An unnamed pipe is created by just not giving it a name....
*
* ---
f_open #0,pipe_name(pc) Open an unnamed pipe
move.w d0,d6 Save its handle
* +++
*
* We want to make copies of the original standard in and out handles so we
* can restore them when needed.
*
* ---
f_dup #0 Copy standard in
move.w d0,d5
f_dup #1 Copy standard out
move.w d0,d4
* +++
*
* First process in pipeline gets input from the file, sends it to the pipe...
*
* ---
f_force d7,#0 Standard in comes from the file
f_force d6,#1 Standard out goes to the pipe
* +++
*
* Execute filter as concurrent task with parent; it will have same priority
*
* ---
p_exec null(pc),null(pc),upper(pc),#$10
* +++
*
* Second process gets input from the pipe, sends it to original standard out...
*
* ---
f_force d4,#1 Restore standard out
f_force d6,#0 Standard in comes from the pipe
* +++
*
* Execute second process concurrently; the pipe file manager will synchronize
* execution of each process, so the output of the first does not overrun the
* input to the second...
*
* ---
p_exec null(pc),null(pc),lower(pc),#$10
* +++
*
* Standard out has already beed restored to original handle; we need only
* restore our standard in (our child processes inherit our standard handles
* at the time of their execution; after they are running, what we do with our
* handles does not affect them), and get rid of the handles we no longer need...
*
* ---
f_force d5,#0 Restore standard in
f_close d4 Get rid of copy of standard out
f_close d5 Get rid of copy of standard in
f_close d6 Get rid of the pipe
f_close d7 Get rid of opened file
* +++
*
* Nothing to do till both child processes have fininshed, so just wait for both
* of them...
*
* ---
wait Wait for first child to term
wait Wait for second child to term
c_conin Wait for a key
p_term #0 Kill ourselves
do_eof dc.w 0,0,1
null dc.b 0,0
pipe_name dc.b "pipe:",0
upper dc.b "to_upper.prg",0
lower dc.b "to_lower.prg",0
text_file dc.b "testfile.txt",0
clearsc dc.b 27,"E",0
end
Here are the two filter programs:
************************************************************************
*
* Program to read from standard input and write to standard output.
* Changes any lowercase characters read into uppercase.
*
************************************************************************
include dosmacro.s
tos_init
Start lea string(pc),a5 Point to string space
move.b #200,(a5) Read up to 200 chars
.loop c_conrs (a5) Read a line in
tst.w d0 Was there an error?
bmi.s .exit Exit if so
lea 2(a5),a0 Point to actual string
bra.s .do_count Jump into loop
.case_loop move.b (a0)+,d1 Get next char
cmp.b #"a",d1 Lower than a?
blo.s .do_count Yes, don't change
cmp.b #"z",d1 Higher than z?
bhi.s .do_count Yes, don't change
andi.b #$df,d1 Clear lowercase bit
move.b d1,-1(a0) Back to string
.do_count dbra d0,.case_loop Count down chars
c_conws 2(a5) Write modified string
c_conws crlf(pc) And a cr/lf
bra.s .loop Next line
.exit p_term #0
crlf dc.b $d,$a,0
string ds.b 204 Enough space for the string
end
************************************************************************
*
* Program to read from standard input and write to standard output.
* Changes any uppercase characters read into lower case.
*
************************************************************************
include dosmacro.s
tos_init
Start lea string(pc),a5 Point to string space
move.b #200,(a5) Read up to 200 chars
.loop c_conrs (a5) Read a line in
tst.w d0 Was there an error?
bmi.s .exit Exit if so
lea 2(a5),a0 Point to actual string
bra.s .do_count Jump into loop
.case_loop move.b (a0)+,d1 Get next char
cmp.b #"A",d1 Lower than A?
blo.s .do_count Yes, don't change
cmp.b #"Z",d1 Higher than Z?
bhi.s .do_count Yes, don't change
ori.b #$20,d1 Set lowercase bit
move.b d1,-1(a0) Back to string
.do_count dbra d0,.case_loop Count down chars
c_conws 2(a5) Write modified string
c_conws crlf(pc) And a cr/lf
bra.s .loop Next line
.exit p_term #0
crlf dc.b $d,$a,0
string ds.b 204 Enough space for the string
end
This code was produces and assembled with the Devpac development
package. Note: the files dosmacro.s and des_defs.s are included with
the arc that contained this text file.
Named pipes
While unnamed pipes are useful for building command pipelines from a
shell program, that aren't much good for general purpose interprocess
communication. The reason is simple: since unnamed pipes have no
name, they cannot be created by one process, then opened by another.
Once an unnmaed pipe is created, handles to it may be passed to child
processes, but thats it. Even the creator of it cannot do another
open to get another handle to it. If you open three unnamed pipes in
a row, you get three distinct pipes. Also, unnamed pipes are limited
in the size of the circular buffer which is used to move data through.
Named pipes have none of the restrictions of unnamed pipes. Since
they have a name, they can be created by a process, and left in the
system when the process terminates. Other processes can open them,
read from or write to them, list them and see if there is any unread
data in them using the Fsfirst/snext commands, and set the size of the
circular buffer when they are created.
So if named pipes are so flexible, why have unnamed pipes? Because
named pipes require allocation of memory (with unnamed pipes, the
circular buffer exists entirely within the path descriptor structure),
and because a command line shell would require extra overhead to
insure that the name it was giving to a pipe to be used for a pipeline
was not already in use. It is easier to create any number of unnamed
pipes because no name is involved.
Several things to keep in mind about named pipes:
The name is a normal GemDOS 8.3 name.
They will always have at least 256 bytes of buffer, even if a smaller
size is requested.
The pipe file manager knows how many users of a pipe there are, and
if you try to read from a pipe that has no (current) possibility of a
writer, you will receive an EOF error. Simply put, if the number of
readers is the same as the number of users, you will get an EOF error.
If you are the last user of an empty pipe, and you close it, it will
vanish from existance. However, if there is data in the pipe and you
close it, it will remain in memory until someone opens it, reads the
remaining data, and then closes it. After this, it will terminate.
If you are writing to the pipe, and you fill the buffer, but there
are no readers, you will wait (forever maybe) until another process
opens the pipe and reads the buffer, thus allowing you to complete
the write. Unlike readers, which return eof if there is no writer,
writers wait until there is a reader (this is not true of unnamed
pipes, since if the number of writers = the number of users, it would
be impossible for another process to open the pipe to read the data;
thus, in this case, an error MUST be returned).
**************
So, what use is there for a named pipe? Well, one of the most useful
things I can think of is to use a pipe to implement client/server
functions over a network. This would involve the use of two pipes,
both created by the server. One would be a pipe that the server reads
from, and the client writes to (the command pipe), the other would be
a pipe that the server writes to and the client reads from (the
results pipe). To make sure that a minimal amount of process
switching occures, we would also make sure that the cammand and
result pipes had buffers that are larger (say, by 20%) than the data
that will be placed in them.
As an example of how this works, I created a way to get process
information on processes running on other machines over a network.
The process monitor (which is provided with PowerNet) was modified to
look for these command/result pipes on other machines on the network.
If they existed, it would open them, write a command to the command
pipe, then wait for the results from the result pipe.
Following is the source code for the server task. It is amazingly
short, given the fact that it provides a service to another process
over a network. It would be easy to expand on this idea to provide
far more complex services in a networked environment:
************************************************************************
*
* Background process monitor.
* This program sets itself up in the background, and creates two named
* pipes called "COMMAND.BSP" and "RESULTS.BSP". It reads the command
* pipe for requests to fetch information on a specific process.
*
* A single word is sent by a requestor. The word is the process ID
* of the process to get the information for.
*
* The results of this call is written to the results pipe. An entire
* process information structure is written, even if the call resulted
* in an error. In the case of an error, the error code (a byte) is
* returned in the pi_queueID code byte of the process information
* structure
*
* The return block is defined in the file 'DOS_DEFS.S'. It is the
* structure returned from a p_procinf call. Its size is set in
* DOS_DEFS.S as the variable 'pi_size'.
*
************************************************************************
include dos_defs.s
include dosmacro.s
RSRESET
buffer rs.b pi_size Space to place process info into
rs.b 200 Space for a stack
block_size equ __RS
start bra initial Do initializing stuff
the_rest m_shrink d5,start-256(pc) Free unused memory
s_send d3,d4 Wake parent up
.loop f_read buffer(a6),#2,d7 Try to read a command
move.w buffer(a6),d0 Get process to read
p_procinf buffer(a6),d0 Get info on it
tst.w d0 Work ok?
bpl.s .send
move.b d0,buffer+pi_queueID(a6) Return error here
.send f_write buffer(a6),#pi_size,d6 On its way!
bra.s .loop Next command
* From the label 'start' to this point, plus the 'block_size' structure
* listed above, plus the 256 byte basepage, is all that remains of this
* background task once it is installed. Currently, this is 654 bytes.
variable:
initial lea start-256(pc),a0 Get basepage
move.l #end_of_it-start+10000,d0
lea 0(a0,d0.l),sp Temp stack
m_shrink d0,(a0) Free memory up before we make pipes
c_conws sign_on(pc) Sign-on message to screen
f_create #0,command_pip(pc) Create the command pipe
move.w d0,d7
bmi.s .error
* We'll open the command pipe again. If you try to read from a pipe that
* has no other users, it will return an EOF error. We want this process
* to be able to stay dormant while it waits for commands to be sent
* through the command pipe. An EOF error would not make this possible,
* as the process would have to check for the error, do a delay, then
* make the call again, etc. If we simply open the pipe again, the pipe
* file manager will note that there is another owner and will not
* return the EOF error.
f_open #1,command_pip(pc) Don't bother keeping the handle
f_create #0,results_pip(pc) And the results pipe
move.w d0,d6
bmi.s .error
lea variable(pc),a6
move.l #(variable-start)+256+block_size,d5 Memory size to keep
lea block_size(a6),sp Put stack at the top of vars
p_getppid Get our parent's process ID
move.w d0,d4
moveq #S_wake,d3 Signal to send
bra the_rest
.error c_conws error(pc) No good, error out
p_term #0
end_of_it nop
command_pip dc.b "pipe:\command.bsp\10",0 Commands are only two bytes
results_pip dc.b "pipe:\results.bsp\500",0 At least pi_size bytes
sign_on dc.b "PowerDOS Background Process Monitor",$d,$a,0
error dc.b "Unable to open communications pipe",$d,$a,0
end
Two things to note about the above program: you can see by the pipe
file names that the requested size of the pipe is given as a decimal
value after the pipe name. If the name is not followed by a size,
then the default 256 byte buffer is used. Note that if you request
less than 256 bytes, 256 bytes becomes the size, thus the \10 given
in the cammand pipe name has no real purpose, other than as a
reference.
The other point of note is that the command pipe was opened twice.
The reason is as listed. If you are the only current user (one opened
handle) of the pipe and you try to read from it, you'll get an eof
error returned. That can be useful, but in this case the process has
nothing to do while waiting for a command, so it should stay dormant
until a command is present. If it is dormant, it uses no CPU time;
its only used resource becomes the memory it occupies. The pipe file
manager will wake it up as soon as any data is written to the pipe.
By opening the pipe twice, the file manager sees that there is
another user (or opened handle) of the pipe. The pipe file manager
assumes that the other user (handle) of the pipe might start writing
at any time, so the reader is allowed to wait. The pipe file manager
does not care that the other user is the same user that is trying to
read (which means in this case that the number of readers, in fact,
does equal owners). This is not a bug; it was for this very reason
that the pipe file manager does not take into account WHO owns the
handles, only that readers does not equal owners.
Now, I said that there could be times that it would be good to get an
EOF error from the command pipe. Lets say we had a server that did
many things. Using the above technique, the server would be stuck
waiting for a process to write a command, and would not be able to do
anything else. By not having the pipe opened twice, it becomes very
easy to detect if there may be data available (i.e. another user has
the pipe opened). We simply do the read, and if our return (in d0.l)
is negative (the actual EOF error is defined in the DOS_DEFS.S file),
we know that there is not currently another user, thus no data to
read. We could go on to do other things, in a loop, coming back
periodically to check the pipe.
Ok, you ask, but what if the pipe gets opened over the network, but
no commands are sent? Well, in that case, the server would get stuck.
There are two ways around this.
1. Never, as a client, open the command pipe and leave it open. You
could open it, send a command, receive the results and close it again.
2. Forget number one. Have the server NOT read the command pipe if it
does not contain a command.
Ok, how do we do number 2? By using the Fstatus call (it is called
f_status in the provided macro file). Lets say register d7 contained
your command pipe file handle. Simply do the following:
f_status #st_input,d7 See if data is available
tst.l d0 If true (<>0), data is available
beq.s no_data
f_read buffer(pc),#size,d7 Read command from pipe
....
The 'st_input' constant is defined in DOS_DEFS.S along with some
other usefull status operation subcodes.
Ok, what do we do if there is nothing to do, but we don't want to get
stuck waiting for data to become available, but we also don't want to
soak up a bunch of CPU time while waiting for something to happen?
This is easy. First, if you are a background task, you could lower
your priority like this:
p_priority #0,#0 Get our priority
subq.w #2,d0 Lower it a little
p_priority d0,#0 Set new, lower priority for us
If your priority was 100, and the forground applications was also
100, you would both get half the CPU time (assuming no other
processes). By lowering yours by two, to 98, you will run only half
as often as the other process, or 1/3 of the CPU time would be yours
(because processes are aged before priority comparisons are done,
lowering your priority by one would have no effect).
What is a lot more usful, however, is to RAISE your priority. Since
GEM programs are CPU hogs, but since they are very user input
dependant, it is sometimes better to be able to preempt the GEM
process whenever you need to run, but to pass off the CPU when you
don't need to run, using the following command:
sleep #100 Pause for 100 milliseconds (1/10 second)
The value following the sleep macro is the number of milliseconds you
wish to be dormant. Your server program could thus do its loop,
checking for stuff to do, like read a command from the command pipe,
then free the CPU for other, less important stuff, like the foreground
application (ha ha). You can adjust the number of milliseconds to
wait to whatever the requirements of your server might be. The longer
the delay, the less often you'll check for things to do. Conversely,
the shorter the delay, the more often you'll hog the CPU in a loop
that ends up with nothing to do.