AIX Manual Threads
AIX Manual Threads
Threads
The AIX Version 4.1 kernel now supports multiple threads of control within a
single process. An application can use this to bypass classical Inter-Process
Communications (IPC). That is, data can be shared between the various threads
of control using normal process storage, and access to this data is coordinated
using library routines, thus removing the overhead of IPC associated with shared
memory and semaphores.
There is also a set of kernel services provided to the writer of kernel extensions
for creating and managing threads. New locking services are also provided to
assist the kernel developer working in the multi-processor environment. Now a
kernel developer can write software so it will run on both uni-processor and
multi-processor machines.
13.2 Terms
The threads programming environment has new terminology that needs to be
understood.
Multi-Threaded A program that has multiple threads of control.
This can run in user or kernel space.
Multi-Processor A hardware system that has multiple processors -
multiple units executing instructions.
Symmetrical Multi-Processor A multi-processor system in which the capabilities
and configuration of each processor is (nearly)
identical. This means that the various processors
run a single copy of software, and share and
1 POSIX 1003.4a Draft 7 is not an approved standard. It is IBM′s intention to track evolving standards activity in this area.
The AIX Version 4.1 threads support is based on the OSF/1 libpthreads
implementation. It supports what is referred to as a 1:1 model. This means that
for every thread visible to an application, there is a corresponding kernel thread.
In contrast, the DCE pthreads (POSIX threads) package, available on earlier
releases of AIX, supported a N:1 model. This means that all the threads visible
to a process were mapped onto a single kernel thread. Architecturally, it is
possible to have a M:N libpthreads model, where (“M”) user threads are
multiplexed on a number (“N”) of kernel threads. However, this is not supported
by AIX Version 4.1.
13.3 Threads
In an AIX process, there is one stream of instructions that is in control of the
process at one time. In a multi-threaded process, the process starts out with
one stream of instructions and may later create other instruction streams, called
threads, to do tasks. This is very much like one process forking another
process. There are, however, two main differences between forking a child
process and creating a thread within the current process:
Facilities are provided to the programmer to do many of the same things that
can be done with processes. These facilities include calls for thread creation,
termination, synchronizing, communication, error recovery, and management.
When a thread is running in user space, it can make system calls just like in a
simple process. Thus, the programmer has the same level of control over how
the threads behave and what services they request of the system within the
process that they have always had for individual processes.
In a multi-threaded process, there is the concept of user threads that run in user
space. Each such user thread is associated with a kernel thread that runs in
kernel space (the “1:1 model” mentioned above). It is the kernel thread that is
managed by the scheduler and handles all of the kernel requirements of the user
thread. A kernel thread, however, may be associated with a user thread to do
user work or it may be unassociated with any user thread and running as a
kernel thread (that is, a kproc or a kernel extension).
Kernel threads appear and behave in AIX Version 4.1 much like kernel
processes did in AIX Version 3.2. It is in user space that the differences between
threads and processes become apparent.
There is some kernel data that is shared between the threads, but the kernel
also maintains thread specific data. Process-related kernel data has been
stored in the proc and user structures. In AIX Version 4.1, these control blocks
have been broken up in process- and thread-specific structures. The user and
proc structures now contain only data that is maintained at the process level.
There is now a thread structure that contains thread-specific data that was
formerly in the proc structure. There is also a uthread structure that contains
thread specific data that used to be in the user structure. The next chart shows
these new control blocks and some sample fields.
In AIX Version 3.2, the kernel maintained data and scheduled and managed
processes. In AIX Version 4.1, all of these functions need to be performed on
both processes and threads. The thread is now the unit of work that is managed
by the scheduler. Each kernel thread is dispatched individually. Some functions,
however, still operate on the process level. These functions affect all threads
within a process. A good example is setting the nice value for a process. That
nice value applies to all threads for the process. Another is exit, which
terminates all threads in a process.
So in AIX Version 4.1, while most operations are now performed on threads,
there are still functions that operate at the process level, and affect all threads in
the process. This is done for compatibility and practicality: in the case where
there is only a single thread in the process, these operations function as before.
There are now three scheduling algorithms that can be selected when creating a
thread.
SCHED_RR This is a round robin scheduling mechanism. The thread is time
sliced at a fixed priority.
SCHED_RR is similar to creating a fixed-priority, real-time
process. The thread must have root authority to be able to use
this scheduling mechanism.
SCHED_FIFO This is a non-preemptive scheduling mechanism. A thread
created with SCHED_FIFO will run at a fixed priority and will not
be timesliced. It will be allowed to run on a processor until it
voluntarily relinquishes by blocking or yielding.
A thread using SCHED_FIFO must also have root authority to
use it. It is possible to create a thread with SCHED_FIFO that
has a high enough priority that it could monopolize the
processor.
SCHED_OTHER This is normal AIX scheduling and is the default, where priority
degrades with CPU usage.
In an M:N implementation, there can be user threads that are not permanently
attached to kernel threads, in addition to system scope threads as described
above. These threads are called process scope threads . This allows multiple
user threads to be scheduled and multiplexed onto a smaller number of kernel
threads. This is all done in user space, as part of the libpthreads function,
thereby reducing demand on system resources. This sort of implementation is
particularly advantageous for applications that have large numbers of threads,
most of which are waiting most of the time.
All process scope threads contend for resources within the process. They are
scheduled onto a smaller pool of system scope threads. The advantage of this is
when a process scope thread is running and it blocks, the system scope thread
suspends the user thread and starts running the next available process scope
thread. So the process scope threads are scheduled onto the system scope
threads and the kernel threads associated with the system scope threads are
scheduled onto the processors.
You can select the type of scheduling for process scope threads. You can have
normal round robin, FIFO, or “other.” The “other” scheduling mechanism is a
priority-based scheduling scheme similar to what the system scheduler provides.
“Other” is the default.
Process scope threads can have large time slices or no time slices at all (FIFO).
Because they contend with other process scope threads in the process for
access to the process′s system scope threads and the real processors, they
should not be used when real-time scheduling is needed.
In the M:N model, there can be system scope threads and single-threaded
processes in addition to process scope threads. Also, there can be both system
and process scope threads within a process. An application would use system
13.6.2.1 Fork
In AIX Version 4.1, the child process is created with a single thread (the calling
thread). This new process contains a replica of the calling thread and its entire
address space at the time of the fork(). This means that the address space
could contain mutexes held by threads that do not exist in the child process.
Likewise, the data protected by these locks might not be in a consistent state. If
the child process attempts to acquire one of these mutexes, it could hang; if it
attempts to use the data protected by such a mutex, it could malfunction.
Therefore, any application using fork() in a multi-threaded application should
only execute safe operations between the call to fork() and exec(), to avoid
errors. (Safe operations are those that either do not take locks or are known to
be safe by the application.) Alternatively, fork handlers can be registered with
the pthread_atfork() routine in order to reset appropriate state.
The pthread_atfork() call registers three handlers to be called before and after
the call to the fork(). The three handlers are:
Prepare Called just before the fork() begins
Parent Called after the fork() is complete in the parent process
Child Called after the fork() is complete in the child process
The prepare fork handlers are called in last-in first-out (LIFO) order, whereas the
parent and child fork handlers are called in first-in first-out (FIFO) order. This
allows programs to preserve any desired locking order. The expected usage is
that the prepare handler acquires all mutex locks, and the other two handlers
release them.
The POSIX 1003.4a Draft 7 specification defines a new function, forkall(). This
function solves the problem of inconsistent data between the child and the
parent by requiring that the child contain replicas of all threads in the parent.
This routine, defined under the _POSIX_THREAD_FORKALL option, is not
implemented in AIX Version 4.1. Also, this function, by solving one problem
associated with fork, introduces another one, which is how to deal with threads
suspended in system calls or about to execute system calls that should not be
executed in the new process.
13.6.3 Signals
According to the POSIX 1003.1 standard, the sigaction() function is used to
establish actions to be taken upon receipt of a signal. For each signal, it can be
called to specify one of three types of actions:
SIG_DFL Allow the signal-specific default action to happen.
SIG_IGN Ignore the signal.
A pointer to a function Call the function when the signal is delivered to catch
the signal.
The standard further specifies the default action for signals, and whether or not
they can be caught, blocked or ignored. This behavior can be quite complex,
due to use of the signal mechanism for a variety of different purposes.
The POSIX 1003.4a Draft 7 specification extends this behavior for threads with
the following points:
1. A signal is delivered exactly once (that is, one time to one thread)
2. The actions specified by sigaction() apply to the entire process (that is, to all
threads in the process)
All the rules in POSIX 1003.1 about which signals cannot be caught, blocked, or
ignored remain the same in a threaded environment.
Signal Actions: The process can specify an action (accept the system default,
ignore the signal, or run a signal handler) associated with each signal. This
action is the same for all threads in the process. SIGSTOP and SIGKILL cannot
be caught or ignored.
Blocked Signals: Each thread has a signal mask that defines the set of signals
currently blocked from delivery to it. (Note that the POSIX 1003.1 standard
specifies that SIGKILL and SIGSTOP cannot be blocked, and that SIGCONT
continues a thread that is stopped, even if it has blocked SIGCONT.) The
sigaction(), sigsuspend(), and sigthreadmask() system calls control the
manipulation of the signal mask.
The kernel maintains a signal mask per kernel thread. Therefore, when the
calling thread is global, as in AIX Version 4.1 (which implements the 1:1 model),
Pending Signals: The sigpending() function returns the union of the set of
signals pending for the thread or the process.
Send a Signal
A process can send a signal to itself or another process with the kill() system
call. External events, such as terminal activity, can also send signals to a
process.
A thread can send a signal to a particular thread in the sending thread′s process
via pthread_kill(). (pthread_kill() cannot be used to send a signal from a thread
in one process to a thread in another process.)
The signal is delivered to the target thread if it does not block delivery of that
signal and the action associated with the signal is not “ignore.” If the signal
cannot be delivered, it remains pending until it is unblocked, it is selected by a
call to sigwait(), or the action associated with it is set to “ignore.”
Sigsuspend
The sigsuspend() function suspends the calling thread until any caught signal
arrives. It is similar to pause().
Sigwait
The POSIX 1003.4a Draft 7 specification defines a new interface, sigwait(), to wait
synchronously for a set of asynchronous signals. It returns the number of the
signal that ended the wait.
Furthermore, the signals defined by set must be blocked at the time of the call to
sigwait(). This means that, for a particular signal, a program cannot both specify
a signal handler with sigaction() and wait for it with sigwait().
The sigwait() routine is fully implemented inside the libpthreads library. A global
handler is installed for all the signals waited for by sigwait(). A shadow thread is
then created for the entire process. When a sigwaited signal arrives, the signal
handler wakes up the shadow thread, which wakes up the thread waiting for it.
If a signal arrives and there is no thread waiting for it, the signal is re-raised.
13.6.4 Libraries
If you want to be able to write multi-threaded applications, then you will have to
link your programs with the libpthread library.
Two libraries are thread safe in AIX Version 4.1. There is a thread-safe libc
called libc_r. This is also needed by all multi-threaded applications. There is
also a thread-safe libbsd.
The libc_r, besides being thread safe, contains modifications to crt0() and fork()
to handle thread creation. A variety of other calls were changed internally to
handle threads cancellation conditions.
13.6.5 Scheduling