IRIX Binary Compatibility, Part 5
Pages: 1, 2, 3
Thread Models in IRIX and NetBSD
IRIX supports portable threads, of course, but because this is all implemented in userland, it is transparent for our IRIX emulation on NetBSD. We do not have to set up anything to support that.
Native threads are implemented through sproc(2) system calls
and variants: nsproc(2), sprocsp(2),
and so on. Variants differ from the original system call by offering options
such as the possibility to choose where the new thread's stack will be
allocated. IRIX has special terminology for native threads created by
sproc(2): they constitute a share group.
Here is sproc(2)'s prototype:
pid_t sproc (void (*entry)(void *), unsigned inh, void *arg);
The first argument is the thread entry point. Once the kernel has created the new thread, it returns to userland and resumes execution at the address pointed to by the entry argument.
inh stands for inherit. This integer value holds a few flags
which specify whether various resources should be shared or not between the
thread and its parent. Sharable resources include: address space, UID/GID,
process limits, file descriptors, umask value, and working directory.
Finally, arg is a pointer to the thread entry function's
arguments. There is no argument counter, as the parent and the child are
supposed to know each other well enough to be aware of what is pointed to by
arg.
Now, what about NetBSD? Of course NetBSD supports user threads, although
there is no built-in user thread library. If you need user threads, you have to
install a third-party thread library. The NetBSD package collection includes
multiple user thread libraries: devel/unproven-pthreads,
devel/pth, and devel/mit-pthreads. Of course all the
packages work. There are three implementations available because none is
perfect, so we leave the choice to the user.
With native threads, things become more difficult since NetBSD does not support native threads yet. Given this situation, emulating IRIX native threads may seem an impossible task. Fortunately, it is not, and we will explain why a bit later. For now, let us explain why NetBSD is so poor regarding threading.
We explained before that both user threads and native threads have their drawbacks. In an ideal world, we would want both a standardized API, and a thread model where threads can take advantage of multiple processors and are not blocked because of a blocking system call issued by another thread.
NetBSD is now close to be that ideal world thanks to Nathan J. Williams, who is working on an hybrid thread model that maps user threads to kernel threads. This concept is called Scheduler Activations and was first introduced by Sun Microsystems in Solaris.
There is no thread capability built into NetBSD, whether user or native, just because we want the ideal world for NetBSD and nothing else. But before we enter the perfect world, we want to emulate IRIX native threads.
In fact, NetBSD does support native threads. NetBSD just lacks the API to
spawn native threads from userland. From the kernel, and especially from a
foreign OS emulation subsystem, there is no problem: you can create a native
thread. This has been used for Linux emulation first since a lot of Linux
applications require native threads through the clone(2) system
call. In order to emulate Linux's clone(2), NetBSD needed some
support for native threads. We end up with this strange situation where the
NetBSD kernel is able to emulate features that it does not support natively.
In the NetBSD kernel sources, native threads are supported through the
fork1(9) kernel function, found in sys/kern/kern_fork.c.
fork1(9) has some options, including the possibility to share the
virtual memory space with the parent.
It is worth mentioning that the NetBSD fork(2) system call
implementation is just a wrapper to fork1(9) with appropriate
arguments:
int sys_fork(struct proc *p, void *v, register_t *retval)
{
return (fork1(p, 0, SIGCHLD, NULL, 0, NULL, NULL, retval, NULL));
}
In this file, there is also a sys___clone() function, which is
a Linux clone(2) compatible system call. It is not exported in the
kernel API, but if you ever want to natively build a Linux application that
uses clone(2) on NetBSD, consider adding
sys___clone() to NetBSD native's syscall.master and
rebuild a kernel (note that you also need to patch the libc to make the system
call available).
Having described threads and the NetBSD implementation, let us move on to something more serious: the actual implementation.
Everything is done in sys/compat/irix/irix_prctl.c.
Here we have the various sproc(2) variant implementations:
irix_sys_pidsprocsp(), irix_sys_sprocsp() and
irix_sys_sproc(), respectively implementing
pidsprocsp(2), sprocsp(2), and sproc(2).
All these functions call irix_sproc() with appropriate arguments.
irix_sproc() does the actual work, which is mostly to call
fork1(9).