Does this sound right?
If a spinlock is an atomic test-and-set in a tight loop, as follows:
Code:
mov eax,1
lea edx,[var]
spinlock:
lock xchg [edx],eax ; lock prefix is optional, because xchg is always atomic
and eax,eax
jnz spinlock
then the only practical applications for a spinlock are:
- Waiting for a thread on another processor to release the spinlock (mov [var], 0).
- Waiting for an interrupt to release the spinlock.
The latter seems impractical to me in a multitasking system, because the spinlock is just sitting there wasting time that could be given to other processes. To me, it doesn't even seem correct for a singletasking system, because the interrupt and the code may be waiting for each other to release the spinlock (correct me if this is impossible, I'm too tired to figure it out). Even in a multitasking system where another process has the spinlock and eventually the process is preempted, it still seems wasteful to me.
That being said, I think that a thread on a UP system should yield its timeslice during a spinlock if another thread has aquired it. Something like this...
Code:
spinlock:
mov eax,1
lock xchg [var],eax ; lock prefix is optional, because xchg is always atomic
and eax,eax
jz spinlock_end
push spinlock ; call thread_yield
jmp thread_yield ; jmp spinlock
spinlock_end:
And that should take care of mutual exclusion. That atomic operation is necessary, even in a UP system, because the thread could be preempted between the instruction to test, and the instruction to set. Would it be practical to use extra code to determine exactly how to behave in a spinlock? for example:
Code:
// psuedocode
extern int UP; // Whether this is a Uniprocessor system
typedef struct
{
int value;
int processor;
} Spinlock;
Spinlock s;
void acquire(Spinlock *s)
{
if (UP)
while (!test_and_set(&s->value)) thread_yield();
else
{
int p = get_processor();
while (!test_and_set(&s->value))
{
if (p == s->processor) thread_yield(); // If the thread that has the spinlock is on the same processor, give up this timeslice
}
s->processor = p;
}
}
void release(Spinlock *s)
{
s->value = 0;
}
This seems like the best way to me, what do you think?