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.

*

On This Page

Introduction Introduction
The Interix Pthread Implementation The Interix Pthread Implementation
Porting Pthread Source Code to Interix Porting Pthread Source Code to Interix
Summary Summary
Related Links Related Links
Appendix A – Pthread APIs implemented in Interix Appendix A – Pthread APIs implemented in Interix

Introduction

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.

What is 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.

What are Threads?

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.

What are Pthreads?

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.

Pthread concepts

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.

Pthread Creation

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.

Thread Termination

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.

Thread Synchronization

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.

Mutexes

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.

Read-Write Locks

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.

Condition Variables

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.

Pthread Scheduling

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.

PThread Scheduling Contention Scope and Allocation Domain

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.

Thread Safe Functions

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:

  1. Functions that maintain or return pointers to internal static buffers.  Examples are asctime(), strtok() and ttyname().

  2. 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.

Pthreads and Signals

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.

The Interix Pthread Implementation

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.  

Interix Implementation Uses Windows Kernel Support

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.

Porting Pthread Source Code to Interix

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

Summary

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.

Appendix A – Pthread APIs implemented in Interix

_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).