Re: Re:SMP Compatibility
Posted: Thu Jun 21, 2012 9:37 am
One very important thing is missing here for Intel where the processor can execute instructions out of order.
To be absolutely sure that the proposed spin lock implementation works, a sfence instruction must be introduced before unlocking or in the unlock.
sfence ensures that all pending memory are processed before the execution continues.
For example:
the a=1 can be issued by the processor (I am not talking about the compiler) AFTER the unlock, which may ruin the very purpose of the lock.
The correct code is:
Now of course, there are situations where the sfence can be changed to lfence or even removed. But that's optimizations.
To be absolutely sure that the proposed spin lock implementation works, a sfence instruction must be introduced before unlocking or in the unlock.
sfence ensures that all pending memory are processed before the execution continues.
For example:
Code: Select all
lockSpinlock(&sp);
a=1;
unlockSpinlock(&sp);
The correct code is:
Code: Select all
lockSpinlock(&sp);
a=1;
__asm__ __volatile__("sfence" : : : "m" );
unlockSpinlock(&sp);
Dreamsmith wrote:Actually, in a uniprocessor system you don't need spinlocks at all, and you shouldn't be using a spinlock to guard sections from other threads on the same processor. As long as the process in question is active, the spinlock is guarenteed to not be released. Instead of spinning, you should be suspending the task and turning over control to the task that holds the lock. You only want to spin in circumstances where a lock could be released while you're spinning, which generally can only happen in an SMP environment.
OTOH, implementing standard semaphores or mutexes will require spinlocks if they're to work properly in an SMP environment, e.g. app calls acquireSemaphore, kernel disables interrupts, locks a spinlock, does what it needs to do to the semaphore, then releases the spinlock and reenables interrupts. If the semaphore can't be acquired, the task asking for it is put to sleep until some other task calls releaseSemaphore.
Anyhow, implementing a spinlock requires using a LOCK prefix on certain instructions, or instructions that implicitly LOCK. The XCHG instruction is one, so it's a good choice. LOCKing is necessary to keep two processors from modifying it at once. Here's how it can be done under GCC:
typedef unsigned char SpinLock;
INLINE void lockSpinlock(SpinLock *sp)
{
register SpinLock tmp = 1;
do
{
__asm__ __volatile__("xchgb %0,%1"
: "=r"(tmp), "=m"(*sp)
: "0"(tmp), "1"(*sp));
} while ( tmp );
}
INLINE void unlockSpinlock(SpinLock *sp)
{
register SpinLock tmp = 0;
__asm__ __volatile__("xchgb %0,%1"
: "=r"(tmp), "=m"(*sp)
: "0"(tmp), "1"(*sp));
}
Should there be a spinlock on all kernel functions? Probably not. A spinlock should only be used to guard certain sections of sensitive code. You want very specific locks, locking very specific and very small sections of code. If your kernel is preemptable on a uniprocessor, don't use spinlocks, use mutexes or semaphores with the ability to suspend/resume tasks, and only use spinlocks while implementing the semaphores/mutexes.