Spinlocks

Spinlocks are a thread locking mechanism for preemption in a kernel space where a thread waits in a loop "spinning" over and over checking to see if the lock is released. Linux implements spinlocks for their preemption and can be found in the source code.

The code can be accessed by visiting

  • The Linux Kernel Archives and downloading the source tarball. Using tar with the -j option can extract the bzip file, or you can use a windows tool like winrar. From here you can use a linux find while in the top level directory with something like find . -name 'spinlock.h' or most file browsers have built in find functions.

    Now on to some code:

    #define spin_lock_init(_lock)				
    do {							
    	spinlock_check(_lock);				
    	raw_spin_lock_init(&(_lock)->rlock);		
    } while (0)
    

    Well that doesnt tell us much, other than that the spinlock init relies on a raw spinlock somewhere. More digging reveals:

    extern void __raw_spin_lock_init(raw_spinlock_t *lock, const char *name,
    				   struct lock_class_key *key);
    # define raw_spin_lock_init(lock)				
    do {								
    		static struct lock_class_key __key;			
    													
    		__raw_spin_lock_init((lock), #lock, &__key);		
    } while (0)
    

    Initializing a spinlock takes a lock, a name and a key. The raw_spinlock_t and lock_class_key can be found in separate header files spinlock_types.h, and lockdep.h respectively. The good news is that the preprocessor does a lot of the work automatically and when spin_lock_init is called we just pass a spin_lock struct and the raw_spin_lock is called with a name and key struct automatically. This initialized your lock struct(spinlock_t) so you can use it later on.

    Before critical sections, the lock should be aqcuired with a call to

    static inline void spin_lock(spinlock_t *lock)
    {
    raw_spin_lock(&lock->rlock);
    }
    

    similar to the init call, it defines the upper level API by calls to "raw" versions which take just a lock struct. This obtains the lock so you can enter the critical section.

    static inline void spin_unlock(spinlock_t *lock)
    {
    	raw_spin_unlock(&lock->rlock);
    }
    

    And here we have the same deal to release the lock after the critical section. Also, of course these rely on an atomic action. We can see at the end of the spinlock.h, an atomic action is put in.

    #include linux/atomic.h
    extern int _atomic_dec_and_lock(atomic_t *atomic, spinlock_t *lock);
    #define atomic_dec_and_lock(atomic, lock) 
    		__cond_lock(lock, _atomic_dec_and_lock(atomic, lock))
    

    These are the most basic calls in the API, but lock has 4 calls with matching unlock calls, regarding disabling hardware and software interrupts before taking the locks. These rely on the lower level irq(interrupt request) functionality included with the #include irqflagsin spinlock.h. They behave similarly regarding syntax. Many other Kernel resources rely on the functionality of the spinlocks, so they are of vital importance to modern Operating systems. For example, all these kernel headers include and reference spinlock.h

    semaphore.h
    sched.h
    memory.h
    types.h
    seqlock.h
    

    and many many more. Using semaphore as an example, the spinlock_t type specified in spinlock_types.h is a member of the semaphore struct that gets initialized whenever a semaphore is initialized

    struct semaphore {
    	raw_spinlock_t		lock;
    	unsigned int		count;
    	struct list_head	wait_list;
    };