简单地讲,竞争冒险可以用如下的例子来说明,当两个线程都需要对某一临界资源进行操作时:
Thread A:
while(0 == semaphore)
{
/* 问题点A */
semaphore = 1;
}
/* 后续对共享资源的处理 */
Thread B:
while(0 == semaphore)
{
/* 问题点B */
semaphore = 1;
}
/* 后续对共享资源的处理 */
假设线程A运行到问题点A,此时正好发生了线程的切换,线程B获得了执行权,此时semaphore的值还是为0,于是线程B认为自己获得对共享资源的控制,而之后一旦线程A获得执行权,又会认为自己也获得了执行权,于是就出现了对共享资源的竞争冒险。
而解决这种竞争冒险的问题,一种办法是应用图灵奖获得者Edsger Dijkstra设计的软件信号量,一种办法是使用体系结构提供的类似于test and increase这样的原子操作,还有一种办法稍微傻点(当初我刚做arm的时候不知道swp这条指令),于是就用了关中断-获取变量-修改变量-开中断这种方法,在单核的cpu中也能实现类似于信号量的操作,但是在多核系统中完全失去作用了。
在ARMv6之前信号量的实现由SWP和SWPB指令来实现,而在ARMv6及之后的kernel中则推荐使用LDREX和STREX命令对来实现信号量。
ARM体系结构提供的原子操作就是SWP(B),SWP的语法如下:
SWP rd,rm,[rs]
这条语句会将总线锁住,完成如下两步操作:
1、 rd = [rs]
2、 [rs] = rm
也许你会问,这怎么实现信号量操作啊?请看如下示例代码:
sem_lock;
swp rd,rm,[rs]
cmp rd,#0x0
jne sem_lock
比如说锁打开的时候为0,关闭为1,那么如果在锁打开时,执行sem_lock就会使rd = 0,而锁的值变为1,实现了关锁。因为swp是原子操作,所以不用担心执行过程中锁的值放生改变。
而如果此时锁为关闭状态,那么执行sem_lock就会是rd的值为1,软件此时就应该认为获取锁失败,不断循环以等待其持有者释放。
在新的架构中使用LDREX和STREX对来实现信号量,关于这两个指令比较复杂,需要更多的时间去讨论和理解,这两个指令的一个好处是从架构级别对共享资源进行保护,而不是以前的那种需要软件协商的方式,在swp这种模式中,只要有一个线程不遵守规则,那么一切保护都将成为空谈,而LDREX/STREX方式中,可以由硬件机制作出一定程度的保证。
|