免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 5669 | 回复: 10
打印 上一主题 下一主题

互斥锁与信号量的深层探讨,请赐教!!! [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-03-04 15:46 |只看该作者 |倒序浏览
本帖最后由 tgvlcw 于 2011-03-04 15:50 编辑

最近在研究互斥锁与信号量,信号量还好理解,但是互斥锁的逻辑却有些乱,求人帮忙理一下。太乱了!!!
获得与释放互斥锁最重要的几段代码如下:

  1. //这是提供给驱动的接口
  2. void __sched mutex_lock(struct mutex *lock)
  3. {
  4.         might_sleep();
  5.         /*
  6.          * The locking fastpath is the 1->0 transition from
  7.          * 'unlocked' into 'locked' state.
  8.          */
  9.         __mutex_fastpath_lock(&lock->count, __mutex_lock_slowpath);
  10.         mutex_set_owner(lock);
  11. }

  12. //这个函数会根据CPU所支持的ARM指令版本而选择不同的版本,以下为V5以下
  13. //arm指令通用的函数,V5以上的在arch/arm目录下
  14. static inline void
  15. __mutex_fastpath_lock(atomic_t *count, void (*fail_fn)(atomic_t *))
  16. {
  17.         if (unlikely(atomic_xchg(count, 0) != 1))
  18.                 fail_fn(count);
  19. }

  20. static __used noinline void __sched
  21. __mutex_lock_slowpath(atomic_t *lock_count)
  22. {
  23.         struct mutex *lock = container_of(lock_count, struct mutex, count);

  24.         __mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, 0, _RET_IP_);
  25. }

  26. //以下为没有获得锁而处理的最核心的函数,看着多,其实不难
  27. static inline int __sched
  28. __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
  29.                        unsigned long ip)
  30. {
  31.         struct task_struct *task = current;
  32.         struct mutex_waiter waiter;
  33.         unsigned long flags;

  34.         preempt_disable();
  35.         mutex_acquire(&lock->dep_map, subclass, 0, ip);
  36.         spin_lock_mutex(&lock->wait_lock, flags);

  37.         debug_mutex_lock_common(lock, &waiter);
  38.         debug_mutex_add_waiter(lock, &waiter, task_thread_info(task));

  39.         /* add waiting tasks to the end of the waitqueue (FIFO): */
  40.         list_add_tail(&waiter.list, &lock->wait_list);
  41.         waiter.task = task;

  42.         if (atomic_xchg(&lock->count, -1) == 1)
  43.                 goto done;

  44.         lock_contended(&lock->dep_map, ip);

  45.         for (;;) {

  46.                 if (atomic_xchg(&lock->count, -1) == 1)
  47.                         break;

  48.                 if (unlikely(signal_pending_state(state, task))) {
  49.                         mutex_remove_waiter(lock, &waiter,
  50.                                             task_thread_info(task));
  51.                         mutex_release(&lock->dep_map, 1, ip);
  52.                         spin_unlock_mutex(&lock->wait_lock, flags);

  53.                         debug_mutex_free_waiter(&waiter);
  54.                         preempt_enable();
  55.                         return -EINTR;
  56.                 }
  57.                 __set_task_state(task, state);

  58.                 //printk(KERN_DEBUG "%s: before, lock_count: 0x%x, pid: %d\n", __func__, \
  59.                                 //(int)lock->count.counter, task_tgid_vnr(current));
  60.                 /* didnt get the lock, go to sleep: */
  61.                 spin_unlock_mutex(&lock->wait_lock, flags);
  62.                 preempt_enable_no_resched();
  63.                 schedule();
  64.                 preempt_disable();
  65.                 printk(KERN_DEBUG "%s: after, lock_count: 0x%x, pid: %d\n", __func__, \
  66.                                 (int)lock->count.counter, task_tgid_vnr(current));
  67.                 spin_lock_mutex(&lock->wait_lock, flags);
  68.         }

  69. done:
  70.         //printk(KERN_DEBUG "%s: lock_count: 0x%x, pid: %d\n", __func__, \
  71.                         //(int)lock->count.counter, task_tgid_vnr(current));
  72.         lock_acquired(&lock->dep_map, ip);
  73.         /* got the lock - rejoice! */
  74.         mutex_remove_waiter(lock, &waiter, current_thread_info());
  75.         mutex_set_owner(lock);

  76.         /* set it to 0 if there are no waiters left: */
  77.         if (likely(list_empty(&lock->wait_list)))
  78.                 atomic_set(&lock->count, 0);

  79.         spin_unlock_mutex(&lock->wait_lock, flags);

  80.         debug_mutex_free_waiter(&waiter);
  81.         preempt_enable();

  82.         return 0;
  83. }

  84. //以下为释放锁的
  85. void __sched mutex_unlock(struct mutex *lock)
  86. {
  87.         __mutex_fastpath_unlock(&lock->count, __mutex_unlock_slowpath);
  88. }

  89. static inline void
  90. __mutex_fastpath_unlock(atomic_t *count, void (*fail_fn)(atomic_t *))
  91. {
  92.         if (unlikely(atomic_xchg(count, 1) != 0))
  93.                 fail_fn(count);
  94. }

  95. static __used noinline void
  96. __mutex_unlock_slowpath(atomic_t *lock_count)
  97. {
  98.         __mutex_unlock_common_slowpath(lock_count, 1);
  99. }

  100. static inline void
  101. __mutex_unlock_common_slowpath(atomic_t *lock_count, int nested)
  102. {
  103.         struct mutex *lock = container_of(lock_count, struct mutex, count);
  104.         unsigned long flags;

  105.         spin_lock_mutex(&lock->wait_lock, flags);
  106.         mutex_release(&lock->dep_map, nested, _RET_IP_);
  107.         debug_mutex_unlock(lock);

  108.         if (__mutex_slowpath_needs_to_unlock())
  109.                 atomic_set(&lock->count, 1);

  110.         if (!list_empty(&lock->wait_list)) {
  111.                 /* get the first entry from the wait-list: */
  112.                 struct mutex_waiter *waiter =
  113.                                 list_entry(lock->wait_list.next,
  114.                                            struct mutex_waiter, list);

  115.                 debug_mutex_wake_waiter(lock, waiter);

  116.                 wake_up_process(waiter->task);
  117.                 printk(KERN_DEBUG "%s: lock_count: 0x%x, pid: %d\n", __func__, \
  118.                                 (int)lock->count.counter, task_tgid_vnr(current));
  119.         }
  120.         spin_unlock_mutex(&lock->wait_lock, flags);
  121. }

复制代码

互斥锁的实现原理和信号量是一样的,最根本的区别是互斥锁的计数变量是volitale的,而信号量却不是。双方都有一个等待队列。
若此时有a和 b两个进程,a先获得锁,然后b也要获得锁,而因为a已经占有锁了,所以b会进入休眠。 当a释放锁后,会去唤醒在等待队列上的进程,按正常逻辑此时应该是b被唤醒而获得锁。但是现在却是,a在释放锁之后,又重新获得锁,a会在调度b之前先将计数值减1,然后才调度进程b,b会因为判断计数值不成功而重新进入休眠。为什么呢?为什么a会在调度b之前而先将计数值减1了呢?

而信号量却不是这样的,信号量是在a释放这个信号之后,唤醒等待队列上的进程,此时会马上调度b,使b获得信号量,若a又要重新获得信号量,会因为信号量的计数值小于等于0而进入休眠。

谁能帮我解释一下。

以下是我打印的log

  1. /这是互斥锁的log
  2. <7>[   83.890838] i2c_transfer: get i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
  3. <7>[   83.890930] i2c_transfer: get i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x0, len: 1
  4. <7>[   83.890960] __mutex_lock_common: before, lock_count: 0xffffffff, pid: 1041
  5. <7>[   83.891143] __mutex_unlock_common_slowpath: lock_count: 0x1, pid: 206
  6. <7>[   83.891174] i2c_transfer: release i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
  7. <7>[   83.891174]
  8. <7>[   83.891204] i2c_transfer: get i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
  9. <7>[   83.891235] __mutex_lock_common: after, lock_count: 0x0, pid: 1041
  10. <7>[   83.891235] __mutex_lock_common: before, lock_count: 0xffffffff, pid: 1041
  11. <7>[   83.891510] __mutex_unlock_common_slowpath: lock_count: 0x1, pid: 206
  12. <7>[   83.891510] i2c_transfer: release i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
  13. <7>[   83.891876]
  14. <7>[   83.891876] i2c_transfer: get i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
  15. <7>[   83.891906] __mutex_lock_common: after, lock_count: 0x0, pid: 1041
  16. <7>[   83.891937] __mutex_lock_common: before, lock_count: 0xffffffff, pid: 1041
  17. <7>[   83.895751] __mutex_unlock_common_slowpath: lock_count: 0x1, pid: 206
  18. <7>[   83.895751] i2c_transfer: release i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
  19. .........
  20. <7>[   83.904693] i2c_transfer: get i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 2
  21. <7>[   83.904693] __mutex_lock_common: after, lock_count: 0x0, pid: 1041
  22. <7>[   83.904724] __mutex_lock_common: before, lock_count: 0xffffffff, pid: 1041
  23. <7>[   83.904876] __mutex_unlock_common_slowpath: lock_count: 0x1, pid: 206
  24. <7>[   83.904876] i2c_transfer: release i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 2
  25. <7>[   83.904907]
  26. <7>[   83.904937] __mutex_lock_common: after, lock_count: 0x1, pid: 1041
  27. <7>[   83.904937] __mutex_lock_common: lock_count: 0xffffffff, pid: 1041
  28. <7>[   83.905090] i2c_transfer: release i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x0, len: 1

  29. //以下是信号量的log
  30. <7>[   53.400207] i2c_transfer: get i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x0, len: 1
  31. <7>[   53.400329] i2c_transfer: get i2c lock, slave->addr: 0x44, cur_pid: 5, flags: 0x0, len: 1
  32. <7>[   53.400360] __down_common: before count: 0x0, pid: 5, up: 0
  33. <7>[   53.400390] __up: count: 0x0, pid: 1041, up: 1
  34. <7>[   53.400390] i2c_transfer: release i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x0, len: 1
  35. <7>[   53.400421]
  36. <7>[   53.400421] i2c_transfer: get i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x1, len: 32
  37. <7>[   53.400451] __down_common: before count: 0x0, pid: 1041, up: 0
  38. <7>[   53.400451] __down_common: after count: 0x0, pid: 5, up: 1
  39. <7>[   53.400695] __up: count: 0x0, pid: 5, up: 1
  40. <7>[   53.400726] i2c_transfer: release i2c lock, slave->addr: 0x44, cur_pid: 5, flags: 0x0, len: 1
  41. <7>[   53.400726]
  42. <7>[   53.400726] i2c_transfer: get i2c lock, slave->addr: 0x44, cur_pid: 5, flags: 0x0, len: 1
  43. <7>[   53.400756] __down_common: before count: 0x0, pid: 5, up: 0
  44. <7>[   53.400756] __down_common: after count: 0x0, pid: 1041, up: 1
  45. <7>[   53.402404] __up: count: 0x0, pid: 1041, up: 1
  46. <7>[   53.402435] i2c_transfer: release i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x1, len: 32
  47. <7>[   53.402435]
  48. <7>[   53.402465] i2c_transfer: get i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x0, len: 2
  49. <7>[   53.402496] __down_common: before count: 0x0, pid: 1041, up: 0
  50. <7>[   53.402557] __down_common: after count: 0x0, pid: 5, up: 1
  51. <7>[   53.402832] __up: count: 0x0, pid: 5, up: 1
  52. <7>[   53.402862] i2c_transfer: release i2c lock, slave->addr: 0x44, cur_pid: 5, flags: 0x0, len: 1
  53. <7>[   53.402862]
  54. <7>[   53.402862] i2c_transfer: get i2c lock, slave->addr: 0x44, cur_pid: 5, flags: 0x0, len: 1
  55. <7>[   53.402893] __down_common: before count: 0x0, pid: 5, up: 0
  56. <7>[   53.402923] __down_common: after count: 0x0, pid: 1041, up: 1
  57. <7>[   53.403106] __up: count: 0x0, pid: 1041, up: 1
  58. <7>[   53.403106] i2c_transfer: release i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x0, len: 2
  59. <7>[   53.403137]
  60. <7>[   53.404083] __down_common: after count: 0x0, pid: 5, up: 1
  61. <7>[   53.404479] i2c_transfer: release i2c lock, slave->addr: 0x44, cur_pid: 5, flags: 0x0, len: 1
复制代码

论坛徽章:
0
2 [报告]
发表于 2011-03-05 11:42 |只看该作者
对解锁理解有点问题,不管是mutex锁还是semaphore,在解锁的时候任务是不会调度的,而是等待“调度点”,等待队列上的进程才会有机会执行。

论坛徽章:
0
3 [报告]
发表于 2011-03-05 12:15 |只看该作者
自己画了张图,帮助理解一下!
[img]

[/img]

论坛徽章:
0
4 [报告]
发表于 2011-03-06 11:07 |只看该作者
注意 __mutex_unlock_common_slowpath 与 up 的区别:
  1. static inline void
  2. __mutex_unlock_common_slowpath(atomic_t *lock_count, int nested)
  3. {
  4.         ......
  5.         if (__mutex_slowpath_needs_to_unlock())
  6.                 atomic_set(&lock->count, 1);
  7.         ......
复制代码
__mutex_unlock_common_slowpath总是会将lock->count置1. (__mutex_slowpath_needs_to_unlock在X86下应该总是返回1。)
假设A进程在持有mutex时,先unlock再lock,那么unlock已经将lock->count置1,则lock会成功。
所以你看到进程A总是持有mutex,进程B总是等待(虽然进程A在unlock时,会将进程B唤醒。但是等进程B醒来,重新检查lock->count的时候,lock->count已经再次被进程A置0了)。
  1. void up(struct semaphore *sem)
  2. {
  3.         unsigned long flags;

  4.         spin_lock_irqsave(&sem->lock, flags);
  5.         if (likely(list_empty(&sem->wait_list)))
  6.                 sem->count++;
  7.         else
  8.                 __up(sem);
  9.         spin_unlock_irqrestore(&sem->lock, flags);
  10. }
复制代码
up则不同,它只有在没有进程等待的情况下才将sem->count++(++以后sem->count才可能大于0)。
如果有进程在等待,则sem->count必定为0,并且up之后sem->count还是0。
假设A进程在持有sem时,先up再down。如果此时有进程B在等待这个sem,则进程A执行up过后sem->count还是0,down会使进程A进入等待。
所以进程A和进程B是交替的在持有sem。

论坛徽章:
0
5 [报告]
发表于 2011-03-07 22:17 |只看该作者
谢谢4楼的,如果是这样,那互斥锁的交互性不是比信号量要差多了吗?假如a进程连续占有这个锁很多次,虽然每次都unlock了,但是b进程还是不能获得这个锁,那么b进程就要sleep很久了。

这是不是互斥锁的bug呢?还是互斥锁就是这么设计的呢?

论坛徽章:
0
6 [报告]
发表于 2011-03-08 12:39 |只看该作者
信号量 在semop函数中 做了进程调度, 而 mutex没有做, 所以 信号量是轮流占有, 而 mutex ,unlock后再lock 是还能申请到锁

论坛徽章:
0
7 [报告]
发表于 2011-03-08 16:55 |只看该作者
如果是这样,那互斥锁的交互性不是比信号量要差多了吗?假如a进程连续占有这个锁很多次,虽然每次都unlock了,但是b进程还是不能获得这个锁,那么b进程就要sleep很久了。
tgvlcw 发表于 2011-03-07 22:17


我不知道为什么sem和mutex在实现上会有这样的差异。
但是,我觉得这个差异跟交互性无关。进程A在mutex_unlock时虽然将lock值重置为1,但是同样还是wake_up了B进程。没错,wake_up不会引发调度,最多只会设置NEED_SCHED。但是后面的spin_unlock呢?spin_unlock会preempt_enable,这就是一个抢占点。如果进程B真的应该抢占A进程的话,它会抢占。如果它不应该抢占,那么睡眠也是应该的。
而sem的这种实现似乎更强调“先来后到”。 两种实现孰优孰劣,还真不好说。


信号量 在semop函数中 做了进程调度, 而 mutex没有做
qianhulou 发表于 2011-03-08 12:39


这个何以见得? 还望指点。

论坛徽章:
0
8 [报告]
发表于 2011-03-15 17:56 |只看该作者
关注,up一下

论坛徽章:
0
9 [报告]
发表于 2011-03-16 09:37 |只看该作者
本帖最后由 qianhulou 于 2011-03-16 14:32 编辑

我看的源代码啊 代码中有进程调度
/* We need to sleep on this operation, so we put the current
         * task into the pending queue and go to sleep.
         */
               
        queue.sops = sops;
        queue.nsops = nsops;
        queue.undo = un;
        queue.pid = task_tgid_vnr(current);
        queue.alter = alter;
        if (alter)
                list_add_tail(&queue.list, &sma->sem_pending);
        else
                list_add(&queue.list, &sma->sem_pending);

        if (nsops == 1) {
                struct sem *curr;
                curr = &sma->sem_base[sops->sem_num];

                if (alter)
                        list_add_tail(&queue.simple_list, &curr->sem_pending);
                else
                        list_add(&queue.simple_list, &curr->sem_pending);
        } else {
                INIT_LIST_HEAD(&queue.simple_list);
                sma->complex_count++;
        }

        queue.status = -EINTR;
        queue.sleeper = current;
        current->state = TASK_INTERRUPTIBLE;
        sem_unlock(sma);

        if (timeout)
                jiffies_left = schedule_timeout(jiffies_left);
        else
                schedule();

        error = get_queue_result(&queue);


看错了 走不到这里...

论坛徽章:
0
10 [报告]
发表于 2011-03-16 10:05 |只看该作者
据Documentation/mutex-design.txt 描述,mutex有很多优点 更轻量,更快等等。

众所周知的区别就是mutex的解锁只能由本线程进行,semaphore则没有这一限制。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP