In Windows NT, process is just a data structure and the kernel
schedules the threads. If a falling thread and a rising thread happen
to belong to the same process, there is very less overhead due to
context switch. In the other cases, it is more.
I don't know about the UNIX world. WOuld someone be able to explain
how the "two level" system and "pure" kernel thread system differ and
hwo they work?
Also, the FAQ just mentions the WinNT style threads as a separate
category. But it doesn't mention what exactly is the difference.
Thanks,
S.R.
-------------------------------------------------------------------------
Ramesh Shankar (S. Ramesh)
Novell Inc.,
Provo, UT
All opinions are my own. I don't speak for Novell Inc.
-------------------------------------------------------------------------
>I read the FAQ and got confused with the usage of LWP and Thread
>terminology.
You know, I read that part of the FAQ and got confused at the exact same
point. So I scratched my head for about 10 minutes (while bouncing ideas
off of a coworker about what it meant), and I think I finally stumbled upon
what the FAQ author was trying to say. I'll tell you below.
I think what they meant by what the difference was between LWPs and a
thread is that an LWP is a just the most molecular form of a time slice
available from the kernel; it saves only the most important state
information, but no more than that. This is the lightest weight process you
can get. Whereas in a HWP, you not only get all of the features of the LWP,
but also you have much more state information being saved, thus resulting
in a reduction of performance in return for an increase in safety. Because
the LWP is much closer to the definition of what a thread is, many
operating systems may simply use a LWP as a thread, by mapping a thread to
an LWP.
However, LWPs are still a kernel construct just like HWPs are. So there may
only be a limited number of them available at a time. If let's say there is
a maximum of 50 LWPs available per process, and let's say the process has
spawned 100 threads, then obviously every thread can't be mapped into an
LWP because there aren't enough. So each thread probably takes their turns
being mapped to the available LWPs. A thread management library running
within the application itself probably maps and remaps threads to LWPs
based on an internal priority algorithm.
>In Windows NT, process is just a data structure and the kernel
>schedules the threads. If a falling thread and a rising thread happen
>to belong to the same process, there is very less overhead due to
>context switch. In the other cases, it is more.
>I don't know about the UNIX world. WOuld someone be able to explain
>how the "two level" system and "pure" kernel thread system differ and
>hwo they work?
I believe the above explanation covers "two-level" hybrid threads.
A "pure" kernel thread would be one which doesn't require that user-level
thread management library, because it is all handled inside the kernel.
I don't know what you meant by a "rising thread" and a "falling thread".
But threads in general are supposed to eliminate much of the overhead of
context switches as is the case with full heavy-weight processes.
Now, back to the "pure" vs "two-level". I believe the FAQ's
characterization of the pure kernel threads as being less efficient than
the hybrids has to do with the overhead of the memory protection mechanisms
of various processors. Because everytime there is a context switch, the
user-level program drops through a protection barrier to the kernel level,
a memory protection check has to be performed. The kernel is responsible
for doing the protection check, and kernel is also responsible scheduling
time slices. Therefore in a "pure" system, two separate drops to the kernel
are done (once for protection checks, and once for scheduling) for every
thread context switch. With a hybrid, no drops to the kernel are necessary
for a context switch between threads, because the thread scheduling manager
library is running right there at user-level too (you save the overhead of
a protection check). Of course a context switch between processes, would
still require a drop down to the kernel.
>Also, the FAQ just mentions the WinNT style threads as a separate
>category. But it doesn't mention what exactly is the difference.
I understand that all of the Unix threads packages are based off of
Microsoft's threads implementation (first in OS/2 and then later in Win NT
& 95). The concepts are the same, but they've probably evolved away from
each other in some minor ways. I don't know myself, and I'm hoping somebody
here can delve more deeply into this issue. Posix vs. Microsoft threads,
that is.
Yousuf Khan
--
Yousuf J. Khan
yk...@achilles.net
Ottawa, Ont, Canada
Nation's capital
You need a LWP to run a thread. The number of LWPs and threads
in a process can be different, so threads share a pool of LWPs
(think of it as a jobshop system where the threads are jobs and
LWPs are the workers). LWPs are scheduled by the kernel and
(unbounded) threads are scheduled by the thread scheduler (thread)
in the user process. Since scheduling LWPs are performed by the
kernel, it will require a context switch and is more expensive.
Switching threads to run on a LWP is cheap (no context switch).
>In Windows NT, process is just a data structure and the kernel
>schedules the threads. If a falling thread and a rising thread happen
>to belong to the same process, there is very less overhead due to
>context switch. In the other cases, it is more.
>
>I don't know about the UNIX world. WOuld someone be able to explain
>how the "two level" system and "pure" kernel thread system differ and
>hwo they work?
I have no idea how the Windows world operates (mainly because
it's such a hostile environment to do development, e.g.,
we have tried to find how to access the audio device in a PC for
a month, but in vain...), so I will only tell you how threads works in
UNIX. E.g., you have 5 threads t1-t5 and 2 LWPs l1 and l2.
Say t1 and t2 are running on l1 and l2 (l1 and l2 are running on
CPU c1 and c2). Suppose t1 makes a blocking system call. The scheduler
thread in the process will move t1 away from l1 and assign l1 to some
other thread (t3, t4 or t5). This is cheap. But suppose l1's time slice
runs out, then the kernel's scheduler will take l1 away from c1 and
run another LWP on it. In the process t1 also stops (until l1 gets a
CPU again), but this stopping is more expensive. This is a two level
system.
--
Shun Yan Cheung | Email: che...@mathcs.emory.edu | Phone: (404) 727-3823
Emory University | http://www.mathcs.emory.edu | Fax: (404) 727-5611
Dept of Math and CS | --------------------------------------------------------
Atlanta, GA 30322 | Never stop learning ---- `Nee heb je, ja kun je krijgen'
That's sort of a big question. May I suggest that reading one of the books
would really be the best thing to do? We'd be happy to discuss details after
that. I'll suggest that my book would be the best because we do discuss NT a
bit. Several other books would also suffice. Check out the FAQs at:
http://www.LambdaCS.com and http://www.serpentine.com/~bos/threads-faq
which give summaries of each of the current books.
-Bil
Shankar wrote:
>
> I read the FAQ and got confused with the usage of LWP and Thread
> terminology.
>
> I don't know about the UNIX world. WOuld someone be able to explain
> how the "two level" system and "pure" kernel thread system differ and
> hwo they work?
>
> Also, the FAQ just mentions the WinNT style threads as a separate
> category. But it doesn't mention what exactly is the difference.
>
--
================
B...@LambdaCS.com
http://www.LambdaCS.com
Lambda Computer Science
555 Bryant St. #194
Palo Alto, CA,
94301
Phone/FAX: (415) 328-8952
> I understand that all of the Unix threads packages are based off of
> Microsoft's threads implementation (first in OS/2 and then later in Win NT
> & 95). The concepts are the same, but they've probably evolved away from
> each other in some minor ways. I don't know myself, and I'm hoping somebody
> here can delve more deeply into this issue. Posix vs. Microsoft threads,
> that is.
That's pretty funny. UNIX thread packages have been around far longer than
Win NT, and
Microsoft's capabilities are still way behind -- less flexible, less
scaleable, and with very primitive and awkward "stone age" synchronization
primitives that researchers have been known to be inferior (to what became
the model for the POSIX interface) for decades.
When we started working on Digital's threading architecture, long before
POSIX, some zealous members of the team looking into patenting some
interesting aspects of the threading model. We quickly discovered that
anyone trying to patent just about anything "basic" in threads had better
be filing in the early 1960s -- because by the late '60s it had already
all been done (and published, and shipping in commercial systems).
/---[ Dave Butenhof ]-----------------------[ bute...@zko.dec.com ]---\
| Digital Equipment Corporation 110 Spit Brook Rd ZKO2-3/Q18 |
| 603.881.2218, FAX 603.881.0120 Nashua NH 03062-2698 |
\-----------------[ Better Living Through Concurrency ]----------------/
Most of what you say here I have no reason to dispute. In particular, it is
well known that Microsoft didn't invent threads, and I have seen benchmarks
that demonstrate that UNIX threads have superior scalability. But your claim
that NT's threading capabilities are "less flexible", "with very primitive and
awkward 'stone age' synchronization primitives", puzzles me.
Perhaps you are speaking in terms of the internals of the NT kernel, in which
case I'll have to defer to your expertise, but the Win32 threading API, on the
whole, strikes me as higher-level and more sophisticated than the POSIX API.
For instance, Win32 has both a lightweight critical section object similar to
POSIX mutexes and a full-featured mutex, with abandonment detection and locking
timeout. Also, POSIX has nothing even remotely approaching Win32's extremely
powerful WaitForMultipleObjects API. And Win32 threads have built-in message
queue's that are often very handy for solving asynchronous inter-thread messaging
problems.
About the only two parts of POSIX that I wish Win32 threads had are (1) condition
variables (which can be implemented in terms of Win32's primitives, as Doug
Schmidt has demonstrated in ACE) and (2) TSD destructor callback functions.
Ian
___________________________________________________________________________
Ian Emmons Work phone: (415) 372-3623
i...@persistence.com Work fax: (415) 341-8432
Persistence Software, 1720 S. Amphlett Blvd. Suite 300, San Mateo, CA 94402
Yes, It reminded me of stack.
yzhang
-------------------==== Posted via Deja News ====-----------------------
http://www.dejanews.com/ Search, Read, Post to Usenet
Perhaps Unix threads packages may have been around longer than Windows NT,
but not OS/2. Remember OS/2 is also a Microsoft-originated operating
system. The model for Windows NT threads are based off of OS/2 1.x threads.
OS/2 came out in 1986.
Aesthetic stylistic paradigms aside, there's no doubt that Microsoft easily
beat any Unix platform to multithreading with OS/2. Multithreading only
started appearing on Unix platforms around the nineties (mid-nineties). Of
course Unix wasn't actually suffering from this lack of foresight, because
Unix has had its forked processes for a long time which provided roughly
the same functionality as threads.
What I meant by "based on Microsoft threads" is that since Microsoft came
up with threads so early on that the Unix thread implementors may have had
time to copy and improve those mechanisms.
> When we started working on Digital's threading architecture, long before
> POSIX, some zealous members of the team looking into patenting some
> interesting aspects of the threading model. We quickly discovered that
> anyone trying to patent just about anything "basic" in threads had better
> be filing in the early 1960s -- because by the late '60s it had already
> all been done (and published, and shipping in commercial systems).
Oh yeah, no doubt about it, probably a lot of stuff that is considered
"new" in the field of computers these days may already have been tried in
one variation or another in the olden days of mainframe computing decades
earlier.
Yousuf Khan
Um, no. I remember there being threads packages available for BSD and
friends in the 1987 time frame.
<b
--
Let us pray:
What a Great System. b...@eng.sun.com
Please Do Not Crash. b...@serpentine.com
^G^IP@P6 http://www.serpentine.com/~bos
Well, this is clearly bullshit, but ...
> > & 95). The concepts are the same, but they've probably evolved away from
> > each other in some minor ways. I don't know myself, and I'm hoping somebody
> > here can delve more deeply into this issue. Posix vs. Microsoft threads,
> > that is.
>
> That's pretty funny. UNIX thread packages have been around far longer than
> Win NT, and
NT, maybe. But which commercial UNIX systems had preemtive threading
that predates OS/2 1.0? I'm not aware of any ...
> Microsoft's capabilities are still way behind -- less flexible, less
> scaleable, and with very primitive and awkward "stone age" synchronization
> primitives that researchers have been known to be inferior (to what became
> the model for the POSIX interface) for decades.
I think you need to justify this. Its true that the NT primitives are a
bit
slow, but then that's partly because they aren't as primitive (in some
respects). For example, mutexes are recursive (yeah, I know you don't
like
those ... but it can be useful) and they inform waiters when a holder
dies
unnaturally. There was a long thread about doing this for pthreads etc
(summary: you can't - *you* claim that you don't want too, but I don't
think
that's true).
It would certainly be nice to have an NT kernel provided condition
variable
and maybe a barrier too. But these can be emulated, at a cost.
So come on, what's 'stone age' and 'inferior'? Different, yes. (It is
after
all pretty easy to moan about the difficulty of emulating some of the MS
facilities
in UNIX, and Digital UNIX would seem to be lacking more thatn, say,
Solaris too)
>
> When we started working on Digital's threading architecture, long before
> POSIX, some zealous members of the team looking into patenting some
> interesting aspects of the threading model. We quickly discovered that
> anyone trying to patent just about anything "basic" in threads had better
> be filing in the early 1960s -- because by the late '60s it had already
> all been done (and published, and shipping in commercial systems).
Sure. But I don't think those systems were UNIX.
>
> /---[ Dave Butenhof ]-----------------------[ bute...@zko.dec.com ]---\
> | Digital Equipment Corporation 110 Spit Brook Rd ZKO2-3/Q18 |
> | 603.881.2218, FAX 603.881.0120 Nashua NH 03062-2698 |
> \-----------------[ Better Living Through Concurrency ]----------------/
James
In article <01bc0895$a1de9700$2001c8c8@ypc>,
"Yousuf Khan" <yk...@bellglobal.com> writes:
>Perhaps Unix threads packages may have been around longer than Windows NT,
>but not OS/2. Remember OS/2 is also a Microsoft-originated operating
>system. The model for Windows NT threads are based off of OS/2 1.x threads.
>OS/2 came out in 1986.
It was a joint Microsoft/IBM venture then.
My money would be in that bit of initiative coming from the IBM side...
>Aesthetic stylistic paradigms aside, there's no doubt that Microsoft easily
>beat any Unix platform to multithreading with OS/2. Multithreading only
>started appearing on Unix platforms around the nineties (mid-nineties). Of
>course Unix wasn't actually suffering from this lack of foresight, because
>Unix has had its forked processes for a long time which provided roughly
>the same functionality as threads.
>
>What I meant by "based on Microsoft threads" is that since Microsoft came
>up with threads so early on that the Unix thread implementors may have had
>time to copy and improve those mechanisms.
>
>Dave Butenhof <bute...@zko.dec.com> wrote in article
><butenhof-220...@usr607.zko.dec.com>...
>> When we started working on Digital's threading architecture, long before
>> POSIX, some zealous members of the team looking into patenting some
>> interesting aspects of the threading model. We quickly discovered that
>> anyone trying to patent just about anything "basic" in threads had better
>> be filing in the early 1960s -- because by the late '60s it had already
>> all been done (and published, and shipping in commercial systems).
>
>Oh yeah, no doubt about it, probably a lot of stuff that is considered
>"new" in the field of computers these days may already have been tried in
>one variation or another in the olden days of mainframe computing decades
>earlier.
Certainly I was working on a threaded application in 1983 (it started
in 1980 IIRC). This was on a real-time mini-computer, whose executive
and [short term] process scheduling were done in hardware - no software
kernel for example. pthread_mutex_lock, pthread_mutex_trylock, and
pthread_mutex_unlock were each a single machine instruction (although
then known by the names 'claim', 'cond claim', and 'release' respectively).
Multiple threads of execution were done by setting up the VM tables
for a number of processes to all point at the same address space,
effectively forming what we would now think of as a number of bound
lightweight processes.
There's little new under the sun; just the terminology changes...
Actually, this mini-computer also implemented nucleus and doors, pretty
much as described in Sun's white papers on their 'Spring' experimental
operating system. Each doors operation was again a single instruction
and nucleus was implemented in hardware...
Now we're back to a 1969 design... :-)
--
Andrew Gabriel Home: And...@cucumber.demon.co.uk
Consultant Software Engineer Work: Andrew....@net-tel.co.uk
I think it was earlier than this for UNIX systems. The mid nineties
wasn't long ago, and Solaris 2.x has been around for a while (even if
the much hyped threads didn't *actually* work to start with) and
Mach has had threads for a while, as have DCE systems.
>
> Um, no. I remember there being threads packages available for BSD and
> friends in the 1987 time frame.
Hmm. Name a commercial system of type 'BSD and friends' that was
supported on public release and which included preemptive threading.
OS/2 1.0 did have this stuff from the word go.
I think you're on a loser here. Unless you can think of something
very obscure.
(I suggest that we rule out non-preemptive systems, since they
tend to limit what you can do naturally. By that, I mean take
a piece of code that computes for a long time in a single
threaded env, and drop it in to a multithread env, assuming that it
doesn't need to synchronise, and may not do IO. Its probably a
bit mean to rule out ALL user-space threading, even though I
feel that thread packages that ONLY synchronise within a process
are severely limited)
>
> <b
>
> --
> Let us pray:
> What a Great System. b...@eng.sun.com
> Please Do Not Crash. b...@serpentine.com
> ^G^IP@P6 http://www.serpentine.com/~bos
James
>> y> Aesthetic stylistic paradigms aside, there's no doubt that
>> y> Microsoft easily beat any Unix platform to multithreading with
>> y> OS/2. Multithreading only started appearing on Unix platforms
>> y> around the nineties (mid-nineties).
>I think it was earlier than this for UNIX systems. The mid nineties
>wasn't long ago, and Solaris 2.x has been around for a while (even if
>the much hyped threads didn't *actually* work to start with) and
>Mach has had threads for a while, as have DCE systems.
I remember when the first Solaris 2.x for Sparc came out, it was around
1992. OS/2 is still five years older still.
>> Um, no. I remember there being threads packages available for BSD and
>> friends in the 1987 time frame.
>Hmm. Name a commercial system of type 'BSD and friends' that was
>supported on public release and which included preemptive threading.
>OS/2 1.0 did have this stuff from the word go.
That's my feeling too. I don't know of any widely-distributed Unix with
threading before the 1990's. The only possibility is that someone was
experimenting with something or another under Unix in academia (which is
quite likely), prior to OS/2.
"Events" are awkward, and (relatively) slow. Condition variables are
simple, elegant, and efficient. We could all be using Dijkstra's
original semaphore for everything -- and, for some uses, they're still
the best. But we've learned a lot since then, and most of the time we
can do better. Condition variables are better, for more things, than
semaphores and events. They're easier to use correctly, more efficient,
and more flexible.
> For instance, Win32 has both a lightweight critical section object similar to
> POSIX mutexes and a full-featured mutex, with abandonment detection and locking
> timeout. Also, POSIX has nothing even remotely approaching Win32's extremely
> powerful WaitForMultipleObjects API. And Win32 threads have built-in message
> queue's that are often very handy for solving asynchronous inter-thread messaging
> problems.
A default POSIX mutex is "a mutex" -- talking about an additional "full
featured" mutex is meaningless. Sure, you can add all sorts of extras.
We added several one-plus mutex types to DCE threads, and they're being
codified by The Open Group for a follow-on to the "Single UNIX
Specification". "Mutexes", "Mutants", and "Critical sections" are
needless obfuscation.
WaitForMultipleObjects can be nice -- but it doesn't have anything
to do with the basic threading model. Queues are easy, and there are so
many ways to use queues that no O/S service is going to be perfect for
every use.
> About the only two parts of POSIX that I wish Win32 threads had are (1) condition
> variables (which can be implemented in terms of Win32's primitives, as Doug
> Schmidt has demonstrated in ACE) and (2) TSD destructor callback functions.
Condition variables are simple primitives. You can build just about any
synchronization/signalling system out of just about any other. But you
will have an easier time (and far better performance) building COMPLEX
mechanisms out of SIMPLE mechanisms than the other way around. Mutexes
and condition variables are the simple ones. That's why POSIX has them,
and that's exactly my point.
In the end, your choice of synchronization and signalling models are a
matter of personal choice. I like mine better than yours, and you appear
to like yours better than mine. Cool. That's the way it should be.
> "Events" are awkward, and (relatively) slow. Condition variables are
Hmm - *all* the NT primitives are relatively slow. But not so slow that
its a problem if you've designed things right.
> simple, elegant, and efficient. We could all be using Dijkstra's
> original semaphore for everything -- and, for some uses, they're still
> the best. But we've learned a lot since then, and most of the time we
> can do better. Condition variables are better, for more things, than
> semaphores and events. They're easier to use correctly, more efficient,
> and more flexible.
True. (Well, true-ish). But condition variables are quite unlike
anything else,
because they are not self-contained. One of the major conveniences of
the NT model is that the primitives are referenced by handles, can have
names,
are use-counted, and have no race condition on creation and
initialisation.
Admitedly this doesn't preclude the use of composites where the
initialisation
of a condition variable would be given the handle to a mutex.
I think POSIX (and DCE, and UI) screwed up here, sad to say. The
primitives
are much easier to deploy intra-process than inter-process. And UNIX
definitely
needs better inter-process primitives than SysV semaphores. ;-)
<snip>
> Specification". "Mutexes", "Mutants", and "Critical sections" are
> needless obfuscation.
I can't agree with that. If there's a common usage then it makes sense
to codify it, even if it can be synthesised - it means that an OS
designer
might be able to use a shortcut, and users get reuse.
(Admitedly NT critical sections are a bit of a misnomer, but then its
probably less confusing than 'faster mutex that works in one process'.)
>
> WaitForMultipleObjects can be nice -- but it doesn't have anything
> to do with the basic threading model. Queues are easy, and there are so
> many ways to use queues that no O/S service is going to be perfect for
> every use.
I think that's sour grapes. A queue is a queue. Remember that
WaitForMultipleObjects
is also analogous to select in some respects. Admitedly its crippled by
a
stupidly low maximum number of handles. I think the multi-wait *IS*
a critical part of the 'basic threading model' that you use to build NT
applications.
In this case its not an equivalent in any case, since to use a queue
the code that signals the event must know that its integrated into a
multi-
wait system, while something that just signals a semaphore need know
nothing about how it will be used. Sure, you could put an interface
thread
in there as a common pattern, but threads are heavy on address space
usage, and also scheduling, in this context (and I can't see how to
emulate
the same functionality).
However, NT makes a passable (and in many cases entirely adequate)
attempt
to unify:
- arbitrary synchronisation primitives
- async IO on files and sockets (yeah, it could be tidier with sockets
...)
- timers
- GUI events
This is good, just as the IO unification in select is good. Personally
I'd
have likes the POSIX primitives to have file handle characteristics and
be
integrated into select/poll. And yes, I know that POSIX (at least
1003.1)
covers neither select nor poll, which I think is a mistake. :-(
I can see how to fake 'wait for multiple' for 'all' (just about, though
it is
very messy), but you can't do a decent fake on 'any' so far as I can
see.
>
> > About the only two parts of POSIX that I wish Win32 threads had are (1) condition
> > variables (which can be implemented in terms of Win32's primitives, as Doug
> > Schmidt has demonstrated in ACE) and (2) TSD destructor callback functions.
>
> Condition variables are simple primitives. You can build just about any
> synchronization/signalling system out of just about any other. But you
> will have an easier time (and far better performance) building COMPLEX
> mechanisms out of SIMPLE mechanisms than the other way around. Mutexes
> and condition variables are the simple ones. That's why POSIX has them,
> and that's exactly my point.
This is true, but POSIX skips other issues and is quite weak in practice
when you are synchronising between multiple processes. You end up
needing
something to serialise the setup of a control shared mem structure which
is actually serialised properly, so that you can get that first critical
mutex set up in the shared memory. Stupid that there's no nice way to
do this, nor to detect when to destroy them.
>
> In the end, your choice of synchronization and signalling models are a
> matter of personal choice. I like mine better than yours, and you appear
> to like yours better than mine. Cool. That's the way it should be.
That doesn't mean that we can't figure a wish list that would enable
a superset of both with all the benefits though.
I get the feeling that you don't think NT has *any* advantages and are
rather protective of the POSIX model. I think that's a shame. As you
say,
it has some advantages over NT, but in other ways I hope you'd agree
that
it is lacking.
>
> /---[ Dave Butenhof ]-----------------------[ bute...@zko.dec.com ]---\
> | Digital Equipment Corporation 110 Spit Brook Rd ZKO2-3/Q18 |
> | 603.881.2218, FAX 603.881.0120 Nashua NH 03062-2698 |
> \-----------------[ Better Living Through Concurrency ]----------------/
James
Except for critical sections, of course.
++ are use-counted, and have no
++ race condition on creation and initialisation.
Yes, that is definitely nice.
++ Admitedly this doesn't preclude the use of composites where the
++ initialisation of a condition variable would be given the handle to
++ a mutex.
BTW, I believe that it's trivial to extend WinNT 4.0 so that building
condition variables would be simple and efficient. All that needs to
be done is to extend SignalObjectAndWait (e.g., SignalObjectAndWaitEx)
so that it takes *two* handles to wait on, rather than one. These two
handles could then be an auto-event (for handling cond_signal) and a
manual-event (for handling cond_broadcast).
I think that with this minor change, all the nasty complexity and
inefficiencies that's currently necessary to emulate POSIX condition
variable semantics could be eliminated.
Doug
--
Dr. Douglas C. Schmidt (sch...@cs.wustl.edu)
Department of Computer Science, Washington University
St. Louis, MO 63130. Work #: (314) 935-4215; FAX #: (314) 935-7302
http://www.cs.wustl.edu/~schmidt/