Spinlocks

Name

cyg_spinlock_create, cyg_spinlock_destroy, cyg_spinlock_spin, cyg_spinlock_clear, cyg_spinlock_test, cyg_spinlock_spin_intsave, cyg_spinlock_clear_intsave -- Low-level Synchronization Primitive

Synopsis

#include <cyg/kernel/kapi.h>
        

void cyg_spinlock_init(cyg_spinlock_t* lock, cyg_bool_t locked);

void cyg_spinlock_destroy(cyg_spinlock_t* lock);

void cyg_spinlock_spin(cyg_spinlock_t* lock);

void cyg_spinlock_clear(cyg_spinlock_t* lock);

cyg_bool_t cyg_spinlock_try(cyg_spinlock_t* lock);

cyg_bool_t cyg_spinlock_test(cyg_spinlock_t* lock);

void cyg_spinlock_spin_intsave(cyg_spinlock_t* lock, cyg_addrword_t* istate);

void cyg_spinlock_clear_intsave(cyg_spinlock_t* lock, cyg_addrword_t istate);

Description

Spinlocks provide an additional synchronization primitive for applications running on SMP systems. They operate at a lower level than the other primitives such as mutexes, and for most purposes the higher-level primitives should be preferred. However there are some circumstances where a spinlock is appropriate, especially when interrupt handlers and threads need to share access to hardware, and on SMP systems the kernel implementation itself depends on spinlocks.

Essentially a spinlock is just a simple flag. When code tries to claim a spinlock it checks whether or not the flag is already set. If not then the flag is set and the operation succeeds immediately. The exact implementation of this is hardware-specific, for example it may use a test-and-set instruction to guarantee the desired behaviour even if several processors try to access the spinlock at the exact same time. If it is not possible to claim a spinlock then the current thead spins in a tight loop, repeatedly checking the flag until it is clear. This behaviour is very different from other synchronization primitives such as mutexes, where contention would cause a thread to be suspended. The assumption is that a spinlock will only be held for a very short time. If claiming a spinlock could cause the current thread to be suspended then spinlocks could not be used inside interrupt handlers, which is not acceptable.

This does impose a constraint on any code which uses spinlocks. Specifically it is important that spinlocks are held only for a short period of time, typically just some dozens of instructions. Otherwise another processor could be blocked on the spinlock for a long time, unable to do any useful work. It is also important that a thread which owns a spinlock does not get preempted because that might cause another processor to spin for a whole timeslice period, or longer. One way of achieving this is to disable interrupts on the current processor, and the function cyg_spinlock_spin_intsave is provided to facilitate this.

Spinlocks should not be used on single-processor systems. Consider a high priority thread which attempts to claim a spinlock already held by a lower priority thread: it will just loop forever and the lower priority thread will never get another chance to run and release the spinlock. Even if the two threads were running at the same priority, the one attempting to claim the spinlock would spin until it was timesliced and a lot of cpu time would be wasted. If an interrupt handler tried to claim a spinlock owned by a thread, the interrupt handler would loop forever. Therefore spinlocks are only appropriate for SMP systems where the current owner of a spinlock can continue running on a different processor.

Before a spinlock can be used it must be initialized by a call to cyg_spinlock_init. This takes two arguments, a pointer to a cyg_spinlock_t data structure, and a flag to specify whether the spinlock starts off locked or unlocked. If a spinlock is no longer required then it can be destroyed by a call to cyg_spinlock_destroy.

There are two routines for claiming a spinlock: cyg_spinlock_spin and cyg_spinlock_spin_intsave. The former can be used when it is known the current code will not be preempted, for example because it is running in an interrupt handler or because interrupts are disabled. The latter will disable interrupts in addition to claiming the spinlock, so is safe to use in all circumstances. The previous interrupt state is returned via the second argument, and should be used in a subsequent call to cyg_spinlock_clear_intsave.

Similarly there are two routines for releasing a spinlock: cyg_spinlock_clear and cyg_spinlock_clear_intsave. Typically the former will be used if the spinlock was claimed by a call to cyg_spinlock_spin, and the latter when cyg_spinlock_intsave was used.

There are two additional routines. cyg_spinlock_try is a non-blocking version of cyg_spinlock_spin: if possible the lock will be claimed and the function will return true; otherwise the function will return immediately with failure. cyg_spinlock_test can be used to find out whether or not the spinlock is currently locked. This function must be used with care because, especially on a multiprocessor system, the state of the spinlock can change at any time.

Spinlocks should only be held for a short period of time, and attempting to claim a spinlock will never cause a thread to be suspended. This means that there is no need to worry about priority inversion problems, and concepts such as priority ceilings and inheritance do not apply.

Valid contexts

All of the spinlock functions can be called from any context, including ISR and DSR context. Typically cyg_spinlock_init is only called during system initialization.