Pthread Support in Microsoft Windows Services for UNIX Version 3.5
Written by: Mark Funkenhauser
Abstract
The Microsoft® Windows® Services for UNIX (SFU) 3.5 product is a collection of software packages for UNIX users and administrators who need to work with or on Windows platforms. It includes cross-platform network services that allow you to integrate your Windows® and UNIX-based environments together. It also includes a complete UNIX system environment called Interix that installs and runs on Windows, co-existing with the Windows subsystem. This environment comes with hundreds of UNIX utilities, such as ksh, csh, awk and telnet, and a complete C and C++ programming development environment for UNIX applications. With the release of SFU 3.5, this development environment now includes support for POSIX threads (Pthreads) and the POSIX semaphore functions.
This white paper discusses the features, functionality and implementation of the new Interix Pthread support in the SFU 3.5 release.
Introduction
The Interix Pthread Implementation
Porting Pthread Source Code to Interix
Summary
Related Links
Appendix A – Pthread APIs implemented in Interix
The Microsoft Windows Services for UNIX product is a collection of UNIX interoperability tools, such as NFS server, NIS server, NFS client and Remote Shell services. It also contains the Interix technology, a complete UNIX environment that runs on Windows. Interix is like other UNIX systems—it has an operating system part which provides the fundamental UNIX functionality and it has a user environment part consisting of many standard UNIX applications, including software development tools, headers and libraries for the C, C++ and Fortran programming languages.
With the release of Windows Services for UNIX 3.5 in January 2004, Microsoft improves its industry-leading interoperability toolset by adding support for threads; specifically POSIX threads (or Pthreads) to the Interix technology. The Interix SDK in this release includes a full set of Pthread APIs and the subsystem implements the Pthread infrastructure necessary to support these APIs.
This white paper briefly describes the features of POSIX threads, followed by a summary of the features included in the Interix product and finally some technical details about the implementation of POSIX threads in Interix.
Interix is a UNIX environment for use on current Windows platforms. A complete Interix system is composed of several functional components: a subsystem, a collection of utilities, and a Software Developers Kit (SDK). The subsystem is like a UNIX kernel in that it provides the fundamental UNIX operating system functionality—the calls such as the read and write, process creation and management with the fork, and exec functions as well as the signature UNIX mechanisms like signals, named pipes (fifos), symlinks, job control and shared memory. The SDK allows one to build or migrate UNIX source code applications so they will execute on a Windows system. Many of the applications provided in the Interix environment are open source utilities from various UNIX distributions such as OpenBSD, FreeBSD and GNU. Many of these utilities were migrated to Interix by simply recompiling using the original source code.
The Interix environment looks very similar to many UNIX implementations. It has a single root filesystem (which is relative to the directory where it was installed) with the familiar directories such as /etc, /bin, /usr/sbin, /usr/share, /usr/local and /var/adm. Services for UNIX 3.5 provides the files that populate these directories, including many familiar UNIX utilities such as the ksh and csh shells; the little languages such as awk and sed; popular utilities such as more, vi and grep; and X11 client utilities such as xterm. Interix also provides daemon utilities, such as inetd, cron, and syslogd, that are started automatically via the init process and the scripts in the /etc/init.d directory. SFU also provides the Interix SDK, which contains several tools such as gcc and g++ for building applications, and it contains many headers and libraries, such as libc, libcurses, and libX11, which make it easy to migrate existing UNIX applications to Windows.
Starting with the Services for UNIX 3.5 release, Interix includes support for POSIX threads and POSIX internationalization. Many new APIs are available in this release and several utilities have been enhanced to make use of this new functionality. For instance, the utility /bin/localedef can be used to create customized locales for use in Europe and Asia.
The introduction of thread support is very exciting because it opens the way for a new class of utilities to be migrated from existing UNIX systems to Interix and Windows.
A thread is an independent path of execution within a process. A single process may have multiple threads running concurrently within it. A process shares all its system resources (such as memory address space, code, and global data) with all these threads. A thread runs a procedure asynchronously to other threads running the same or different procedures within the process. Each thread has its own specific characteristics and attributes, such as thread ID, scheduling priority, error number (errno) value, thread specific bindings and the system resources required to support this single flow of control. The thread is the basic unit to which the system allocates processor time. The scheduling of thread execution is based on thread priority and processor availability and on a multiprocessor system, different threads can be scheduled to execute on each processor.
Before SFU 3.5, Interix supported only processes which allowed a single thread of control executing within a single address space. An application that used multiple processes had to implement some form of inter-process communication (IPC) in order to transfer information between these processes. This could result in significant overhead and possibly a communication bottleneck. In contrast, on systems that provide multi-threading, threads within the process share all the process’s resources such as memory and file descriptors. Inter-thread communication is much faster than inter-process communication.
Sharing process resources such as memory and file descriptors with multiple threads can be problematic. The challenge is to avoid any potential data access conflicts caused by multiple threads reading or writing the data at the same time. This conflict can be avoided using synchronization techniques such as semaphores and mutexes.
Overall there are many benefits programming with threads instead of processes including:
Each thread has access to the entire address space, including access to all the global variables and file descriptors.
Communication and synchronization is much faster and easier to implement between threads than between different processes using interprocess constructs such as message queues and shared memory.
Parallel programming often improves the performance of a program.
Parallel programming techniques are well suited to some common software models.
Threads APIs can be easier to use. For instance there is just one thread creation (pthread_create) API whereas there are multiple variations on the exec() system call to create a process
Most UNIX implementations provide the POSIX thread functionality and most developers now implement threaded applications using the standard Pthread functions.
The term Pthread is shorthand for POSIX Threads, a programming model and a collection of interfaces that describes the creation, control, inter-thread communication, scheduling and synchronization of threads that was first described in the IEEE POSIX standard document known as IEEE Std. 1003.1c-1995. This standard was incorporated into the IEEE Std 1003.1, 1996 Edition1 .
Many vendors that implemented Pthreads found the POSIX.1c interfaces to be incomplete so they implemented extensions to solve their thread requirements. These extensions resulted in proprietary and non-portable interfaces. Eventually a group was formed to standardize these extensions and incorporate them with the original POSIX.1c work. The result was published in the X/Open CAE Specification System Interfaces and Headers, Issue 5. This specification was carried forward to be the basis for the current POSIX and UNIX standards (such as IEEE 1003.1-2001).
This document discusses threads generally and Pthreads specifically. The discussion is based on the implementation of Pthreads defined by the POSIX IEEE 1003.1-1996 and POSIX IEEE 1003.1-2001 standards because these are the standards to which the Interix implementation in Services for UNIX 3.5 is based.
Basic Pthread concepts include thread creation, termination, synchronization, and scheduling.
When a process is created, one thread is automatically created. This is called the initial thread and it executes the main() routine in a C or C++ program. Additional threads are created by an explicit call to the pthread_create() routine. It is possible to wait for another thread to complete using the pthread_join() routine and threads can be terminated via the pthread_kill() or pthread_cancel() routines.
The pthread_create() routine takes four arguments.
The first is a pointer to a variable that will contain the new thread’s identification if pthread_create() is successful. This allows the caller a way to identify or synchronize with the new thread at a later time.
The second argument is a pointer to a thread attribute object which specifies the initial attributes of the new thread; attributes include stack size, stack address, scheduling policy, priority and scope, and detachstate. If this attribute pointer is NULL then the thread is created with a default set of attributes.
The third argument is the thread start function which is the first routine to be executed when the new thread starts to run.
The last argument is a pointer which is passed directly to the thread start routine when the thread first runs.
The following simple example shows how a thread is created and then the caller waits for it to finish.
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <Pthread.h> void * thread_function(void *arg) { printf("thread_function started. Arg was %s\n", (char *)arg); // pause for 3 seconds sleep(3); // exit and return a message to another thread // that may be waiting for us to finish pthread_exit ("thread one all done”); } int main() { int res; pthread_t a_thread; void *thread_result; // create a thread that starts to run ‘thread_function’ pthread_create (&a_thread, NULL, thread_function, (void*)”thread one”); printf("Waiting for thread to finish...\n"); // now wait for new thread to finish // and get any returned message in ‘thread_result’ pthread_join(a_thread, &thread_result); printf("Thread joined, it returned %s\n", (char *)thread_result); exit(0); }
Thread creation differs from process creation because no parent-child relationship exists between threads. A thread does not know the thread that created it nor does the system maintain a thread-specific list of threads that it created. It is up to the program to maintain a list of thread ids returned from pthread_create() if that is necessary.
It is important to note that there is no synchronization between the thread that called pthread_create() and the scheduling of the new thread. The new thread may start (and finish) before the pthread_create() call returns to the caller.
Threads can terminate themselves by either returning from their thread start function or by explicitly calling the pthread_exit() routine. Normally when a thread terminates, all other threads in the process continue to run. However, there is a special case: When the initial thread returns from the main() routine, the entire process terminates. To avoid this behavior, the initial thread can either avoid returning from the main() routine or it can call pthread_exit().
A thread can also be cancelled by any other thread (including itself) via the pthread_cancel() routine, as long as the identity of the thread to be cancelled is known to the caller. The thread that is being cancelled must cooperate—it must allow itself to be cancelled. Each thread controls its “cancelability” state and type.
The cancelability state can be either enabled or disabled. A thread that has a state of disabled is considered an uncooperative thread because it ignores any cancellation requests. A thread that is in the enabled state—a cooperative thread—can have a type of asynchronous or deferred. A type of asynchronous means that cancellation requests are accepted at any time. A type of deferred means that a cancellation request is accepted only at designated cancellation points. Only specific routines defined by the POSIX.1 standard are considered designated cancellation points and only when these routines are invoked can the cancellation be accepted.
When a cancellation requested is acted upon, cancellation cleanup handlers are invoked in last-in-first-out (LIFO) order. Each thread maintains an ordered list (a stack) of these cancellation handlers and these handlers can be added to or removed from this list using the pthread_cleanup_push() and pthread_cleanup_pop() routines. These cancellation handlers are also invoked when a thread terminates itself via the pthread_exit() routine. These cancellation routines are normally used to release thread-specific or system resources used by the thread. For instance, the thread owner of a mutex is the only one that can unlock the mutex so it must be unlocked before the thread terminates.
Programs that use threads usually need to share data between the threads and need to perform various actions in a particular and coherent order across multiple threads. These operations require a mechanism to synchronize the activity of the threads. This synchronization is used to avoid race conditions and to wait for or signal when resources are available.
Race conditions are avoided by identifying critical sections and enforcing mutual exclusion in these sections. Critical sections are areas where multiple threads could be executing simultaneously and these threads are making changes to common data. Mutual exclusion is achieved when only one thread is allowed to be active in a critical section at a time. To achieve this, the critical section is surrounded by entry and exit guards. The entry guard acts as a barrier and blocks all but one thread at a time from entering and the exit guard acts to release the entry barrier.
The Pthread model uses objects called mutexes and condition variables as the inter-thread synchronization mechanism. The term “mutex” derives from “mutual exclusion.” A mutex is a locking mechanism to ensure the mutually exclusive use of shared data and critical sections of code. Condition variables are used as a waiting mechanism and as a means for communicating information about the state of some data between different threads.
Thread synchronization is voluntary; the thread participants must cooperate for the system to work. Mutexes and condition variables can protect the shared data and can ensure that the integrity of the shared data remains intact but only if the threads use these mechanisms faithfully and appropriately.
A Pthread mutex is an object that can be created, initialized, locked, unlocked or destroyed. A mutex is represented by a variable of type pthread_mutex_t. It is just like any other data variable except that it must be initialized specially, either with the PTHREAD_MUTEX_INITIALIZER macro or the pthread_mutex_init() function.
A thread locks a mutex by calling either of the routines pthread_mutex_lock() or pthread_mutex_trylock(). Once locked, the calling thread is considered the owner of the mutex. It can then safely access or modify the data. When finished, it unlocks the mutex by calling the pthread_mutex_unlock() routine.
When a thread calls pthread_mutex_lock() and the mutex is already locked (i.e. owned by another thread) then the calling thread blocks until the mutex becomes available (i.e. unlocked by the other thread). If the pthread_mutex_trylock() is used, and the mutex is currently locked, then the routine does not block the thread but rather returns an indication that the mutex is busy. When the thread is finished with this data, it unlocks the mutex using pthread_mutex_unlock(). This routine will not only unlock the mutex but it will also unblock one of the threads that may be blocked on this mutex waiting to enter this critical section.
Mutexes can be destroyed and their resources freed using pthread_mutex_destroy(). This routine should be called only when the mutex is no longer needed and when the mutex is unlocked.
The read-write lock is similar to a mutex. It allows either one thread to gain exclusive use of the critical section so that is can safely make changes to some data—a writer thread—or it allows any number of threads to have simultaneous access to read the data because these threads will not make any modifications.
This type of object is used in those instances where data is frequently referenced but seldom modified. Usually most reader threads will be able to enter this critical section without any delay and the writer thread will be forced to wait until all the current readers leave the critical section.
A condition variable is used to communicate information about the state of shared data. A condition variable object is defined with a type called pthread_cond_t and condition variables are always associated with a mutex. The purpose of condition variables is to provide a mechanism to signal and wait for specific events related to data in a critical section being protected by a mutex.
Consider an example: you have two threads, one creating sales requests (a producer) and another thread (a consumer) that is responsible for shipping the material in the sales request. To get sales requests to the shipper thread, the sales thread puts the requests in a queue, from which the shipper thread can remove and process the request. Access to the queue must be made in a critical section in order to ensure no order is accidentally missed. Imagine that the consumer thread enters the critical section and finds the queue empty, so there is no work for it to perform. In this case the consumer wants to wait for the producer to arrive with a new request. In order for the producer to enter the critical section, the consumer must unlock the mutex. In order to be notified (by the producer) that there is something new available, the consumer must also put itself on a waiting list. However, between the time the consumer unlocks the mutex and blocks itself on the waiting list, the producer may enter the critical section and leave. In this case the consumer will have missed being notified and won’t be unblocked.
This is where condition variables play an important role. Waiting on a condition variable ensures that the blocking of the thread and the unlocking of the mutex happens in one atomic operation. When the condition variable is signaled, the blocking of the signaler and the unblock of the waiter also happens atomically and the mutex remains locked where the lock ownership is passed to the waiter. In this way, condition variables are used for signaling, which in turn allows threads to exchange information about the changes of the state of the protected data (in this case the queue).
A Pthread condition variable is represented by an object of type pthread_cond_t. This variable can be statically initialized with the PTHREAD_COND_INTIALIZER macro or dynamically initialized with the pthread_cond_init() routine.
Each condition variable must be associated with a specific mutex. The condition variable wait and signal operations (pthread_cond_wait() and pthread_cond_signal() routines) require specifying both a condition variable and a mutex. When a thread waits on a condition variable, the associated mutex must always be locked. The pthread_cond_wait() routine will automatically block the thread and unlock the mutex in the same operation. And when the pthread_cond_signal() is called, it will ensure the mutex is re-locked before the blocked thread is reactivated and returns from its pthread_cond_wait() call. A call to pthread_cond_signal() will only awaken a single blocked thread. All the other blocked threads on this condition variable remain blocked.
To signal or wake up all threads waiting on this condition variable, there is the pthread_cond_broadcast() routine. All of the waiting threads will try to obtain ownership of the mutex but only one will succeed—one of the threads with the highest scheduling priority. The rest of the threads will queue up.
POSIX defines a priority scheduling model which determines how important any two threads are relative to each other. Whenever more than one thread is runnable—is ready to execute—the system will choose the thread with the highest priority.
The POSIX thread scheduling semantics are defined in terms of a conceptual model where there is a range of valid priorities and there is a set of thread lists with one list assigned to each priority. Any runnable thread will be placed on one of these thread lists based on the thread’s scheduling priority. The ordering within the thread list depends on the scheduling policy. Thus each thread is controlled by its scheduling priority and its scheduling policy.
The purpose of a scheduling policy is to define the operations on these thread lists such as moving threads within and between the lists. Regardless of policy, POSIX specifies that the thread that is at the head of the highest priority thread list is the thread that should be the current running thread.
The ability to schedule thread priorities is an option in the POSIX standard, designated by the symbol POSIX_THREAD_PRIORITY_SCHEDULING. A POSIX implementation supporting this option must also provide mechanisms for assigning realtime scheduling policies and priorities to threads. The mandatory policies are SCHED_FIFO, SCHED_RR and SCHED_OTHER.
The SCHED_FIFO (first in-first out) policy orders threads on a list by the time the threads have been on the list without being executed. Usually the thread at the head of a list is the thread that has been on the list the longest, and the thread at the tail has been on the list the shortest. This policy allows a thread to run until another thread with a higher priority becomes ready to run or until the current thread blocks voluntarily. If the thread is preempted, it remains at the head of its thread priority list; if the thread blocked, when it becomes a runnable thread again it is added to the tail of the thread’s priority list.
The SCHED_RR (round robin) policy is identical to the SCHED_FIFO policy except that the running thread can run for only a limited amount of time before it is pre-empted. When the fixed time limit has been exceeded the running thread is put at the tail of the thread’s priority list and the thread now at the head of the list becomes the running thread. The effect of this policy is to ensure that multiple SCHED_RR threads having the same priority will share the processor.
The SCHED_OTHER policy is implementation specific and conforming POSIX implementations must document the behavior of this policy. An implementation is free to define this policy to be the same as SCHED_FIFO or SCHED_RR or it may be something completely different. POSIX defined this type of policy to provide conforming programs with a method to indicate that they do not need a realtime scheduling policy in a portable manner.
For each scheduling policy there is a range of valid priorities; for SCHED_FIFO and SCHED_RR this range must be at least 32 and for SCHED_OTHER the range is implementation specific. The range of priorities can be determined from the sched_get_priority_min() and sched_get_priority_max() functions.
Besides thread scheduling policies and thread priorities, there are two other scheduling controls: thread scheduling contention scope and thread scheduling allocation domain.
The contention scope defines the set of threads that compete for the use of processing resources. POSIX defines two contention scopes: all the threads in the system (or PTHREAD_SCOPE_SYSTEM), and all the threads in a process (or PTHREAD_SCOPE_PROCESS).
A thread with system contention scope contends for resources with all other threads in the system including those threads in other processes. A high priority thread in one process can prevent system contention scope threads in other processes from running.
A thread with process contention scope is scheduled locally within the process which means threads within a process are scheduled amongst themselves. Normally process contention scope means that the operating system makes the choice of which process to run and then the process itself contains an internal scheduler that tries to implement the POSIX scheduling rules for the threads within the process.
The other control, thread scheduling allocation domain, deals with the set of hardware processors within the system for which threads can compete. Each allocation domain may contain one or more processors and the mapping of processors to allocation domain is implementation defined. There are no POSIX interfaces to specify the association of a thread to an allocation domain (e.g. association to a specific processor). It is up to the implementation to define how runnable threads are assigned to a particular processor, so long as threads are still scheduled according to their priorities. Even though POSIX doesn’t define any interfaces to set a thread’s allocation domain, many systems that support multiprocessors may have their own system specific interfaces.
A thread safe function is a function that can be safely invoked by multiple threads concurrently. This type of function is known as a reentrant function and it must provide internal synchronization to ensure that any shared or global data is properly protected. ANSI-C and POSIX.1-1990 were not designed originally to work in a multithreaded environment and reentrancy was not an issue in their design. When Pthreads was added to the POSIX.1 specification, it mandated that all functions must be thread safe and must support reentrancy unless explicitly stated otherwise. Fortunately many of the original functions could be made thread safe without changing their interfaces but there were several types of functions that required modifications:
Functions that maintain or return pointers to internal static buffers. Examples are asctime(), strtok() and ttyname().
Functions that access data shared by more than one thread. For instance, functions such as malloc() and the stdio routines share buffers across related calls.
In the latter case, the shared data could be protected by a mutex so internal function changes were easily made to make them thread safe. In the former case, a mutex would not work. This type of function was inherently non thread-safe and so POSIX.1 introduced alternative versions. These new versions have the same name as the original but they have the characters “_r” appended to their name (‘r’ for re-entrant safe). For example, some of the new routines are rand_r(), readdir_r(), and asctime_r(). These functions all have additional arguments so that the internal state can be returned to the caller to be reused in a future invocation.
Other changes were made to ensure thread safety. For example the global variable “errno” was changed. It is now a thread local data element so that each thread can check the return status from a function without worrying about interference from other threads. Some function’s thread safety is dependent on the argument values passed to these functions. For instance the tmpnam() function cannot be called with a NULL argument if it is to be used in a thread-safe manner.
For the stdio routines, many of the functions are actually macros and not functions. The functions like getc() and putc() were macros for performance reasons. For thread safety, these functions need to be synchronized but is too expensive to do this for every character. POSIX.1-1996 deemed that it was very important for these functions to be thread-safe so these functions had to change to become thread-safe. However, it also decided to retain the original, faster functions with their unsafe behavior but with new names. These functions are named to clearly indicate their unsafe nature, such as putc_unlocked() and getc_unlocked(). To use these unsafe functions in a safe manner, they can be invoked within an explicitly locked program region using special new stdio locking functions known as flockfile() and funlockfile().
For example:
flockfile(stdout); putc_unlocked(“X”, stdout); putc_unlocked(“\n”, stdout); fprintf(stdout, “another line\n”); funlockfile(stdout);
The flockfile() and funlockfile() functions provide an orthogonal mutual exclusion lock for each stdio FILE stream. These locks behave as if they were the same as those used internally by all the stdio functions for thread-safe synchronization. This design provides a consistent locking mechanism across all stdio related functions.
UNIX has a mechanism known as signals which is used to notify processes of special events, such as timer expirations, special keyboard generated interrupt events, mathematical exceptions, invalid memory references or special process generated signal events. With the introduction of Pthreads, POSIX.1 now allows a signal to be associated with either a process or a specific thread within the process. If the signal is generated by an action that is attributable to a particular thread, such as a memory access violation or by an explicit call to the pthread_kill() function, then the signal will be delivered to that particular thread. All other signals will be generated for the process but will be delivered to only one thread in the process.
With Pthreads, instead of the process having a single signal mask, threads have their own signal masks which are managed with the pthread_sigmask() function. The signal mask specifies which signals are to be blocked from delivery to the thread. When a signal is generated for a specific thread then it will only be delivered to that thread—the signal will remain pending on that thread until the thread becomes eligible to receive it. However, when a signal is generated to the process then it is delivered to any eligible thread in the process. If there are no eligible threads available (e.g all threads have blocked this signal in their signal masks), then the signal remains pending on the process until an eligible thread becomes available.
With the release of SFU 3.5, the Interix subsystem has extended its POSIX support to include much of the Pthread model and Pthread interfaces necessary for conformance to the IEEE Std1003.1™-2001 standard. Interix now supports many of the Pthread, semaphore, mutex, and scheduling specific APIs from this standard (see Appendix A for a detailed list). The Interix environment also comes with updated libraries which support the thread-safe and new re-entrant functions required by this standard. The functionality introduced in this version of Interix includes:
Thread attribute objects
Thread creation and destruction
Thread cancellation
Thread-specific signals
Thread-specific data
Mutex objects
Condition variable objects
Read/write lock objects
Stack guard-page size attribute
The POSIX standard defines a set of mandatory functions and headers and a set of options. All of the Pthread functionality is optional and is split up amongst several options, based on specific functionality. Each of these options is denoted by a symbolic constant which may be defined to have a value in the header file <unistd.h>. If the value is greater than 0, then the option is supported by the implementation. The Interix implementation supports many of the Pthread options and the following symbolic constants are defined in <unistd.h> :
_POSIX_SPIN_LOCKS
_POSIX_READER_WRITER_LOCKS
_POSIX_SEMAPHORES
_POSIX_THREAD_ATTR_STACKADDR
_POSIX_THREAD_ATTR_STACKSIZE
_POSIX_THREAD_PRIORITY_SCHEDULING
_POSIX_THREAD_SAFE_FUNCTIONS
_POSIX_THREADS
Interix also supports many other Pthread functions associated with other POSIX options but the corresponding symbolic constants are not defined in <unistd.h> because Interix doesn’t implement the complete set of functions associated with that option.
Although POSIX_THREAD_PRIORITY_SCHEDULING is defined, Interix only supports the SCHED_OTHER scheduling policy and does not support the SCHED_RR or SCHED_FIFO policies. And the only scheduling contention scope that Interix provides is the PTHREAD_SCOPE_SYSTEM. This means that all threads on the system are scheduled with respect to each other; regardless of the process that owns the thread.
The Interix environment is controlled and maintained by the Interix subsystem. This subsystem is roughly analogous to the UNIX kernel on other UNIX platforms. It maintains the UNIX process creation and process hierarchy, the signaling mechanism, some memory management, and (starting with Services for UNIX 3.5) the Pthread model. Essentially it implements the UNIX system calls which provide the fundamental operations of the UNIX environment. Unlike other UNIX kernels, this subsystem doesn’t run on the bare hardware. It has the advantage that it can rely on and make use of resources provided by the Windows kernel. The Interix subsystem relies on the Windows kernel to manage low level functionality, such as virtual memory, process and thread creation, and some other higher level primitives such as memory allocation, message passing, local procedure calls, timers, and process and thread scheduling controls
Although the Windows kernel provides much of the thread functionality and semantics required by Interix Pthreads, the function calls and syntax between the two threading models are very different. The Interix libraries are responsible for translating between the two.
The following are some similarities between Pthreads and Window’s threads:
Every thread must have an entry point. The name of the entry point is entirely up to you so long as the signature is unique and the linker can adequately resolve any ambiguity.
Each thread is passed a single parameter when it is created. The contents of this parameter are entirely up to the developer and have no meaning to the operating system.
A thread function must return a value.
A thread function needs to use local parameters and variables as much as possible. When you use global variables or shared resources, threads must use some form of synchronization to avoid potentially corrupting data.
The Interix Pthread implementation is built upon the thread functionality already provided by the Windows kernel. The following table shows the Windows resources that are used in Interix.
Pthread object |
Windows kernel object |
---|---|
thread |
kernel thread object |
mutex |
kernel mutex object |
condition variable |
kernel event object |
semaphore |
kernel semaphore object |
read-write lock (rwlock) |
kernel rtl_resource object |
|
|
Table 1
Interix threads are implemented using the same mechanisms and functionality as Windows threads. This means that Interix threads will share the same characteristics as Windows threads; such as
Scheduling priorities : each thread can be assigned a scheduling priority between 1 and 31. The range 1 to 15 is known as the dynamic range and the values 16 through 31 are known as the real-time range.
Scheduling policies and algorithms : the thread with the highest priority value is executed first
System level scoping : all threads in the system are scheduled relative to each other.
The Windows scheduling algorithm is a priority driven, preemptive implementation where the highest priority runnable thread always runs first. Each selected thread is allowed to run for an amount of time called a quantum. A quantum is the maximum time a thread can run before Windows interrupts the thread to find out if there is another runnable thread at the same or higher priority or whether the current thread’s priority should be reduced. Since the scheduling algorithm is preemptive, a thread may be interrupted before it completes its quantum if another thread with a higher priority becomes ready to run.
Windows makes scheduling decisions based strictly on threads and the thread’s current-priority and no consideration is given to the process the thread belongs to. Processes just provide resources and the context in which their threads run. One of the resources provided by the process is a base-priority value which is used to calculate the thread’s current-priority. The thread’s current-priority is never any smaller than the sum of process base-priority value and the thread’s base-priority. The Windows scheduler uses the thread current-priority value to determine when a thread is to be scheduled for execution. The thread current-priority can also be increased temporarily by an optional temporary priority boost value. This boost value decreases with time as the thread uses up its quantum.
The process’s base priority is normally set via a call to the Win32 function SetPriorityClass(), but the Win32 subsystem does not deal directly with Windows scheduling priority values. Instead it defines a set of process priority classes which maps to a known Windows scheduling priority value. The mapping between classes and numeric priority values is listed in Table 2.
Process Priority Class (Win32) |
Windows scheduling priority |
---|---|
REAL_TIME |
24 |
HIGH |
13 |
ABOVE_NORMAL |
10 |
NORMAL |
8 |
BELOW_NORMAL |
6 |
IDLE |
4 |
Table 2
If the process priority class changes, so does its base-priority value. This change automatically changes the current-priority for each thread associated with this process because this value is always calculated from the sum of the thread base-priority and the process base-priority.
The thread base-priority value can be changed via the SetThreadPriority() API. The thread base-priority can have a value range of -2 to +2. This allows the thread’s current-priority value to be adjusted higher or lower relative to the process’ base-priority.
In Interix, only the SCHED_OTHER scheduling policy is supported. This policy is defined as the default Windows scheduling algorithm. By relying on the Windows thread and process implementation, the Interix Pthread implementation has the following characteristics:
Scheduling contention scope is system-wide (PTHREAD_SCOPE_SYSTEM) because scheduling is based per thread and not per process
The existing functions nice(), getpriority() and setpriority() continue to handle process nice values. These nice values have a range of 0 to 29 and map directly to the Windows scheduling priorities of 30 to 1 respectively. Thus, more positive nice values result in less favorable scheduling priority. The Windows scheduling priority represented by the UNIX process nice value is used as the value in the Windows process base-priority.
To access the Windows thread base-priority value, Interix uses the sched_priority member in the newly defined sched_param structure found in the file <sched.h>. The current Interix implementation maps the Pthread definition of priority to the Windows thread base-priority. Therefore this member is only allowed values in the range -2 to +2 when used with the Pthread specific functions such as:
pthread_getschedparam()
pthread_setschedparam()
pthread_attr_setschedparam()
pthread_attr_getschedparam()There are other Pthread specific functions that deal with thread priority values. Functions such as pthread_setschedprio(), sched_get_priority_max() and sched_get_priority_min() accept a priority argument and this argument is limited to the range of values between -2 and +2.
Note that the Interix implementation in SFU 3.5 introduces an inconsistency because Pthread scheduling priority values are supposed to be used consistently for both threads and processes. Clearly the Windows scheduling algorithm uses more than five priority values. Programmers migrating Pthread applications will have to be wary of this, in case the application makes assumptions between process and thread priorities that may not be true in Interix.
The combination of the Interix thread’s priority value with the Interix process nice value is used as the Windows thread current-priority which determines the actual scheduling priority.
changing the processes nice value will also change the current scheduling priority in all the threads associated with that process.
One characteristic that is uniquely Interix is that each Interix process has two initial threads; one to execute the main() function and another special purpose thread called the signaler thread. This signaler thread is an implementation necessity for the subsystem to ensure that a signal can be delivered to a specific thread within the process. The subsystem makes the decision which thread should receive the signal but the subsystem is not capable of directly interrupting or changing the context of this targeted thread. Instead, a special notification can be sent to the process’ signaler thread which has the ability to suspend the target thread and change its context so the target thread will make an immediate call to the subsystem to determine which signal it should handle. The target thread’s original context is saved so that the thread can resume from where it was suspended.
Much of the Interix thread implementation is handled in the Interix application address space itself and not by the Interix subsystem. Efficiency and performance are improved by avoiding making system calls to the Interix subsystem and instead making direct Windows kernel calls. This is possible because an Interix process can call many Windows kernel support routines directly.
There are many available open source software packages that have been ported to Interix and several of these are implemented using the Pthread APIs. Packages such as apache, sendmail with MILTER support, MySQL and perl have all been migrated to the Interix environment with very few modifications: no more than you would expect migrating the application to any other UNIX platform. With respect to migrating the Pthread related code, there were no special modifications necessary—it just all worked.
Compiling Pthread programs on Interix is no different from any other UNIX platform. You must use the –D_REENTRANT option to the compiler to ensure all the Pthread definitions are visible at compile time. You can use -lpthread is also supported but but do not need to because all the Pthread support has been incorporated into the default libraries (libc.a and libpsxdll.a).
The following are some other Pthread issues in Services for UNIX 3.5 :
Be careful when using object files and libraries that were built on SFU3.0 since these functions may not be thread safe
The Interix version of perl distributed with SFU3.5 was inadvertently built with an inappropriate implementation of Pthread support. Download a newer version of perl with the appropriate Pthread support from ftp://ftp.interopsystems.com/pkgs/3.5/perl-5.8.3.1-bin.tgz
The Services for UNIX 3.5 release introduces Pthread and internationalization support in the Interix environment, both in the Interix subsystem and the Interix utilities and SDK. The introduction of Pthread support is very exciting because it opens the way for a new class of utilities to be migrated from existing UNIX systems to Windows platforms using the Interix environment.
The Interix SDK provides many of the important and fundamental Pthread APIs and the Interix subsystem completes the Pthread functionality by implementing the necessary infrastructure and semantics. Much of the thread characteristics and behavior of Interix threads is similar to Windows threads because the Interix implementation of Pthreads relies heavily on the Windows thread functionality.
Porting Pthread applications to Windows using Interix is a simple process. Many open source applications have been ported with no changes to their Pthread code.
For the latest information about Windows Services for UNIX 3.5, see the Windows Services for UNIX Web site at https://www.microsoft.com/technet/interopmigration/unix/sfu/default.mspx.
_pthread_atfork
_pthread_attr_destroy
_pthread_attr_getdetachstate
_pthread_attr_getguardsize
_pthread_attr_getinheritsched
_pthread_attr_getschedparam
_pthread_attr_getschedpolicy
_pthread_attr_getscope
_pthread_attr_getstack
_pthread_attr_getstackaddr
_pthread_attr_getstacksize
_pthread_attr_init
_pthread_attr_setdetachstate
_pthread_attr_setguardsize
_pthread_attr_setinheritsched
_pthread_attr_setschedparam
_pthread_attr_setschedpolicy
_pthread_attr_setscope
_pthread_attr_setstack
_pthread_attr_setstackaddr
_pthread_attr_setstacksize
_pthread_cancel
_pthread_cleanup_pop
_pthread_cleanup_push
_pthread_cond_broadcast
_pthread_cond_destroy
_pthread_cond_init
_pthread_cond_signal
_pthread_cond_timedwait
_pthread_cond_wait
_pthread_condattr_destroy
_pthread_condattr_getpshared
_pthread_condattr_init
_pthread_condattr_setpshared
_pthread_create
_pthread_detach
_pthread_equal
_pthread_exit
_pthread_getconcurrency
_pthread_getschedparam
_pthread_getspecific
_pthread_join
_pthread_key_create
_pthread_key_delete
_pthread_kill
_pthread_mutex_destroy
_pthread_mutex_init
_pthread_mutex_lock
_pthread_mutex_timedlock
_pthread_mutex_trylock
_pthread_mutex_unlock
_pthread_mutexattr_destroy
_pthread_mutexattr_getpshared
_pthread_mutexattr_gettype
_pthread_mutexattr_init
_pthread_mutexattr_setpshared
_pthread_mutexattr_settype
_pthread_once
_pthread_rwlock_destroy
_pthread_rwlock_init
_pthread_rwlock_rdlock
_pthread_rwlock_timedrdlock
_pthread_rwlock_timedwrlock
_pthread_rwlock_tryrdlock
_pthread_rwlock_trywrlock
_pthread_rwlock_unlock
_pthread_rwlock_wrlock
_pthread_rwlockattr_destroy
_pthread_rwlockattr_getpshared
_pthread_rwlockattr_init
_pthread_rwlockattr_setpshared
_pthread_self
_pthread_setcancelstate
_pthread_setcanceltype
_pthread_setconcurrency
_pthread_setschedparam
_pthread_setschedprio
_pthread_setspecific
_pthread_sigmask
_pthread_spin_destroy
_pthread_spin_init
_pthread_spin_lock
_pthread_spin_trylock
_pthread_spin_unlock
_pthread_testcancel
_sched_get_priority_max
_sched_get_priority_min
_sched_yield
_sem_close
_sem_destroy
_sem_getvalue
_sem_init
_sem_open
_sem_post
_sem_timedwait
_sem_trywait
_sem_unlink
_sem_wait.
1 | This standard was also adopted as an International Standard which is known as International Standard ISO/IEC 9945-1:1996(E). |