Translate

Image of Beginning Google Maps API 3
Image of Linux Kernel Development (3rd Edition)
Image of XSLT 2.0 and XPath 2.0 Programmer's Reference (Programmer to Programmer)
Image of Modern Operating Systems (3rd Edition)

Porting WaitForSingleObject to Linux – Part 1

Recently I was involved in porting a 32-bit application which was initially written for Microsoft Windows NT to GNU/Linux.  This application contained a large number of calls to NtWaitForSingleObject and a smaller number of calls to NtWaitForMultipleObject

Now anybody who has had to port code containing more than a few instances of these particular Win32 APIs, or their close cousins WaitForSingleObjectEx, MsgWaitForMultipleObjects, MsgWaitForMultipleObjectsEx, etc. to Unix or GNU/Linux is probably already shuddering with the recollection of long arduous days and nights of trial and error coding to try and correctly mimic the semantics and functionality of these particular Microsoft Windows specific APIs, but for the reader who has not yet had to attempt to port such an application, this post and my next post may help you save your sanity (and possibly your hair!) sometime in the future.

By the way, both of these APIs are marked deprecated in MSDN by Microsoft but still work as expected in Windows NT and Windows XP.  I am not sure about Windows Vista or Windows 7 as I have not tested them on these operating systems.  The two deprecated APIs have been replaced by the equivalant APIs WaitForSingleObject and WaitForMultipleObject respectively.  For the remainder of this post I shall just discuss the replacement APIs but most of what I say will be valid for either the deprecated or the replacement API.

On first examination WaitForSingleObject seems fairly benign.  The description in MSDN states that “This function returns when the specified object is in the signaled state or when the time-out interval elapses”.  Sounds like a fairly simple and innocuous API, right?  Maybe something similar to the POSIX.1 API pthread_cond_timedwait.  Well, you are dead wrong and this post and the following will explain why.

WaitForSingleObject and its cousins can wait for a signal from any or all of the following “objects”: change notification, console input, event, job, memory resource notification, mutex, process, semaphore, thread and waitable timer and in limited circumstances on files and file I/O.  When appropriately signaled, a thread is unblocked and continues.  No published standardized API in the GNU/Linux or Unix world comes come to handling this range of objects in a single API.

This is probably the one single area where a Win32 API is better designed than the GNU/Linux or Unix API set.  In GNU/Linux and Unix there are specific APIs to wait for different kinds of events: select waits for I/O events such as sockets, semop waits for semaphores, wait waits for child processes, etc.

Unfortunately the semantics of WaitForSingleObject and its cousins varies depending on the different object types.  For example, it destroys a thread, but only unlocks a mutex.  For this reason understanding Win32 source code from the viewpoint of porting to GNU/Linux can be much more difficult.

Our first example, i.e. locking a mutex (or mutants as they are sometimes called by Windows programmers), is probably the simplest use of WaitForSingleObject and is relatively easy to port to GNU/Linux.  Consider the following Windows code which creates 2 threads, and a mutex to control access to an integer variable x

INTEGER x;
HANDLE  hMutex;
...

void function1() 
{
   ...
   WaitForSingleObject(hMutex,INFINATE);
   x= 5;
   ReleaseMutex(hMutex);
   ...
}

void funtion2() 
{
   ...
   WaitForSingleObject(hMutex,INFINATE);
   x = 12;
   ReleaseMutex(hMutex);
   ...
}

int WINAPI WinMain(...) 
{
   hMutex = CreateMutex(NULL,FALSE,NULL);
   ....
   HANDLE hThread1 = CreateThread(..., (LPTHREAD_START_ROUTINE)function1, ...);
   ....
   HANDLE hThread2 = CreateThread(..., (LPTHREAD_START_ROUTINE)function2, ...);
   ....
}

Here is the same code ported to GNU/Linux.

int x;
pthread_mutex_t  hMutex;
...

void *function1( void *Context) 
{
   ...
   pthread_mutex_lock(&hMutex);
   x= 5;
   pthread_mutex_unlock(&hMutex);
   ...
}

void *funtion2( void *Context) 
{
   ...
   pthread_mutex_lock(&hMutex);
   x = 12;
   pthread_mutex_unlock(&hMutex);
   ...
}

int WINAPI WinMain(...) 
{
   pthread_mutexattr_t mAttr;
   pthread_t hThread1;
   pthread_t hThread2;

   pthread:mutexattr_init(&mAttr);
   pthread_mutexattr_settype(&mAttr, PTHREAD_MUTEX_RECURSIVE);
   pthread_mutex_init(&hMutex, &mAttr);
   ....
   pthread_create(&hThread1, NULL, function1, 0);
   ....
   pthread_create(&hThread2, NULL, function2, 0);
   ....

}

I am going to assume that you are familar with POSIX threads and thus the above code snippet should be fairly self-explanatory.  When dealing with a mutex, WaitForSingleObject with no timeout is functionally equivalant to pthread_mutex_lock.  The only thing of note is that you should probably use a recursive mutex since a Window thread can relock a mutex without first unlocking it.

If the second parameter in WaitForSingleObject is 0 and not INFINITE, you should probably use pthread_mutex_trylock instead of pthread_mutex_lock since WaitForSingleObject does not enter a wait state if the mutex is not, to use Microsoft’s term, signaled; it always returns immediately.

If the second parameter in WaitForSingleObject is neither 0 or INFINITE, you should probably use pthread_mutex_timedlock instead of pthread_mutex_lock. Under Windows the time-out interval is in milliseconds.  Under GNU/Linux you must pass a populated timespec structure to pthread_mutex_timedlock.  Another wrinkle is that the interval clock and thus the clock resolution may or may not be based on the CLOCK_REALTIME clock depending on whether the Timers option is supported or not.  Therefore care needs to be taken to ensure that the time interval is correctly converted.

The second wrinkle is that pthread_mutex_timedlock and pthread_mutex_trylock do not work with recursive mutexes which are locked by the same thread as shown by the following example.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>

int
main(int  argc,
     char *argv)
{
    pthread_mutex_t hMutex1;
    pthread_mutex_t hMutex2 = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutexattr_t mAttr;
    struct timespec abstime;
    int rc;

    pthread_mutexattr_init(&mAttr);
    pthread_mutexattr_settype(&mAttr, PTHREAD_MUTEX_RECURSIVE_NP);
    pthread_mutex_init(&hMutex1, &mAttr);

    /* lock the mutexes */
    pthread_mutex_lock(&hMutex1);
    pthread_mutex_lock(&hMutex2);

    /* set absolute time to now + 5 seconds */
    clock_gettime(CLOCK_REALTIME, &abstime);
    abstime.tv_sec += 5;

    /* try to relock the first mutex */
    if ((rc = pthread_mutex_timedlock(&hMutex1, &abstime)) > 0 ) {
        fprintf(stderr, "ERROR first pthread_mutex_timedlock. %s\n", strerror(rc));
    }

    /* set absolute time to now + 5 seconds */
    clock_gettime(CLOCK_REALTIME, &abstime);
    abstime.tv_sec += 5;

    /* try to relock the second mutex */
    if ((rc = pthread_mutex_timedlock(&hMutex2, &abstime)) > 0 ) {
        fprintf(stderr, "ERROR second pthread_mutex_timedlock. %s\n", strerror(rc));
    }

    exit(EXIT_SUCCESS);
}

The only way around this problem is to either use non-recursive mutexes or ensure that threads do not attempt to relock recursive mutexes.  In my experience Windows programmers tend to be rather loose and free in this respect so careful code inspection is usually required.

Well that is all for now about the use of WaitForSingleObject with mutexes. See my next post for ideas on how to port code which uses this API to handle events.
 

2 comments to Porting WaitForSingleObject to Linux – Part 1