[RFC]: Overlapped I/O implementation
Martin Wilck
Martin.Wilck at fujitsu-siemens.com
Mon Nov 12 11:09:25 CST 2001
Hi,
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.
Regards,
Martin
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