[comp.unix.misc] UNIX semantics do permit full support for asynchronous I/O

dag@gorgon.uucp (Daniel A. Glasser) (09/04/90)

[was x-posted in comp.unix.wizards and comp.lang.misc]

In article <1990Aug31.142906.26633@uncecs.edu> utoddl@uncecs.edu (Todd M. Lewis) writes:
>In article <31445.26dc0466@ccavax.camb.com> merriman@ccavax.camb.com writes:
>>Sure sounds like VMS QIO calls.
>Sounds like the Amiga's OS to me.  And UNIX doesn't do this?

Well, standard Unix doesn't do this.  VMS always has, so has RSX-11, and
to some degree, even RT-11.  The Amiga OS is a rather recent invention, and
appears to use this old tried and true technique.

For those not familiar with the QIO concept, a read or write QIO is issued
with a special call that allows the application to proceed and be notified
by either a signal (AST in VMS technology) or an event flag.  When the
application needs to use the buffer it either checks the flag or waits until
the signal routine is executed.  Another QIO can be used to determine the
number of bytes read/written, the error status, etc.

This extends beyond doing solicited buffered I/O to unsolicited I/O.
In Unix, when a program wants to do processing in the background while
waiting for keyboard input, it either forks a child to do one or the
other, or polls the tty for input.  Under RSX or VMS, the program attatches
to the keyboard, specifies an unsolicited input Asynch. System Trap (AST)
service routine and goes about its business.  When the user types a key,
the main thread of execution is suspended and the AST service routine is called
to handle the keyboard input.  When done, the AST service routine returns
and the interrupted background processing continues as if nothing has happened.
When you get the hang of it, this is a very simple way to write programs.
I miss this capability in Unix, but have simulated it many times with
fork and signal.

As an extension to Unix, I would suggest the following:

  int writea(int fileno, void *buffer, size_t buflen, void (*complete_rtn)());
  int reada(int fileno, void *buffer, size_t buflen, void (*complete_rtn)());
	where fileno is the file number, buffer is a pointer to the
	bytes to be written/read, buflen is the number of bytes to
	be written/read from/to the buffer, and complete_rtn points
	to a function to be called when the read/write is complete.
	Maybe there should be an additional parameter or two -- flags
	modifying the actions, and a parameter to be passed to the
	completion routines.  The completion routines should take as
	a parameter the result of the write/read.  These routines return
	a status which indicates whether the async. read/write request
	is valid or not.  Care should be taken not to read/write
	the buffer area until the completion routine is called.

Since these are not changes to the existing read/write system calls,
dusty decks would not be broken.  They could also not take advantage
of the new functionality.  (The above proposal is close to the RT-11
completion routine scheme.)

If this debate goes on much longer, I'd suggest that it get removed
from comp.lang.misc, since this is not a language depenent issue.
-- 
Daniel A. Glasser                       One of those things that goes
dag%gorgon@persoft.com                  "BUMP! (ouch!)" in the night.

seanf@sco.COM (Sean Fagan) (09/06/90)

In article <1990Sep04.014353.15085@gorgon.uucp> dag@gorgon.uucp (Daniel A. Glasser) writes:
>As an extension to Unix, I would suggest the following:

I wouldn't (reasons below), but a small comment on your method anyway:

>  int writea(int fileno, void *buffer, size_t buflen, void (*complete_rtn)());
>  int reada(int fileno, void *buffer, size_t buflen, void (*complete_rtn)());

Change that the 'complete_rtn' to:

	void (*complete_rtn)(int)

where the parameter is the return value of the call, either the number of
bytes read/written, or the failure/return code.  Perhaps - <errno> on
failure?

Anyway:  why I wouldn't do it.  (I just got involved in this discussion with
a friend last night, btw, so the arguments are fresh in my head 8-).)

The asynchronous reads/writes can be simulated now, in SysV and (I think)
BSD fairly easily, using shared memory, fork, and signals.  If you're going
to change the kernel enough to add this, I would suggest that it would be
better to go all out and add threads to your system.  They are more generic,
which means that it's not exactly what you want to do, but it's also more
flexible, and can be extended to do other things.

Just my opinions, of course...

-- 
Sean Eric Fagan  | "let's face it, finding yourself dead is one 
seanf@sco.COM    |   of life's more difficult moments."
uunet!sco!seanf  |   -- Mark Leeper, reviewing _Ghost_
(408) 458-1422   | Any opinions expressed are my own, not my employers'.

bzs@world.std.com (Barry Shein) (09/08/90)

From: seanf@sco.COM (Sean Fagan)
>>As an extension to Unix, I would suggest the following:
>
>I wouldn't (reasons below), but a small comment on your method anyway:
>
>>  int writea(int fileno, void *buffer, size_t buflen, void (*complete_rtn)());
>>  int reada(int fileno, void *buffer, size_t buflen, void (*complete_rtn)());
>
>Change that the 'complete_rtn' to:
>
>	void (*complete_rtn)(int)
>
>where the parameter is the return value of the call, either the number of
>bytes read/written, or the failure/return code.  Perhaps - <errno> on
>failure?

Well, I wouldn't do it either, at least not until some evidence is
presented indicating it's worthwhile (to someone other than a
salesperson trying to sell Unix to a VMS customer...)

BUT...I'd change this further:

	int write/reada(....,void (*complete_rtn),caddr_t arg);
	void (*complete_rtn)(caddr_t,int,int);

where:

	caddr_t		a user supplied argument
	int		the file descriptor
	int		the result as described

This would allow multiplexing of several async I/O's thru one routine,
or even posting more than one on the same fd and knowing which
finished without having to maintain an external state (or in the case
where it might be possible they finish in a different order than
requested, which might be possible due to disk queue re-ordering or
similar considerations.)

Something you might also want in that result, or yet another arg,
would be whether it was a read or a write or whatever it was that
completed, tho I guess one could argue that this should be mux'd via
different completion routines. But I bet that would lead to this sort
of silliness:

	#define IO_READ  1
	#define IO_WRITE 2

	void
	awrite_complete(fnp,fd,result)...
	{
	   (*fnp)(IO_WRITE,fd,result);
	}
	aread_complete(fnp,fd,result)...
	{
	   (*fnp)(IO_READ,fd,result);
	}

where both fnp's are the same, ah well (this would be because a lot of
common code is used on read or write, like just setting a bit in a
commit map.)

Providing the fd would, for example, allow the completion routine to
close the file on EOF (or even take some action, like rewind/offload a
tape etc, on ERROR, without all sorts of global state.)

Note that errno on entry to the completion routine had better be the
errno resulting from the I/O or I'd be quite upset.

Gee, maybe the buffer should be passed also...I guess you could argue
that could go into the caddr_t which could point to a structure, I'd
buy that I guess.

Then again I haven't read the POSIX spec, how close does it come to
all this?
-- 
        -Barry Shein

Software Tool & Die    | {xylogics,uunet}!world!bzs | bzs@world.std.com
Purveyors to the Trade | Voice: 617-739-0202        | Login: 617-739-WRLD