上文说到小明驱车离开银行,随即开到了比较大的商城茂业(在深圳来说,茂业算是大商城咯)楼下,准备把车停在停车场下,进里面看看zippo火机,自己怎么也算是个“白骨精”(新时代对白领、骨干、精英的称谓),吸烟也得有个好装备:)驱车来到停车场前,发现保安在为排在前面的10辆车一个个的发停车卡,发停车卡很快的,自己等一下吧。不禁又联想到这个和操作系统的那个机制有些类似呢?对了,互斥锁。其实互斥锁和当count为1的信号量有相似之处,但却又不同。
信号量:count为1时,也可以达到互斥的效果,当进程想进入临界区的时候,首先获得信号量,当count为
0时,进程进入睡眠状态,等待获得信号量的进程释放信号量。
互斥锁:专为互斥设计的,内部实现为:首先快速的的方式获得互斥锁,若是快速获得信号量
失败,则进入通用的(慢速的)获取互斥锁,通用互斥锁获取方式同count为0时的机制相同。
从这里可以看到,互斥锁的一个设计原则是基于一个事实:拥有互斥锁的进程总是会在尽可能短的时间里释放。只有在这个前提下,使用互斥锁效率才是最高的,否则,和使用信号量差别也不大。对应到自己,发现自己在排队等待的过程多类似获得停车卡的一个过程,大家首先都认为发停车卡是一个很快的过程,所以,都排队等候而不会离开。当然,也有运气不好的,发到自己就发完了,没有停车位,那么你也可以选择停车等候,一旦有了停车位,保安会通知你让你进去。
互斥锁在内核中的定义如下:
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 thread_info *owner;
#endif
#ifdef CONFIG_DEBUG_MUTEXES
const char *name;
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
去除调试信息后,我们发现和信号量的定义没有什么不同,主要差别体现在获取锁的操作DOWN和释放锁的操作UP上:
DOWN操作:
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);
}
UP操作:
void __sched mutex_unlock(struct mutex *lock)
{
/*
* The unlocking fastpath is the 0->1 transition from 'locked'
* into 'unlocked' state:
*/
#ifndef CONFIG_DEBUG_MUTEXES
/*
* When debugging is enabled we must not clear the owner before time,
* the slow path will always be taken, and that clears the owner field
* after verifying that it was indeed current.
*/
mutex_clear_owner(lock);
#endif
__mutex_fastpath_unlock(&lock->count, __mutex_unlock_slowpath);
}
我们看红色和蓝色部分,一个是快速路径获取,一个是慢速路径获取,慢速路径获取没有什么可说的,现在我们重点说一下快速路径的获取:
static inline void
__mutex_fastpath_lock(atomic_t *count, void (*fail_fn)(atomic_t *))
{
int __ex_flag, __res;
__asm__ (
"ldrex %0, [%2] \n\t" //完成__res=count->counter
"sub %0, %0, #1 \n\t" //完成__res=__res - 1
"strex %1, %0, [%2] " //完成用_res的当前值来更新count->counter
: "=&r" (__res), "=&r" (__ex_flag)
: "r" (&(count)->counter)
: "cc","memory" );
__res |= __ex_flag;
if (unlikely(__res != 0))
fail_fn(count);
}
红色的ldrex和strex是ARM架构下实现“读-更新-写回”原子操作的方式,其原理也是在操作过程中对bus进行监控,x86上则是利用带有LOCK前缀的方式来实现总线的监控,从而达到原子操作的方式。up的实现类似,在此不累述。其实上面的原理很简单就可以对应到我们现实生活中,想想看:首先,保安从对讲机或电脑中获知车位的情况,然后,发给等待停车位的车辆以停车卡,最后,更新车位的数量。这个过程在我们现实过程中是没有问题的,是我们认为的保证这个过程是原子的;若哪天保安犯了糊涂,发给停车卡后,忘了更新停车位的数量,那么最终一定会出现一个停车位多个车在抢的情况。而对应到内核中,准确的说应当是计算机体系结构中,唯一可以控制对一块内存的并发性访问的要点就是控制总线,所以,不同架构产生了不同的控制总线的指令,但其目的是相同的。
好了,让我们再次回到小明那里,小明今天的运气还不错,没等几分钟,就获得了停车卡,径直驶向停车场,下一次还会遇到哪些与内核中相似的机制呢?敬请期待。
阅读(2025) | 评论(0) | 转发(0) |