Eduardo Tongson wrote:

AS> Ah, so DragonFly is doggedly sticking to M:N eh?  Interesting.
AS>

ET> DragonFly will be using a MxN variant, M:Ncpus

How does M:Ncpus differ exactly from M:N ?


AS> The Linux clone() call can take different parameters to make AS> spawned code take on either the characteristics of threads AS> OR processes. Not sure if there is a tradeoff involved or AS> if it is truly the more elegant solution.

ET> clone() purpose was to create and allow _threads_ to run just
ET> like processes (they are actually processes in the kernel) which
ET> are allowed to block in the kernel without intervening with
ET> each other.  The problem with 1:1 is the overhead of forking a
ET> kernel process for each thread and expensive context switches and
ET> operations to have them behave accordingly to spec. M:N mitigates
ET> the disadvantages of the M:1 and 1:1 models.

Note that linux processes are different from Unix processes in that
they are much more efficient (NT processes have the most overhead,
but then NT supported threads before most Unices did and NT /threads/
are much more efficient than Unix /processes/).

According to the very interesting article at
http://www.itworld.com/nl/lnx_tip/02092001/ ,

1) "Unlike processes, threads run within the same address space
and share their process' data. In such environments, the thread
creation and destruction takes place considerably faster compared
to a full-blown process' creation or destruction. Under Solaris,
for example, launching a new thread is about 70 times faster than
launching a new process. Linux, however, is radically different."

2) "Under Linux, threads and processes are almost indistinguishable
except for one thing: threads run within the same address space
whereas processes have distinct address spaces. However, no differences
exist between the two from a scheduler point-of-view.  Thus, a context
switch between two threads of the same process essentially jumps from
one code location to another, plus setting a few CPU registers."

3) "In this regard, Linux newcomers often are unaware of the substantial
differences between Linux and other operating systems. To implement
concurrency, they use multithreading exclusively, mistakenly assuming
as high an overhead associated with Linux multiprocessing as on other
platforms. However, this is not the case. In fact, many multithreaded
applications ported from other platforms to Linux can benefit from
replacing multithreading with multiprocessing; this will eliminate
the overhead of critical sections and other locking mechanisms used
in multithreaded applications."

-------------------------------------------------------------------

As 2) hints at, one of the things the clone() parameters allow you
to control is address space sharing.  If the clone() parameters
specify shared address space, it's essentially creating a thread.
If you allocate your own address space (with copy-on-write) for
the newly spawned 'execution entity', it would be a process.

What 3) is saying is that you can throw away all the nasty critical
sections, mutexes, semaphores, (i.e. locking mechanism kludges)
if you use processes - clone() with parameters specifying own address
space - rather than threads  - clone() with parameters specifying
/shared/ address space.

These thread locking mechanisms create their own kind of performance
overhead but more importantly, tremendous debugging headaches.  In
fact the latter has led to a sentiment that 'threads are evil'.

So if there is little overhead difference between spawning Linux
'processes' and 'threads', spawning a 'process' (i.e. clone()ing
a newly spawned 'execution entity' with its own copy-on-write
memory/address space) is far more desirable.

Besides thread/process creation time, there is the issue of context
switching.  Now, process context switching will admittedly introduce
more overhead than thread context switching, but we are also led to
believe that under Linux, the penalty is much smaller(?) than on
traditional Unix.

-------------------------------------------------------------

So I've been wondering from way back: if NPTL threads are
so efficiently spawned and scheduled, can't the same apply
to 2.6 fork()ed processes?

If you use clone(), you are not portable, but why can't fork()
be nearly as efficient as NPTL threads (or at least exhibit
similar dramatic improvement compared to the old fork() )
since they are both based on the same underlying clone()
call??

What special feature of 2.6 does NPTL take advantage of?  futexes?
Anything else besides that?  And can't we apply something similar
to fork()ed processes?

fork(), in many ways, offers a far cleaner API than pthreads,
so if we can get an efficient fork() on top of 2.6's clone(),
we can shun the overengineered pthreads API (and threads in
general) and get greatly improved performance for both new and old
fork()-based unix code (the latter, probably without even needing
to recompile).

Such code would not be very efficient on other unixes, but would
still compile properly... and assuming the above scenario holds,
would fly on Linux without all the nastiness of thread programming.

Also, if neither fork() nor pthreads will do it for you, you can
always invent your own concurrency mechanisms.  If you want
to take advantage of kernel scheduling (necessary for SMP, but
not otherwise) though, they will have to built on top of clone()
on Linux.


AS> (Side note on thread APIs: Linus and the other hackers on the AS> kernel mailing list hate the posix threads API and consider it AS> overengineered. The main reason to use it is only because it AS> is a cross-*nix standard. You can probably do a direct clone() AS> call in your app if you don't care about running on under *nixes.)

ET> This is a bad thing as it implies that users of Linux should conform
ET> to _their_ preferred methods rather than using portable standards.
ET> As always this will limit dev and user freedom. Good thing they
ET> allowed Ulrich Drepper et al to provide the portable standard in
ET> this case NPTL which delivers quite well.

Agree.  Anyway, I forgot that the clone() system call can be wrapped
up by traditional fork() and *fork() calls (to get portability) while
I was writing the above.  :-D



--
reply-to: a n d y @ n e o t i t a n s . c o m
http://www.neotitans.com
Web and Software Development

--
Philippine Linux Users' Group (PLUG) Mailing List
[email protected] (#PLUG @ irc.free.net.ph)
Official Website: http://plug.linux.org.ph
Searchable Archives: http://marc.free.net.ph
.
To leave, go to http://lists.q-linux.com/mailman/listinfo/plug
.
Are you a Linux newbie? To join the newbie list, go to
http://lists.q-linux.com/mailman/listinfo/ph-linux-newbie

Reply via email to