[RFC]: Overlapped I/O implementation

Martin Wilck Martin.Wilck at fujitsu-siemens.com
Mon Nov 12 11:09:25 CST 2001


I'd like to summarize my thoughts about an implementation
of overlapped I/O in Wine that will offer full Windows functionality.
I havent't thought it through in all detail, but I have a clear enough
picture to write an RFC.

I regard the following points as crucial:

1 "Generic" routines like ReadFileEx() need to work with files of almost
  arbitrary type, i.e. normal files, pipes, sockets, comm ports, ...

2 Different file types imply different semantics for overlapped I/O.
  In particular:

a Files that don't support positioning (i.e. FIFOs, or basically
  everything except regular files) need to be accessed sequentially,
  i.e. there may only be one reader and one writer at a time, and
  these must be scheduled in the same order in which they were submitted
  by the application. Otherwise, the data read or written will be
  potentially scrambled. For regular files, on the other hand, spawning
  several I/O requests in parallel may be beneficial.

b Files on some devices (in particular, block devices on Linux) need
  to spawn a separate thread for the async I/O operation, because I/O
  on such devices is always blocking, which is undesirable. Neither
  the process itself nor the service thread should be blocked by a
  time-consuming floppy I/O operation, for example.

3 The semantics of individual asynchronous I/O requests depend on the
  function that was called by the application, e.g. ReadFile() or
  WSARecv(), which take different arguments. Such calls may be mixed by
  the application, although it's certainly not good programming practice
  to do so.

Based on these arguments, I'd suggest the following:

A) The generic functions need to find out what the properties of a
   file descriptor wrt asynch I/O are. The most general way to implement
   this is to have an element

int (*get_async) (struct object*, struct file_async*)

   in the obj_ops array. For non-file objects, this element would be NULL
   or no_get_async(). For non-overlapped file descriptors, the function
   would assign NULL to the file_async* argument, and return an error,
   a valid pointer would be assigned to file_async* otherwise.

   struct file_async would contain per-file information about asynch
   I/O, e.g.

struct file_async {
   struct async_private *readers;    /* list of async read requests */
   struct async_private *writers;    /* list of async write requests */
   int flags                         /* supports positioning?
				        Is I/O blocking on host OS ? etc. */
   int (*get_timeout) (struct object *obj, int type, int count);
                                     /* function that returns timeout for
                                        device, needed e.g. for serial comm */

   I currently see no need for other file-type dependedent functions;
   It should be possible to write generic routines that handle the
   requests correctly according to the flags variable.

   The Wine server API would containe a file_get_async() request that
   returns the file_async structure associated with the file by calling
   the get_async() element of obj_ops.

   The async_private struct would look pretty similar as it looks now,
   with a generic part and a part that depends on the function invoked
   for doing async I/O (e.g. for sockets, whether ReadFile() or
   WSARecv() was called on the socket fd). IMO the async_private should
   not contain file type-dependent information which is per-file.
   It should rather contain a pointer to the respective file_async struct.
   Most importantly, the async_private struct it carries a
   pointer to a function that does the actual I/O, and another function
   pointer that handles the termination of the request (invocation of user
   completion routine etc.)

B) The check_async() function in synchro.c operates on file descriptors
   rather than async requests. Once an overlapped I/O request is
   issued by the application, it is queued either in the readers or
   writers list of the async object (which is obtained from the server).
   If both the readers and writers lists were NULL beforehand, the fd
   is inserted into the list for check_async to work on.

   check_async() activates the first element(s) of both the readers and
   writers lists (where activate means that the actual read/write call
   is triggered). For FIFOS, only one element would be activated; for
   regular files several requests up to a given limit (e.g. 4).

   If the file is on a device where normal I/O would be blocking, a
   new thread is created for each activated request to satisfy it.

   When the requests' I/O routine (separate thread or not) is completed,
   it activates an event that triggers the generic "finish" routine.

   The finish routine calls the finish routine of the request, cleans up,
   and activates a new request if there is one that was queued but not
   activated. If both the readers and writers queue are empty after
   finishing the request, the file descriptor is removed from the
   list of handles to check in check_async().

C) If CancelIo() is called or a file is closed, all pending IO requests
   are removed from the file's async readers and writers lists; potential
   subthreads are terminated.

With this approach, the wineserver would only be needed for retreiving the
file's async I/O properties. The biggest change to the wine core would
be the addition of a new function to obj_ops.

I am looking forward to comments.


PS. A minor remark: The above approach would make it unnecessary to
    check for the return value of xyz_get_info() to be FD_TYPE_OVERLAPPED
    when initiating overlapped I/O. Instead, one could simply check
    if the async object returned by get_async() is non-NULL.

Martin Wilck                Phone: +49 5251 8 15113
Fujitsu Siemens Computers   Fax:   +49 5251 8 20409
Heinz-Nixdorf-Ring 1	    mailto:Martin.Wilck at Fujitsu-Siemens.com
D-33106 Paderborn           http://www.fujitsu-siemens.com/primergy

More information about the wine-devel mailing list