关闭

互斥锁mutex

101人阅读 评论(0) 收藏 举报
在信号量最后的部分说,当count=1的时候可以用信号量实现互斥。在早期的Linux版本中就是当count=1来实现mutex的。 内核重新定义了一个新的数据结构 struct mutex, 将其称为互斥锁或者互斥体。同时对信号量的DOWN和UP操作针对struct mutex做了修改。

互斥锁的定义和初始化

因为struct mutex的定义中有一些调试相关的成员,在这里去掉调试信息。
[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * Simple, straightforward mutexes with strict semantics: 
  3.  * 
  4.  * - only one task can hold the mutex at a time 
  5.  * - only the owner can unlock the mutex 
  6.  * - multiple unlocks are not permitted 
  7.  * - recursive locking is not permitted 
  8.  * - a mutex object must be initialized via the API 
  9.  * - a mutex object must not be initialized via memset or copying 
  10.  * - task may not exit with mutex held 
  11.  * - memory areas where held locks reside must not be freed 
  12.  * - held mutexes must not be reinitialized 
  13.  * - mutexes may not be used in hardware or software interrupt 
  14.  *   contexts such as tasklets and timers 
  15.  * 
  16.  * These semantics are fully enforced when DEBUG_MUTEXES is 
  17.  * enabled. Furthermore, besides enforcing the above rules, the mutex 
  18.  * debugging code also implements a number of additional features 
  19.  * that make lock debugging easier and faster: 
  20.  * 
  21.  * - uses symbolic names of mutexes, whenever they are printed in debug output 
  22.  * - point-of-acquire tracking, symbolic lookup of function names 
  23.  * - list of all locks held in the system, printout of them 
  24.  * - owner tracking 
  25.  * - detects self-recursing locks and prints out all relevant info 
  26.  * - detects multi-task circular deadlocks and prints out all affected 
  27.  *   locks and tasks (and only those tasks) 
  28.  */  
  29. struct mutex {  
  30.     /* 1: unlocked, 0: locked, negative: locked, possible waiters */  
  31.     atomic_t        count;  
  32.     spinlock_t      wait_lock;  
  33.     struct list_head    wait_list;  
  34. };  
这里必须要对注释详细说明一下,其中说明一些mutex的严格语法信息:
a.  在同一时刻只能有一个task获得互斥锁
b.  只有锁的获得者才能有资格释放锁
c.  多处释放锁是不允许的
d.  递归获取锁是不允许的
e.  互斥锁必须使用系统的API初始化,不允许直接操作使用memset/memcpy
f.   获得锁的task是不允许退出
g.  持有锁驻留的内存区域不能被释放
h. 互斥锁不能用于中断上下文中, spin_lock是可以用于中断上下文的 。

再解释下struct mutex成员的含义:
count:        count是一个原子变量,(关于原子变量不懂的,可以看前面的原子变量文章)。 当count=1代表资源可用,等于0代表资源不可用,加锁状态。 负值代表有等待者。
wait_lock: 是一个自旋锁变量, 用于对wait_list的操作变为原子变量
wait_list  : 用于管理那些在获取mutex的进程,在无法获取互斥锁的时候,进入wait_List睡眠。

是不是和semaphore一样。 既然一样,互斥锁的定义和初始化也不能直接操作,必须使用系统提供的API:

定义一个静态的struct mutex变量的同时初始化的方法:
[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. #define __MUTEX_INITIALIZER(lockname) \  
  2.         { .count = ATOMIC_INIT(1) \  
  3.         , .wait_lock = __SPIN_LOCK_UNLOCKED(lockname.wait_lock) \  
  4.         , .wait_list = LIST_HEAD_INIT(lockname.wait_list) \  
  5.         __DEBUG_MUTEX_INITIALIZER(lockname) \  
  6.         __DEP_MAP_MUTEX_INITIALIZER(lockname) }  
  7.   
  8. #define DEFINE_MUTEX(mutexname) \  
  9.     struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)  

如果需要初始化一个互斥锁,则可以使用mutex_init宏:
[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * mutex_init - initialize the mutex 
  3.  * @mutex: the mutex to be initialized 
  4.  * 
  5.  * Initialize the mutex to unlocked state. 
  6.  * 
  7.  * It is not allowed to initialize an already locked mutex. 
  8.  */  
  9. # define mutex_init(mutex) \  
  10. do {                            \  
  11.     static struct lock_class_key __key;     \  
  12.                             \  
  13.     __mutex_init((mutex), #mutex, &__key);      \  
  14. while (0)  
  15.   
  16. void __mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)  
  17. {  
  18.     atomic_set(&lock->count, 1);  
  19.     spin_lock_init(&lock->wait_lock);  
  20.     INIT_LIST_HEAD(&lock->wait_list);  
  21.     mutex_clear_owner(lock);  
  22. #ifdef CONFIG_MUTEX_SPIN_ON_OWNER  
  23.     osq_lock_init(&lock->osq);  
  24. #endif  
  25.   
  26.     debug_mutex_init(lock, name, key);  
  27. }  
上面的两种初始化,效果最后都是一样。将count初始化为1, 将wait_lock设置为unlock状态,初始化wait_list链表头。

互斥锁的DOWN操作

互斥锁的DOWN操作在linux内核中定义为mutex_lock函数,如下:
[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * mutex_lock - acquire the mutex 
  3.  * @lock: the mutex to be acquired 
  4.  * 
  5.  * Lock the mutex exclusively for this task. If the mutex is not 
  6.  * available right now, it will sleep until it can get it. 
  7.  * 
  8.  * The mutex must later on be released by the same task that 
  9.  * acquired it. Recursive locking is not allowed. The task 
  10.  * may not exit without first unlocking the mutex. Also, kernel 
  11.  * memory where the mutex resides mutex must not be freed with 
  12.  * the mutex still locked. The mutex must first be initialized 
  13.  * (or statically defined) before it can be locked. memset()-ing 
  14.  * the mutex to 0 is not allowed. 
  15.  * 
  16.  * ( The CONFIG_DEBUG_MUTEXES .config option turns on debugging 
  17.  *   checks that will enforce the restrictions and will also do 
  18.  *   deadlock debugging. ) 
  19.  * 
  20.  * This function is similar to (but not equivalent to) down(). 
  21.  */  
  22. void __sched mutex_lock(struct mutex *lock)  
  23. {  
  24.     might_sleep();  
  25.     /* 
  26.      * The locking fastpath is the 1->0 transition from 
  27.      * 'unlocked' into 'locked' state. 
  28.      */  
  29.     __mutex_fastpath_lock(&lock->count, __mutex_lock_slowpath);  
  30.     mutex_set_owner(lock);  
  31. }  

mutex_lock是用来获得互斥锁,如果不能立刻获得互斥锁,进程将睡眠直到获得锁为止。

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. #ifdef CONFIG_DEBUG_ATOMIC_SLEEP  
  2.   void __might_sleep(const char *file, int line, int preempt_offset);  
  3. /** 
  4.  * might_sleep - annotation for functions that can sleep 
  5.  * 
  6.  * this macro will print a stack trace if it is executed in an atomic 
  7.  * context (spinlock, irq-handler, ...). 
  8.  * 
  9.  * This is a useful debugging help to be able to catch problems early and not 
  10.  * be bitten later when the calling function happens to sleep when it is not 
  11.  * supposed to. 
  12.  */  
  13. # define might_sleep() \  
  14.     do { __might_sleep(__FILE__, __LINE__, 0); might_resched(); } while (0)  
  15. #else  
  16.   static inline void __might_sleep(const char *file, int line,  
  17.                    int preempt_offset) { }  
  18. # define might_sleep() do { might_resched(); } while (0)  
  19. #endif  
might_sleep用于调试使用,可以很好的判断是否在原子上下文中是否睡眠,只有当CONFIG_DEBUG_ATOMIC_SLEEP宏开启的时候有效。

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * The locking fastpath is the 1->0 transition from 'unlocked' into 'locked' state. 
  3.  */  
  4. __mutex_fastpath_lock(&lock->count, __mutex_lock_slowpath);  

__mutex_fastpath_lock函数用于锁的状态从unlocked转为locked。函数的设计思想体现在__mutex_fastpath_lock和__mutex_lock_slowpath两条主线上。__mutex_fastpath_lock用于快速判断是否可以获得互斥锁,如果成功获得,就直接返回。否则就进入到__mutex_lock_slowpath函数中。这样设计是基于一个事实,就是获得某一个互斥锁绝大多数是可以获得的。也可一说进入到__mutex_lock_slowpath的几率很低。

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  *  __mutex_fastpath_lock - try to take the lock by moving the count 
  3.  *                          from 1 to a 0 value 
  4.  *  @count: pointer of type atomic_t 
  5.  *  @fail_fn: function to call if the original value was not 1 
  6.  * 
  7.  * Change the count from 1 to a value lower than 1, and call <fail_fn> if 
  8.  * it wasn't 1 originally. This function MUST leave the value lower than 
  9.  * 1 even when the "1" assertion wasn't true. 
  10.  */  
  11. static inline void  
  12. __mutex_fastpath_lock(atomic_t *count, void (*fail_fn)(atomic_t *))  
  13. {  
  14.     if (unlikely(atomic_dec_return(count) < 0))  
  15.         fail_fn(count);  
  16. }  
该函数是给count执行减1的操作,如果执行失败就执行slowpath函数。此函数减1的操作是调用的原子操作中的dec函数执行的,详细可见原子操作小节。

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. __visible void __sched __mutex_lock_slowpath(atomic_t *lock_count)  
  2. {  
  3.     struct mutex *lock = container_of(lock_count, struct mutex, count);  
  4.   
  5.     __mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, 0,  
  6.                 NULL, _RET_IP_, NULL, 0);  
  7. }  
调用__mutex_lock_common函数来执行最后的获得互斥锁的旅途。
[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * Lock a mutex (possibly interruptible), slowpath: 
  3.  */  
  4. static __always_inline int __sched __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,  
  5.             struct lockdep_map *nest_lock, unsigned long ip,  
  6.             struct ww_acquire_ctx *ww_ctx, const bool use_ww_ctx)  
  7. {  
  8.     struct task_struct *task = current;  
  9.     struct mutex_waiter waiter;  
  10.     unsigned long flags;  
  11.     int ret;  
  12.   
  13.     preempt_disable();  
  14.     mutex_acquire_nest(&lock->dep_map, subclass, 0, nest_lock, ip);  
  15.   
  16.     if (mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx)) {  
  17.         /* got the lock, yay! */  
  18.         preempt_enable();  
  19.         return 0;  
  20.     }  
  21.   
  22.     spin_lock_mutex(&lock->wait_lock, flags);  
  23.   
  24.     /* 
  25.      * Once more, try to acquire the lock. Only try-lock the mutex if 
  26.      * it is unlocked to reduce unnecessary xchg() operations. 
  27.      */  
  28.     if (!mutex_is_locked(lock) && (atomic_xchg(&lock->count, 0) == 1))  
  29.         goto skip_wait;  
  30.   
  31.     debug_mutex_lock_common(lock, &waiter);  
  32.     debug_mutex_add_waiter(lock, &waiter, task_thread_info(task));  
  33.   
  34.     /* add waiting tasks to the end of the waitqueue (FIFO): */  
  35.     list_add_tail(&waiter.list, &lock->wait_list);  
  36.     waiter.task = task;  
  37.   
  38.     lock_contended(&lock->dep_map, ip);  
  39.   
  40.     for (;;) {  
  41.         /* 
  42.          * Lets try to take the lock again - this is needed even if 
  43.          * we get here for the first time (shortly after failing to 
  44.          * acquire the lock), to make sure that we get a wakeup once 
  45.          * it's unlocked. Later on, if we sleep, this is the 
  46.          * operation that gives us the lock. We xchg it to -1, so 
  47.          * that when we release the lock, we properly wake up the 
  48.          * other waiters. We only attempt the xchg if the count is 
  49.          * non-negative in order to avoid unnecessary xchg operations: 
  50.          */  
  51.         if (atomic_read(&lock->count) >= 0 &&  
  52.             (atomic_xchg(&lock->count, -1) == 1))  
  53.             break;  
  54.   
  55.         /* 
  56.          * got a signal? (This code gets eliminated in the 
  57.          * TASK_UNINTERRUPTIBLE case.) 
  58.          */  
  59.         if (unlikely(signal_pending_state(state, task))) {  
  60.             ret = -EINTR;  
  61.             goto err;  
  62.         }  
  63.   
  64.         if (use_ww_ctx && ww_ctx->acquired > 0) {  
  65.             ret = __mutex_lock_check_stamp(lock, ww_ctx);  
  66.             if (ret)  
  67.                 goto err;  
  68.         }  
  69.   
  70.         __set_task_state(task, state);  
  71.   
  72.         /* didn't get the lock, go to sleep: */  
  73.         spin_unlock_mutex(&lock->wait_lock, flags);  
  74.         schedule_preempt_disabled();  
  75.         spin_lock_mutex(&lock->wait_lock, flags);  
  76.     }  
  77.     mutex_remove_waiter(lock, &waiter, current_thread_info());  
  78.     /* set it to 0 if there are no waiters left: */  
  79.     if (likely(list_empty(&lock->wait_list)))  
  80.         atomic_set(&lock->count, 0);  
  81.     debug_mutex_free_waiter(&waiter);  
  82.   
  83. skip_wait:  
  84.     /* got the lock - cleanup and rejoice! */  
  85.     lock_acquired(&lock->dep_map, ip);  
  86.     mutex_set_owner(lock);  
  87.   
  88.     spin_unlock_mutex(&lock->wait_lock, flags);  
  89.     preempt_enable();  
  90.     return 0;  
  91. }  

该函数先不是急于将没有获得互斥锁的进程放入到等待队列,而是在放入之前还要看是否锁已经释放的挣扎。这是基于这样的一个事实: 拥有互斥锁的进程应该在尽短的时间内释放锁,如果刚好释放了锁,就不需要进入到等待队列等待了。

挣扎一:
[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. if (mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx)) {  
  2.     /* got the lock, yay! */  
  3.     preempt_enable();  
  4.     return 0;  
  5. }  

挣扎二:
[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * Once more, try to acquire the lock. Only try-lock the mutex if 
  3.  * it is unlocked to reduce unnecessary xchg() operations. 
  4.  */  
  5. if (!mutex_is_locked(lock) && (atomic_xchg(&lock->count, 0) == 1))  
  6.     goto skip_wait;  

在经过两次挣扎之后,终于选择放弃。将此进程加入到等待队列。
[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /* add waiting tasks to the end of the waitqueue (FIFO): */  
  2. list_add_tail(&waiter.list, &lock->wait_list);  
  3. waiter.task = task;  

接下来和信号量大体逻辑相似,在一个for循环中将进程设置state,  然后调度出去。 等待互斥锁的UP操作之后,返回。

互斥锁的UP操作

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * mutex_unlock - release the mutex 
  3.  * @lock: the mutex to be released 
  4.  * 
  5.  * Unlock a mutex that has been locked by this task previously. 
  6.  * 
  7.  * This function must not be used in interrupt context. Unlocking 
  8.  * of a not locked mutex is not allowed. 
  9.  * 
  10.  * This function is similar to (but not equivalent to) up(). 
  11.  */  
  12. void __sched mutex_unlock(struct mutex *lock)  
  13. {  
  14.     /* 
  15.      * The unlocking fastpath is the 0->1 transition from 'locked' 
  16.      * into 'unlocked' state: 
  17.      */  
  18.     __mutex_fastpath_unlock(&lock->count, __mutex_unlock_slowpath);  
  19. }  

mutex_unlock用于释放一个互斥锁,只有以前获得锁的task才可以释放锁,同时mutex_unlock不能用于中断上写文,因为其可能会睡眠。此函数和up函数很相似,但不等同。
__mutex_fastpath_unlock函数用于锁的状态从"locked"到"unlocked"。

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. static inline void  
  2. __mutex_fastpath_unlock(atomic_t *count, void (*fail_fn)(atomic_t *))  
  3. {  
  4.     if (unlikely(atomic_inc_return(count) <= 0))  
  5.         fail_fn(count);  
  6. }  
可以看到依旧是调用的是原子操作的inc函数,然后对返回值返回。如果设置失败就调用slowpath函数。

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * Release the lock, slowpath: 
  3.  */  
  4. __visible void  
  5. __mutex_unlock_slowpath(atomic_t *lock_count)  
  6. {  
  7.     struct mutex *lock = container_of(lock_count, struct mutex, count);  
  8.   
  9.     __mutex_unlock_common_slowpath(lock, 1);  
  10. }  
调用__mutex_unlock_common_slowpath函数执行唤醒操作。
[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * Release the lock, slowpath: 
  3.  */  
  4. static inline void __mutex_unlock_common_slowpath(struct mutex *lock, int nested)  
  5. {  
  6.     unsigned long flags;  
  7.   
  8.     /* 
  9.      * As a performance measurement, release the lock before doing other 
  10.      * wakeup related duties to follow. This allows other tasks to acquire 
  11.      * the lock sooner, while still handling cleanups in past unlock calls. 
  12.      * This can be done as we do not enforce strict equivalence between the 
  13.      * mutex counter and wait_list. 
  14.      * 
  15.      * 
  16.      * Some architectures leave the lock unlocked in the fastpath failure 
  17.      * case, others need to leave it locked. In the later case we have to 
  18.      * unlock it here - as the lock counter is currently 0 or negative. 
  19.      */  
  20.     if (__mutex_slowpath_needs_to_unlock())  
  21.         atomic_set(&lock->count, 1);  
  22.   
  23.     spin_lock_mutex(&lock->wait_lock, flags);  
  24.     mutex_release(&lock->dep_map, nested, _RET_IP_);  
  25.     debug_mutex_unlock(lock);  
  26.   
  27.     if (!list_empty(&lock->wait_list)) {  
  28.         /* get the first entry from the wait-list: */  
  29.         struct mutex_waiter *waiter =  
  30.                 list_entry(lock->wait_list.next,  
  31.                        struct mutex_waiter, list);  
  32.   
  33.         debug_mutex_wake_waiter(lock, waiter);  
  34.   
  35.         wake_up_process(waiter->task);  
  36.     }  
  37.   
  38.     spin_unlock_mutex(&lock->wait_lock, flags);  
  39. }  

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. if (__mutex_slowpath_needs_to_unlock())  
  2.     atomic_set(&lock->count, 1);  
以上函数是用来将count执行set为1的操作。

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. if (!list_empty(&lock->wait_list)) {  
  2.     /* get the first entry from the wait-list: */  
  3.     struct mutex_waiter *waiter = list_entry(lock->wait_list.next,struct mutex_waiter, list);  
  4.   
  5.     debug_mutex_wake_waiter(lock, waiter);  
  6.     wake_up_process(waiter->task);  
  7. }  
从等待队列取第一个等待者,然后执行唤醒操作。
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:7312次
    • 积分:1100
    • 等级:
    • 排名:千里之外
    • 原创:107篇
    • 转载:2篇
    • 译文:0篇
    • 评论:0条