Saturday 3 December 2016

Concurrent Access to Resources Driver Development

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