当前位置: 技术问答>linux和unix
互斥锁与信号量的深层探讨,请赐教!!!
来源: 互联网 发布时间:2017-01-09
本文导语: 最近在研究互斥锁与信号量,信号量还好理解,但是互斥锁的逻辑却有些乱,求人帮忙理一下。太乱了!!! 获得与释放互斥锁最重要的几段代码如下: //这是提供给驱动的接口 void __sched mutex_lock(struct mutex *lock) ...
最近在研究互斥锁与信号量,信号量还好理解,但是互斥锁的逻辑却有些乱,求人帮忙理一下。太乱了!!!
获得与释放互斥锁最重要的几段代码如下:
互斥锁的实现原理和信号量是一样的,最根本的区别是互斥锁的计数变量是volitale的,而信号量却不是。双方都有一个等待队列。
若此时有a和b两个进程,a先获得锁,然后b也要获得锁,而因为a已经占有锁了,所以b会进入休眠。 当a释放锁后,会去唤醒在等待队列上的进程,按正常逻辑此时应该是b被唤醒而获得锁。但是现在却是,a在释放锁之后,又重新获得锁,a会在调度b之前先将计数值减1,然后才调度进程b,b会因为判断计数值不成功而重新进入休眠。为什么呢?为什么a会在调度b之前而先将计数值减1了呢?
而信号量却不是这样的,信号量是在a释放这个信号之后,唤醒等待队列上的进程,此时会马上调度b,使b获得信号量,若a又要重新获得信号量,会因为信号量的计数值小于等于0而进入休眠。
谁能帮我解释一下。
以下是我打印的log
获得与释放互斥锁最重要的几段代码如下:
//这是提供给驱动的接口
void __sched mutex_lock(struct mutex *lock)
{
might_sleep();
/*
* The locking fastpath is the 1->0 transition from
* 'unlocked' into 'locked' state.
*/
__mutex_fastpath_lock(&lock->count, __mutex_lock_slowpath);
mutex_set_owner(lock);
}
//这个函数会根据CPU所支持的ARM指令版本而选择不同的版本,以下为V5以下
//arm指令通用的函数,V5以上的在arch/arm目录下
static inline void
__mutex_fastpath_lock(atomic_t *count, void (*fail_fn)(atomic_t *))
{
if (unlikely(atomic_xchg(count, 0) != 1))
fail_fn(count);
}
static __used noinline void __sched
__mutex_lock_slowpath(atomic_t *lock_count)
{
struct mutex *lock = container_of(lock_count, struct mutex, count);
__mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, 0, _RET_IP_);
}
//以下为没有获得锁而处理的最核心的函数,看着多,其实不难
static inline int __sched
__mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
unsigned long ip)
{
struct task_struct *task = current;
struct mutex_waiter waiter;
unsigned long flags;
preempt_disable();
mutex_acquire(&lock->dep_map, subclass, 0, ip);
spin_lock_mutex(&lock->wait_lock, flags);
debug_mutex_lock_common(lock, &waiter);
debug_mutex_add_waiter(lock, &waiter, task_thread_info(task));
/* add waiting tasks to the end of the waitqueue (FIFO): */
list_add_tail(&waiter.list, &lock->wait_list);
waiter.task = task;
if (atomic_xchg(&lock->count, -1) == 1)
goto done;
lock_contended(&lock->dep_map, ip);
for (;;) {
if (atomic_xchg(&lock->count, -1) == 1)
break;
if (unlikely(signal_pending_state(state, task))) {
mutex_remove_waiter(lock, &waiter,
task_thread_info(task));
mutex_release(&lock->dep_map, 1, ip);
spin_unlock_mutex(&lock->wait_lock, flags);
debug_mutex_free_waiter(&waiter);
preempt_enable();
return -EINTR;
}
__set_task_state(task, state);
//printk(KERN_DEBUG "%s: before, lock_count: 0x%x, pid: %dn", __func__,
//(int)lock->count.counter, task_tgid_vnr(current));
/* didnt get the lock, go to sleep: */
spin_unlock_mutex(&lock->wait_lock, flags);
preempt_enable_no_resched();
schedule();
preempt_disable();
printk(KERN_DEBUG "%s: after, lock_count: 0x%x, pid: %dn", __func__,
(int)lock->count.counter, task_tgid_vnr(current));
spin_lock_mutex(&lock->wait_lock, flags);
}
done:
//printk(KERN_DEBUG "%s: lock_count: 0x%x, pid: %dn", __func__,
//(int)lock->count.counter, task_tgid_vnr(current));
lock_acquired(&lock->dep_map, ip);
/* got the lock - rejoice! */
mutex_remove_waiter(lock, &waiter, current_thread_info());
mutex_set_owner(lock);
/* set it to 0 if there are no waiters left: */
if (likely(list_empty(&lock->wait_list)))
atomic_set(&lock->count, 0);
spin_unlock_mutex(&lock->wait_lock, flags);
debug_mutex_free_waiter(&waiter);
preempt_enable();
return 0;
}
//以下为释放锁的
void __sched mutex_unlock(struct mutex *lock)
{
__mutex_fastpath_unlock(&lock->count, __mutex_unlock_slowpath);
}
static inline void
__mutex_fastpath_unlock(atomic_t *count, void (*fail_fn)(atomic_t *))
{
if (unlikely(atomic_xchg(count, 1) != 0))
fail_fn(count);
}
static __used noinline void
__mutex_unlock_slowpath(atomic_t *lock_count)
{
__mutex_unlock_common_slowpath(lock_count, 1);
}
static inline void
__mutex_unlock_common_slowpath(atomic_t *lock_count, int nested)
{
struct mutex *lock = container_of(lock_count, struct mutex, count);
unsigned long flags;
spin_lock_mutex(&lock->wait_lock, flags);
mutex_release(&lock->dep_map, nested, _RET_IP_);
debug_mutex_unlock(lock);
if (__mutex_slowpath_needs_to_unlock())
atomic_set(&lock->count, 1);
if (!list_empty(&lock->wait_list)) {
/* get the first entry from the wait-list: */
struct mutex_waiter *waiter =
list_entry(lock->wait_list.next,
struct mutex_waiter, list);
debug_mutex_wake_waiter(lock, waiter);
wake_up_process(waiter->task);
printk(KERN_DEBUG "%s: lock_count: 0x%x, pid: %dn", __func__,
(int)lock->count.counter, task_tgid_vnr(current));
}
spin_unlock_mutex(&lock->wait_lock, flags);
}
互斥锁的实现原理和信号量是一样的,最根本的区别是互斥锁的计数变量是volitale的,而信号量却不是。双方都有一个等待队列。
若此时有a和b两个进程,a先获得锁,然后b也要获得锁,而因为a已经占有锁了,所以b会进入休眠。 当a释放锁后,会去唤醒在等待队列上的进程,按正常逻辑此时应该是b被唤醒而获得锁。但是现在却是,a在释放锁之后,又重新获得锁,a会在调度b之前先将计数值减1,然后才调度进程b,b会因为判断计数值不成功而重新进入休眠。为什么呢?为什么a会在调度b之前而先将计数值减1了呢?
而信号量却不是这样的,信号量是在a释放这个信号之后,唤醒等待队列上的进程,此时会马上调度b,使b获得信号量,若a又要重新获得信号量,会因为信号量的计数值小于等于0而进入休眠。
谁能帮我解释一下。
以下是我打印的log
/这是互斥锁的log
[ 83.890838] i2c_transfer: get i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
[ 83.890930] i2c_transfer: get i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x0, len: 1
[ 83.890960] __mutex_lock_common: before, lock_count: 0xffffffff, pid: 1041
[ 83.891143] __mutex_unlock_common_slowpath: lock_count: 0x1, pid: 206
[ 83.891174] i2c_transfer: release i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
[ 83.891174]
[ 83.891204] i2c_transfer: get i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
[ 83.891235] __mutex_lock_common: after, lock_count: 0x0, pid: 1041
[ 83.891235] __mutex_lock_common: before, lock_count: 0xffffffff, pid: 1041
[ 83.891510] __mutex_unlock_common_slowpath: lock_count: 0x1, pid: 206
[ 83.891510] i2c_transfer: release i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
[ 83.891876]
[ 83.891876] i2c_transfer: get i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
[ 83.891906] __mutex_lock_common: after, lock_count: 0x0, pid: 1041
[ 83.891937] __mutex_lock_common: before, lock_count: 0xffffffff, pid: 1041
[ 83.895751] __mutex_unlock_common_slowpath: lock_count: 0x1, pid: 206
[ 83.895751] i2c_transfer: release i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
.........
[ 83.904693] i2c_transfer: get i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 2
[ 83.904693] __mutex_lock_common: after, lock_count: 0x0, pid: 1041
[ 83.904724] __mutex_lock_common: before, lock_count: 0xffffffff, pid: 1041
[ 83.904876] __mutex_unlock_common_slowpath: lock_count: 0x1, pid: 206
[ 83.904876] i2c_transfer: release i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 2
[ 83.904907]
[ 83.904937] __mutex_lock_common: after, lock_count: 0x1, pid: 1041
[ 83.904937] __mutex_lock_common: lock_count: 0xffffffff, pid: 1041
[ 83.905090] i2c_transfer: release i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x0, len: 1
//以下是信号量的log
[ 53.400207] i2c_transfer: get i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x0, len: 1
[ 53.400329] i2c_transfer: get i2c lock, slave->addr: 0x44, cur_pid: 5, flags: 0x0, len: 1
[ 53.400360] __down_common: before count: 0x0, pid: 5, up: 0
[ 53.400390] __up: count: 0x0, pid: 1041, up: 1
[ 53.400390] i2c_transfer: release i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x0, len: 1
[ 53.400421]
[ 53.400421] i2c_transfer: get i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x1, len: 32
[ 53.400451] __down_common: before count: 0x0, pid: 1041, up: 0
[ 53.400451] __down_common: after count: 0x0, pid: 5, up: 1
[ 53.400695] __up: count: 0x0, pid: 5, up: 1
[ 53.400726] i2c_transfer: release i2c lock, slave->addr: 0x44, cur_pid: 5, flags: 0x0, len: 1
[ 53.400726]
[ 53.400726] i2c_transfer: get i2c lock, slave->addr: 0x44, cur_pid: 5, flags: 0x0, len: 1
[ 53.400756] __down_common: before count: 0x0, pid: 5, up: 0
[ 53.400756] __down_common: after count: 0x0, pid: 1041, up: 1
[ 53.402404] __up: count: 0x0, pid: 1041, up: 1
[ 53.402435] i2c_transfer: release i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x1, len: 32
[ 53.402435]
[ 53.402465] i2c_transfer: get i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x0, len: 2
[ 53.402496] __down_common: before count: 0x0, pid: 1041, up: 0
[ 53.402557] __down_common: after count: 0x0, pid: 5, up: 1
[ 53.402832] __up: count: 0x0, pid: 5, up: 1
[ 53.402862] i2c_transfer: release i2c lock, slave->addr: 0x44, cur_pid: 5, flags: 0x0, len: 1
[ 53.402862]
[ 53.402862] i2c_transfer: get i2c lock, slave->addr: 0x44, cur_pid: 5, flags: 0x0, len: 1
[ 53.402893] __down_common: before count: 0x0, pid: 5, up: 0
[ 53.402923] __down_common: after count: 0x0, pid: 1041, up: 1
[ 53.403106] __up: count: 0x0, pid: 1041, up: 1
[ 53.403106] i2c_transfer: release i2c lock, slave->addr: 0x67, cur_pid: 1041, flags: 0x0, len: 2
[ 53.403137]
[ 53.404083] __down_common: after count: 0x0, pid: 5, up: 1
[ 53.404479] i2c_transfer: release i2c lock, slave->addr: 0x44, cur_pid: 5, flags: 0x0, len: 1
|
按照93楼的解释,互斥锁的交互性仍然很高啊,你遇到的问题是 __mutex_unlock_common_slowpath 与 up 的差异导致的。
信号量计数机制应该比互斥锁更复杂一些,所以相对而言,还是互斥锁效率高些吧。
信号量计数机制应该比互斥锁更复杂一些,所以相对而言,还是互斥锁效率高些吧。
|
互斥锁为什么是这样的特性呢?
那就不能实现a、b两个进程交替访问了
那就不能实现a、b两个进程交替访问了
|
如果a在解锁之后,不是立即再加锁,比如sleep一小会
那么b貌似就能抢到锁了
那么b貌似就能抢到锁了
|
同步机制是相对于线程而言的。。。
|
唯一能明白的就是和通信相关,以现在的能力也只能明白到这了。
|
受益非浅,前段出用过,最后选择了信号量,看来还是明智啊,不然也会遇到这样的问题 。
|
互锁应该可以用信号量实现吧!!!
|
看你的mutex的log似乎有问题。
注意一点:mutex跟semaphore重要的区别就是 ownership,
对于mutex,只有获得mutex的进程才能释放该mutex,如果其他的进程调用mutex_unlock,是不能释放的!
检查一下你使用mutex的代码。
注意一点:mutex跟semaphore重要的区别就是 ownership,
对于mutex,只有获得mutex的进程才能释放该mutex,如果其他的进程调用mutex_unlock,是不能释放的!
检查一下你使用mutex的代码。
|
按理说系统会公平的选择等待的线程获得互斥量的所有权的,这个信号量和互斥量应该是一样的,还是查查是否由于其他原因导致的。
|
这期间应该是在内核态与用户态之间切换了100次吧
而不是一直处于内核态
|
我觉得在mutex_unlock后,若发生了调度,等待队列上的进程就可能得到该锁了。
|
个人浅见:
互斥是信号量的一个特例,互斥量在连续unlock后,只要有一个线程lock,其他的线程就不能lock成功了,而信号量不一样,可以在signal两次后,连续调用两次wait,并能成功。
互斥资源同时只有一个线程能使用,信号量能控制同时使用资源的线程的数量。
互斥是信号量的一个特例,互斥量在连续unlock后,只要有一个线程lock,其他的线程就不能lock成功了,而信号量不一样,可以在signal两次后,连续调用两次wait,并能成功。
互斥资源同时只有一个线程能使用,信号量能控制同时使用资源的线程的数量。
|
信号量一般用于多线程/进程的同步,互斥锁一般用于锁定临界资源的吧。
就定义来说是没有先后顺序的吧。
就定义来说是没有先后顺序的吧。
|
[ 83.890960] __mutex_lock_common: before, lock_count: 0xffffffff, pid: 1041
[ 83.891143] __mutex_unlock_common_slowpath: lock_count: 0x1, pid: 206
[ 83.891174] i2c_transfer: release i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
[ 83.891174]
[ 83.891204] i2c_transfer: get i2c lock, slave->addr: 0x34, cur_pid: 206, flags: 0x0, len: 1
[ 83.891235] __mutex_lock_common: after, lock_count: 0x0, pid: 1041
[ 83.891235] __mutex_lock_common: before, lock_count: 0xffffffff, pid: 1041
[ 83.891510] __mutex_unlock_common_slowpath: lock_count: 0x1, pid: 206
看起来,你只在pid 1041里调用了mutex_lock (没调用mutex_unlock),在pid 206里调用了mutex_unlcok,这是你的本意吗?
|
好像把问题复杂化了吧
信号量 : 生产者up,消费者down,没人up,消费者就等待
互斥锁 : 谁想要访问资源,谁去锁资源,如果有人已经锁住了资源,后续的申请者就只有等待拿着锁的人解锁
线程or进程 : 至少对互斥锁来说,如果要进程间使用,把它放在系统的共享内存区就可以了
我以前的代码有用信号量的,后来发现信号量把逻辑搞复杂了,于是就换成锁了,效果还行
信号量 : 生产者up,消费者down,没人up,消费者就等待
互斥锁 : 谁想要访问资源,谁去锁资源,如果有人已经锁住了资源,后续的申请者就只有等待拿着锁的人解锁
线程or进程 : 至少对互斥锁来说,如果要进程间使用,把它放在系统的共享内存区就可以了
我以前的代码有用信号量的,后来发现信号量把逻辑搞复杂了,于是就换成锁了,效果还行
|
代码太长没有认真一句一句的看。现在再看看的话确实是FIFO的队列。
重看了下互斥锁与信号量的定义,确实是有先后顺序的。
那造成你互斥锁的LOG不按先后顺序进行可能是加锁策略造成的,互斥锁加锁是直接加锁而不会先把其放到等待队列中。
而信号量的加锁是会先根据信号量判断是否需要等待,也就是会先查看等待队列中是否进程/线程在等待。
|
任务调度似乎还有优先级自动调整,不能让一个任务不停占有资源的
|
93楼正解。sem的up操作中首先检查是否有任务在down队列,如果有则交给最应该给任务,其余继续等待。
mutex的unlock则不同,直接释放。
mutex的unlock则不同,直接释放。
|
对的。
如果没有进程等待的话,行为应该是一样的。
如果有进程等待的时候,不一样。mutex:想要获取mutex的进程,可以“插队”,
semaphore:想要获取semaphore的进程,必须排在队伍的后面,不能“插队”。
如果没有进程等待的话,行为应该是一样的。
如果有进程等待的时候,不一样。mutex:想要获取mutex的进程,可以“插队”,
semaphore:想要获取semaphore的进程,必须排在队伍的后面,不能“插队”。