Concurrent access to resources
• Concurrency arises because of
• Interrupts, which interrupts the
current thread to execute an interrupt handler. They may be using shared
resources.
• Kernel pre-emption, if enabled,
causes the kernel to switch from the execution of one system call to another.
They may be using shared resources.
• Multiprocessing, in which case code
is really executed in parallel on different processors, and they may be using
shared resources as well.
• The solution is to keep as much
local state as possible and for the shared resources, use locking.
• In terms of concurrency, the kernel
has the same constraint has a multi-threaded program: all its state is global
and visible in all executions contexts
Concurrency
protection with locks
Linux mutexes
• The main locking primitive since
Linux 2.6.16.
• The kernel used to have semaphores
only, and mutexes have been introduced as a simplification of binary
semaphores.
• The process requesting the lock
blocks when the lock is already held. Mutexes can therefore only be used in
contexts where sleeping is allowed.
• Mutex definition:
#include
<linux/mutex.h>
• Initializing a mutex statically:
• DEFINE_MUTEX(name);
• Or initializing a mutex dynamically:
• void mutex_init(struct mutex *lock);
Locking and unlocking mutexes
• void mutex_lock(struct mutex *lock);
– Tries to lock the mutex, sleeps
otherwise.
– Caution: can't be interrupted,
resulting in processes you cannot kill!
• int mutex_lock_killable(struct mutex
*lock);
– Same, but can be interrupted by a
fatal (SIGKILL) signal. If interrupted, returns a non zero value and doesn't
hold the lock. Test the return value!!!
• int mutex_lock_interruptible(struct
mutex *lock);
– Same, but can be interrupted by any
signal.
• int mutex_trylock(struct mutex
*lock);
– Never waits. Returns a non zero
value if the mutex is not available.
• int mutex_is_locked(struct mutex
*lock);
– Just tells whether the mutex is
locked or not.
• void mutex_unlock(struct mutex
*lock);
– Releases the lock. Do it as soon as
you leave the critical section.
Spinlocks
• Locks to be used for code that is
not allowed to sleep (interrupt handlers), or that doesn't want to sleep
(critical sections). Be very careful not to call functions which can sleep!
• Originally intended for
multiprocessor systems
• Spinlocks never sleep and keep
spinning in a loop until the lock is available.
• Spinlocks cause kernel preemption to
be disabled on the CPU executing them.
• The critical section protected by a
spinlock is not allowed to sleep.
• Initializing
spinlocks
• static
• DEFINE_SPINLOCK (my_lock);
• Dynamic
• void spin_lock_init (spinlock_t
*lock);
• Several variants, depending on where
the spinlock is called:
• void spin_[un]lock(spinlock_t
*lock);
• Doesn't disable interrupts. Used for
locking in process context (critical sections in which you do not want to sleep).
• void spin_lock_irqsave /
spin_unlock_irqrestore (spinlock_t *lock, unsigned long flags);
• Disables / restores IRQs on the
local CPU. Typically used when the lock can be accessed in both process and
interrupt context, to prevent preemption by interrupts.
• void spin_[un]lock_bh(spinlock_t
*lock);
• Disables software interrupts, but
not hardware ones. Useful to protect shared data accessed in process context
and in a soft interrupt (“bottom half”). No need to disable hardware interrupts
in this case.
• Note that reader / writer spinlocks
also exist.
Deadlock situations
• They
can lock up your system. Make sure they never happen.
• Don’t
call a function that can try to access the same lock
• Hold
multiple locks.
• Mixing
order of locks while acquiring.
Atomic variables
• Useful when the shared resource is
an integer value
• Even an instruction like n++ is not
guaranteed to be atomic on all processors!
• Header
– #include <asm/atomic.h>
• Type
– atomic_t
– contains a signed integer (at least
24 bits)
• Atomic operations (main ones)
• Set or read the counter:
– atomic_set(atomic_t *v, int i);
– int atomic_read(atomic_t *v);
• Operations without return value:
– void atomic_inc(atomic_t *v);
– void atomic_dec(atomic_t *v);
– void atomic_add(int i, atomic_t *v);
– void atomic_sub(int i, atomic_t *v);
• Similar functions testing the
result:
– int atomic_inc_and_test(...);
– int atomic_dec_and_test(...);
– int atomic_sub_and_test(...);
• Functions returning the new value:
– int atomic_inc_and_return(...);
– int atomic_dec_and_return(...);
– int atomic_add_and_return(...);
– int atomic_sub_and_return(...);
Atomic bit operations
• Supply very fast, atomic operations
• On most platforms, apply to an
unsigned long type.
• Apply to a void type on a few
others.
• Set, clear, toggle a given bit:
• void set_bit(int nr, unsigned long *
addr);
• void clear_bit(int nr, unsigned long
* addr);
• Test bit value:
• int test_bit(int nr, unsigned long
*addr);
• Test and modify (return the previous
value):
• int test_and_set_bit(...);
• int test_and_clear_bit(...);
• int test_and_change_bit(...);
No comments:
Post a Comment