Using Semaphores and Mutex in Linux Device drivers to tackle concurency
Semaphores provide a satisfactory solution for issues related to concurrency . Access to critical section is controlled by enforcing threads to hold a lock before entering the critical section , Without a semaphore being unlocked no thread is allowed access to execute in the critical section .
Semaphore is a single integer value bound to two functions P and V . P is called down or some variation of that name and V is called up . Function P is used to lock the critical region and V is used to unlock . Function P decreases the count of semaphores , for example from 1 to 0 . When the value of the semaphore still remains 0 or less no other threads are allowed to execute in the critical region . There are made to wait or even may be put to sleep till the region is unlocked .
Functions of type V releases the semaphores that’s being help , these category of functions increases the value count of the semaphore and may even wake up the processes waiting for the access into the critical region .
Semaphores are generally and mostly used for the purpose of mutual exclusion , that is to make sure that only one thread of execution or a process executes in the critical section .
Linux Implementation of Semaphores
<asm/semaphore.h> defines several functionalities for semaphore and mutex implementation ( Mutexes are just a special case of semaphores ) .
To initialize a declared semaphore ,
void sema_init(struct semaphore *sema, int val) can be used ,
this initializes the declared mutex to value val ( The second argument to the function ) ;
But , as i had mentioned earlier semaphores are generally used for implementation of mutex ,
the following two functions declare and initialize the mutex
DECLARE_MUTEX(mutex_name ) ;
These functions declares mutex named as ‘mutex_name’ , the function DECLARE_MUTEX declares the mutex and initializes it to 1 ( 1 is the unlocked state , a thread or a process can possibly lock the critical region ) , wherein the later function declares the mutex and initializes it to 0 ( 0 is the locked state ) , mutex has to be explicitly unlocked before it can be used for locking .
For initializing the mutex at runtime the following functions can be used ,
void init_MUTEX(struct semaphore *sema ) ;
void init_MUTEX_LOCKED(struct semaphore *sema) ;
Holding and releasing Mutex ( The version of P [down] and V[up] functions )
Linux kernel provides three versions of down function
void down(struct semaphore *sema) ;
int down_interruptible(struct semaphore *sema) ;
int down_trylock(struct semaphore *sema ) ;
As briefed earlier down decrements the value of the semaphore , successful exection of any version of down function leads to locking of the critical region of the code , and the thread of execution or the process is said to have acquired the lock . Other user space process trying to access the critical region of code will be made to wait .
In case of the first version of down ( void down_interruptible() ) , the user space process waiting for the mutex to be released cannot to interrupted , it’ll be running in a dreaded state . This is not desirable in many cases , so the second version of down allows the waiting user space process to be interrupted .
The third version can used to test and lock , if the lock is already being held the function returns with a negative value , If no other thread is holding the lock , the lock will be acquired . Using down_trylock() the process wont wait for the acquired lock to be released , it returns immediately .
void up(struct semaphore * sema) is used to unlock the acquired lock and even wake up the processes waiting on the semaphore .
When executing in the critical region on occurance of error its very important to unlock the region before returning