to tell it that the write is completed. Asynchronous I/O is important for many nonthreaded
applications, as it allows the application to continue to work, even while there is I/O pending.
When a process makes a system call (see Figure 3-6), the following events occur:
The process traps to the kernel.
The trap handler runs in kernel mode and saves all the registers.
The handler sets the stack pointer to the process structure's kernel stack.
The kernel runs the system call.
The kernel places any requested data into the user-space structure that the programmer
6. The kernel changes any process structure values affected.
7. The process returns to user mode, replacing the registers and stack pointer, and returns the
appropriate value from the system call.
Of course, system calls don't always succeed. They can out-and-out fail or they can be interrupted.
In C, when they fail they return a failure value and set errno. When interrupted by a signal the
call is forced out of the kernel, the signal handler is run, and the system call returns EINTR.
(Presumably, the program will see this value and repeat the system call.) The Java model for
handling these situations is to throw exceptions (there are a variety of exceptions for failing
system calls and a special exception, InterruptedException, for interruptions).
What happens in a process with multiple LWPs? Almost exactly the same thing. The LWP enters
the kernel, there's a kernel stack for each LWP, all the usual things happen, and the system call
returns. And if several LWPs make system calls? They all execute independently and everything
works as expected, with the usual caveats.
If several calls affect the same data, things could turn ugly. For example, if two threads issue calls
to change the working directory, one of them is going to get a surprise. Or if two threads do
independent calls to read(), using the same file descriptor, the file pointer will not be
coordinated by either one of them, resulting in one thread reading from a different place than it
expected. We'll deal with these issues later.
The really nice thing about different threads being able to execute independent system calls is
when the calls are blocking system calls. Ten different threads can issue ten synchronous reads, all
of which block, yet all the other threads in the process can continue to compute. Cool.
Signals are the mechanism that UNIX has always used to get asynchronous behavior in a program.
With threads, we are able to get most asynchronous behavior without signals. Only interruptions
need some signal-like mechanism in order to work. In Java, the UNIX signal model is not used at
all (this is a good thing!) and interruptions are done by using the exception system.
Threads libraries can be implemented as self-contained user-level libraries or as kernel-based
routines. The same program can be written in either, the difference often being quite minor. The
main distinction of threads vs. processes is that threads share all process resources and data. The
programming trade-offs, problems, and designs are the same for POSIX, Win32, and Java.
Search WWH :