tags:

views:

451

answers:

2

I'm trying to make a really simple spinlock mutex in C and for some reason I'm getting cases where two threads are getting the lock at the same time, which shouldn't be possible. It's running on a multiprocessor system which may be why there's a problem. Any ideas why it's not working?

void mutexLock(mutex_t *mutexlock, pid_t owner)
{
int failure = 1;
while(mutexlock->mx_state == 0 || failure || mutexlock->mx_owner != owner)
{
    failure = 1;
    if (mutexlock->mx_state == 0)
    {
        asm(
        "movl    $0x01,%%eax\n\t"      // move 1 to eax
        "xchg    %%eax,%0\n\t"         // try to set the lock bit
        "mov     %%eax,%1\n\t"         // export our result to a test var
        :"=r"(mutexlock->mx_state),"=r"(failure)
        :"r"(mutexlock->mx_state)
        :"%eax"
        );
    }
    if (failure == 0)
    {
        mutexlock->mx_owner = owner; //test to see if we got the lock bit
    }
    } 
}
+5  A: 

Well for a start you're testing an uninitialised variable (failure) the first time the while() condition is executed.

Your actual problem is that you're telling gcc to use a register for mx_state - which clearly won't work for a spinlock. Try:

    asm volatile (
    "movl    $0x01,%%eax\n\t"      // move 1 to eax
    "xchg    %%eax,%0\n\t"         // try to set the lock bit
    "mov     %%eax,%1\n\t"         // export our result to a test var
    :"=m"(mutexlock->mx_state),"=r"(failure)
    :"m"(mutexlock->mx_state)
    :"%eax"
    );

Note that asm volatile is also important here, to ensure that it doesn't get hoisted out of your while loop.

caf
You're right, but that wasn't the problem...
Adam
@Adam: See update.
caf
Heh...thanks, Gcc Extended Inline Assembly is pretty darn new to me. That looks like it solves the problem nicely :).
Adam
+2  A: 

The problem is that you load mx_state into a register (the 'r' constraint) and then do the exchange with the registers, only writing back the result into mx_state at the end of the asm code. What you want is something more like

asm(
    "movl    $0x01,%%eax\n\t"      // move 1 to eax
    "xchg    %%eax,%1\n\t"         // try to set the lock bit
    "mov     %%eax,%0\n\t"         // export our result to a test var
    :"=r"(failure)
    :"m" (mutexlock->mx_state)
    :"%eax"
    );

Even this is somewhat dangerous, as in theory the compiler could load the mx_state, spill it into a local temp stack slot, and do the xchg there. It also is somewhat inefficient, as it has extra movs hardcoded that may not be needed but can't be eliminated by the optimizer. You're better off using a simpler asm that expands to a single instruction, such as

failure = 1;
asm("xchg  %0,0(%1)" : "=r" (failure) : "r" (&mutex->mx_state), "0" (failure));

Note how we force the use of mx_state in place, by using it's address rather than its value.

Chris Dodd