Overview of mutexes

Semaphore is a mechanism to protect multiple processors from accessing a common resource in a parallel processing environment ,mutex For mutex operations .
Semaphore count Initialize to 1,down()/up() You can also achieve something like mutex The role of .

mutex The semantics of semaphore is simpler and lighter than semaphore , In the test scenario of intense lock contention ,mutex Faster than semaphore execution , Better scalability , in addition mutex The definition of data structure is smaller than semaphore .

mutex The advantages of

  1. mutex It's much more efficient than semaphores :
  2. mutex The spin wait mechanism was first implemented
  3. mutex Try to get the lock before sleeping
  4. mutex Realization MCS To avoid multiple CPU Contention for locks leads to CPU Cache bumps .

mutex Precautions for use of :

  1. Only one thread at a time can hold mutex.
  2. Only the lock holder can unlock . You can't hold it in another process mutex, Release him in another process .
  3. Recursive locking and unlocking are not allowed .
  4. When the process holds mutex when , The process cannot exit .
  5. mutex You have to use the official API To initialize the .
  6. mutex You can sleep , So it's not allowed to be used in interrupt handler or interrupt bottom half , for example tasklet、 Timers, etc .

Catalog :

/linux/include/linux/mutex.h/*
 * Simple, straightforward mutexes with strict semantics:
 *
 * - only one task can hold the mutex at a time
 * - only the owner can unlock the mutex
 * - multiple unlocks are not permitted
 * - recursive locking is not permitted
 * - a mutex object must be initialized via the API
 * - a mutex object must not be initialized via memset or copying
 * - task may not exit with mutex held
 * - memory areas where held locks reside must not be freed
 * - held mutexes must not be reinitialized
 * - mutexes may not be used in hardware or software interrupt
 *   contexts such as tasklets and timers
 *
 * These semantics are fully enforced when DEBUG_MUTEXES is
 * enabled. Furthermore, besides enforcing the above rules, the mutex
 * debugging code also implements a number of additional features
 * that make lock debugging easier and faster:
 *
 * - uses symbolic names of mutexes, whenever they are printed in debug output
 * - point-of-acquire tracking, symbolic lookup of function names
 * - list of all locks held in the system, printout of them
 * - owner tracking
 * - detects self-recursing locks and prints out all relevant info
 * - detects multi-task circular deadlocks and prints out all affected
 *   locks and tasks (and only those tasks)
 */struct mutex {
  /* 1: unlocked, 0: locked, negative: locked, possible waiters */
  atomic_t    count;
  spinlock_t    wait_lock;
  struct list_head  wait_list;#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)
  struct task_struct  *owner;#endif#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
  void      *spin_mlock;  /* Spinner MCS lock */#endif#ifdef CONFIG_DEBUG_MUTEXES
  const char     *name;
  void      *magic;#endif#ifdef CONFIG_DEBUG_LOCK_ALLOC
  struct lockdep_map  dep_map;#endif};


Role and access rules :

  1. Mutex lock is mainly used to realize the mutex access function in the kernel . Kernel mutexes are in atoms API On top of that , But this is not visible to kernel users .
  2. Access to it must follow some rules : Only one task can hold a mutex at a time , And only this task can unlock the mutex . Mutexes cannot be recursively locked or unlocked . A mutex object must pass through it API initialization , They can't be used memset Or copy initialization . A task cannot end when it holds a mutex . The memory area used by the mutex cannot be released . The mutex in use cannot be reinitialized . And mutexes cannot be used to interrupt context .
  3. Mutex is faster than the current kernel semaphore option , And more compact .

The use of mutexes

initialization

Static is defined as :

DEFINE_MUTEX(name);

dynamic initialization mutex, as follows :

mutex_init(&mutex);

The specific implementation is as follows :

#define mutex_init(mutex) \
do { \
static struct lock_class_key __key; \
\
__mutex_init((mutex), #mutex, &__key); \
} while (0)void__mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key){atomic_set(&lock->count, 1);spin_lock_init(&lock->wait_lock);INIT_LIST_HEAD(&lock->wait_list);mutex_clear_owner(lock);#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
lock->spin_mlock = NULL;#endifdebug_mutex_init(lock, name, key);}

Apply for mutex

mutex The list of operations is as follows :

Method describe
mutex_lock(struct mutex*) For the specified mutex locked , If not, sleep
mutex_unlock(struct mutex*) For the specified mutex Unlock
mutex_trylock(struct mutex*) View gets the specified mutex, Return if successful 1; Otherwise the lock is acquired , The return value is 0
mutex_is_lock(struct mutex*) If the lock has been expropriated , Then return to 1; Otherwise return to 0

mutex The simplicity and efficiency of semaphores are due to more restrictions than semaphores . It's different from semaphores , because mutex Just implemented Dijkstra The most basic behavior in the original intention of design . therefore mutex The usage scenarios of are relatively more stringent .

(1) Code :linux/kernel/mutex.c

void inline fastcall __sched mutex_lock(struct mutex *lock);   
 // Get mutex .

Actually, it's to give count Do self subtraction , Then it uses its own spin lock to enter the critical region . First get count Value , Will be count Set as -1, Judge if the original count Of is set to 1, That is to say, mutex can get , Directly , Jump out of . Otherwise, enter the loop and test the mutex repeatedly . In circulation , Also, get the original state of mutex first , I'm going to call it -1, Judge if you can get ( be equal to 1), Then exit the loop , Otherwise, set the state of the current process to non interruptible state , Unlock your own spin lock , put to sleep , When the dispatcher wakes up , Get your own spin lock , Enter a new query to check its own state ( The state of the mutex ) The cycle of .

(2) Specific see linux/kernel/mutex.c

int fastcall __sched mutex_lock_interruptible(struct mutex *lock);

and mutex_lock() equally , Also get mutex . After getting the mutex or going to sleep until getting the mutex, it will return 0. If you go to sleep while waiting to get the lock, you receive a signal ( Sleep interrupted by a signal ), Then return to _EINIR.

(3) Specific see linux/kernel/mutex.c

int fastcall __sched mutex_trylock(struct mutex *lock);

Trying to get a mutex , If successful, return 1, Otherwise return to 0, Don't wait for .

Release the mutex

Specific see linux/kernel/mutex.c

void fastcall mutex_unlock(struct mutex *lock);

Release the mutex obtained by the current process . This function cannot be used in interrupt context , And it is not allowed to release an unlocked mutex .

Mutex trial considerations

  1. Only one task can be held at any time mutex, in other words ,mutex The usage count is always 1
  2. to mutex The locker must be responsible for re unlocking it —— You can't lock a in a context mutex, On the other hand Unlock it in a context . This limitation makes mutex It is not suitable for the complex synchronization scenario of kernel and user space . most The way that is often used is : Lock and unlock in the same context .
  3. Recursively locking and unlocking is not allowed . in other words , You can't recursively hold the same lock , Also, you can't unlock one that has been unlocked mutex
  4. When holding a mutex when , The process cannot exit
  5. mutex Can't be used in interrupt or bottom half , Even using mutex_trylock() Not good either.
  6. mutex Only through the official API management : It can only be initialized using the methods described in the previous section , Can't be copied 、 Manual Initialization or repeated initialization

Semaphores and mutexes

Mutexes are very similar to semaphores , The coexistence of the two in the kernel can be confusing . Fortunately , Their standard usage has simple specifications : Unless mutex There's a constraint that prevents you from using , Otherwise, it should be used prior to semaphore mutex. When you write new code , Only on special occasions ( It's usually very low-level code ) You need to use semaphores . So I suggest choose mutex. If it is found that its constraints cannot be met , And there's no other choice , Consider choosing semaphores

Spin lock and mutex applications

Learn when to use spin lock , When to use mutex ( Or semaphores ) It's important to write good code , But most of the time , It doesn't take much consideration , Because only spinlocks can be used in interrupt context , Only mutex can be used in task sleep .

Let's summarize the requirements of various locks

demand Suggested locking method
Low overhead locking Spin lock is preferred
Short term lock in Spin lock is preferred
Long term lock in Give priority to mutexes
Lock in interrupt context Use spin lock
It takes sleep to hold a lock Using mutexes

Mutex lock lock and unlock use examples

How to use it is as follows :

1. struct mutex mutex;2. mutex_init(&mutex); /* Definition */
3. // Lock
4. mutex_lock(&mutex);5.  
6. // A critical region
7. 
8. // Unlock
9. mutex_unlock(&mutex);

It can be seen that , Mutex is a simplified version of semaphore , Because you no longer need to manage any usage counts .

The following network card DM9000 The driver , Which writes eeprom The operation of mutex Mechanism :

static voiddm9000_write_eeprom(board_info_t *db, int offset, u8 *data){unsigned long flags;if (db->flags & DM9000_PLATF_NO_EEPROM)return;mutex_lock(&db->addr_lock);spin_lock_irqsave(&db->lock, flags);iow(db, DM9000_EPAR, offset);iow(db, DM9000_EPDRH, data[1]);iow(db, DM9000_EPDRL, data[0]);iow(db, DM9000_EPCR, EPCR_WEP | EPCR_ERPRW);spin_unlock_irqrestore(&db->lock, flags);dm9000_wait_eeprom(db);mdelay(1);/* wait at least 150uS to clear */spin_lock_irqsave(&db->lock, flags);iow(db, DM9000_EPCR, 0);spin_unlock_irqrestore(&db->lock, flags);mutex_unlock(&db->addr_lock);}

You can see that every time you drive to eeprom Write data ( Access critical resources ), You need to get the mutex corresponding to the resource first db->addr_lock, And the lock must be released after use .