Today's Outline
- concurrency in the kernel
- atomic operations
- spinlocks, preemption, and interrupts
- semaphores
- reader-writer locking and semaphores, seq locks
- barriers
- big kernel lock
Sources of Concurrency
- interrupts may cause code to run interleaved (unless interrupts
are disabled)
- kernel preemption may cause different tasks to run interleaved
while in the kernel (preemption is caused by an interrupt)
- sleeping allows other tasks to run while this task is suspended
- synchronization with user space (blocking) does the same
- SMP provides true concurrency
- Love, p. 111
integer atomic operations
- special type atomic_t, integer with at least 24 bits
- atomic_read to convert it to an integer
- change value atomically:
- atomic_set to set the value
- atomic_add to add a value
- atomic_inc to add 1, atomic_dec to subtract 1
- change and test the value atomically:
- atomic_sub_and_test returns true if the result is zero
- atomic_add_negative returns true if the result is negative
- atomic_dec_and_test returns true if the result is zero
- atomic_inc_and_test returns true if the result is zero
bit atomic operations
- work on any bit of any pointer (e.g. bit 300 of a char *
is an acceptable specification)
- change value atomically:
- set_bit(int bit, void * pointer) to set the bit to 1
- clear_bit(int bit, void * pointer) to set the bit to 0
- change_bit(int bit, void * pointer) to complement the bit
- test or test and set:
- test_and_set_bit (int bit, void * pointer) returns the old value
- test_and_clear_bit (int bit, void * pointer) returns the old value
- test_and_change_bit (int bit, void * pointer) returns the old value
- test_bit (int bit, void * pointer) returns the value
- non-atomic versions also exist -- prefix two underscores to the
function (or macro) name
spinlocks, preemption, and interrupts
- spin lock could be implemented via a test_and_set
or atomic_sub_and_test
- spin_lock and spin_unlock
- if:
- an interrupt handler tries to acquire a spin lock that is
locked,
- it will spin forever (except possibly on an SMP)
- so if
the lock may be used by an interrupt handler, need
to suspend interrupts before acquiring the lock:
- spin_lock_irqsave and spin_unlock_irqrestore
- can be called with interrupts already disabled
- so spin_lock_irqsave and spin_unlock_irqrestore
are what should be used in most cases
semaphores
- semaphore down() operation decrements it, blocking if zero,
and up() increments the variable or wakes up one waiting thread
- since may block, cannot be used in interrupt or soft IRQ or
tasklet code
- a binary semaphore is called a mutex in Linux:
DECLARE_MUTEX for a static mutex or init_MUTEX
for a dynamic mutex
- a counting semaphores is initialized with a value, allows that
many simultaneous locks:
DECLARE_SEMAPHORE_GENERIC for a static semaphore or sema_init
for a dynamic semaphore
- for both, use down_interruptible to acquire
- for both, use up to release
- down will suspend the process and not allow it to
be interrupted, i.e. not awaken it when there is a signal, so should
be used sparingly
reader-writer locking and semaphores, seq locks
- if reading and writing are separate activities, can use
reader-writer locks or semaphores
- reader-writer locks:
- any number of readers may acquire the lock
- at most one writer may acquire the lock, and only when there are
no readers
- writers must wait until all readers (and any other writer) are done
- favors readers
- read_lock_irqsave(), read_unlock_irqrestore(),
write_lock_irqsave(), and write_unlock_irqrestore()
- reader-writer semaphores are similar, but
- do not spin
- can downgrade a writer to a reader: downgrade_writer()
- with seq locks (discussed previously),
- writers increment the lock variable before and after writing
(after checking that the lock variable is even)
- readers read the lock variable before and after reading, and
loop if the lock variable is odd or has changed
- favors writers over readers
big kernel lock
- old Linux kernels were not preemptive and did not support SMP
- some of that old code has not been converted yet
- instead, the code calls lock_kernel() before
execution, and unlock_kernel() after
- if the code sleeps, the kernel lock is transparently released
and reacquired
- the code may acquire (and must then release) this BKL (big kernel
lock) recursively
- a solution to allow the running of legacy code, but intended to
disappear