I am trying to understand how xv6 uses the xchg instruction to achieve atomicity when creating locks. Here is the relevant snippet from spinlock.c:
Code: Select all
void acquire ( struct spinlock *lk )
{
...
while( xchg( &lk->locked, 1 ) != 0 )
{
// spin, waiting for lock to become available
}
...
}
Code: Select all
static inline uint xchg ( volatile uint *addr, uint newval )
{
uint result;
// The + in "+m" denotes a read-modify-write operand.
asm volatile(
"lock; xchgl %0, %1" :
"+m" ( *addr ), "=a" ( result ) :
"1" ( newval ) :
"cc"
);
return result;
}
What I don't understand is how the function xchg returns a value. How can you get a return value from the assembly xchg instruction? What does the code inside asm volatile do? What would it look like if it was not inline, but instead written in a separate assembly file? The only line I sort of understand is "lock; xchgl %0, %1". Any insight would be much appreciated!In one atomic operation, xchg swaps a word in memory with the contents of a register. The function acquire repeats this xchg instruction in a loop; each iteration atomically reads lk->locked and sets it to 1. If the lock is already held, lk->locked will already be 1, so the xchg returns 1 and the loop continues. If the xchg returns 0, however, acquire has successfully acquired the lock (locked was 0 and is now 1) so the loop can stop.
Edit: The following quote from this OS textbook, makes me think that the line "+m" ( *addr ), "=a" ( result ) does some voodoo to move the old value (addr) into the return value (result):
The key, of course, is that this sequence of operations is performed atomically. The reason it is called "test and set" is that it enables you to "test" the old value (which is what is returned) while simultaneously "setting" the memory location to a new value; as it turns out, this slightly more powerful instruction is enough to build a simple spin lock...