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.