🔥博客主页:PannLZ
🎋系列专栏:《Linux系统之路》
😘欢迎关注:👍点赞🙌收藏✍️留言
文章目录
- 并发解决
- 1.中断屏蔽
- 2.原子操作
- 2.1整形原子操作
- 2.2位原子操作
- 原子变量使用例子
并发解决
1.中断屏蔽
在单CPU范围内避免竞态的一种简单而有效的方法是在进入临界区之前屏蔽系统的中断,但是在驱动编程中不值得推荐,驱动通常需要考虑跨平台特点而不假定自己在单核上运行。
CPU一般都具备屏蔽中断和打开中断的功能,这项功能可以保证正在执行的内核执行路径不被中断处理程序所抢占,防止某些竞态条件的发生。具体而言,中断屏蔽将使得中断与进程之间的并发不再发生,而且,由于Linux内核的进程调度等操作都依赖中断来实现,内核抢占进程之间的并发也得以避免了。
中断屏蔽的使用方法为:
local_irq_disable() /* 屏蔽中断*/
. . .
critical section /* 临界区*/
. . .
local_irq_enable() /* 开中断*/
local_irq_disable(
)和local_irq_enable()
都只能禁止和使能本CPU内的中断,因此,并不能解决SMP多CPU引发的竞态。因此,单独使用中断屏蔽通常不是一种值得推荐的避免竞态的方法(换句话说,驱动中使用`local_irq_disable/enable()通常意味着一个bug),它适合与下文将要介绍的自旋锁联合使用。
2.原子操作
原子操作可以保证对一个整型数据的修改是排他性的。
Linux内核提供了一系列函数来实现内核中的原子操作,这些函数又分为两类,分别针对位和整型变量进行原子操作。位和整型变量的原子操作都依赖于底层CPU的原子操作,与CPU架构密切相关。
对于ARM处理器而言,底层使用
LDREX
和STREX
指令,比如atomic_inc()底层的实现会调用到atomic_add(),其代码如下:static inline void atomic_add(int i, atomic_t *v) { unsigned long tmp; int result; prefetchw(&v->counter); __asm__ __volatile__("@ atomic_add\n" "1: ldrex %0, [%3]\n" " add %0, %0, %4\n" " strex %1, %0, [%3]\n" " teq %1, #0\n" " bne 1b" : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter) : "r" (&v->counter), "Ir" (i) : "cc"); }
LDREX(Load with )用于读取内存中的值,并标记对该段内存的独占访问
STREX(Store with Exclusive Access)在更新内存数值时,会检查该段内存是否已经被标记为独占访问,并以此来决定是否更新内存中的值
STREX Rx, Ry, [Rz]
如果执行这条指令的时候发现已经被标记为独占访问了,则将寄存器Ry中的值更新到寄存器Rz指向的内存,并将寄存器Rx设置成0。指令执行成功后,会将独占访问标记位清除1。而如果执行这条指令的时候发现没有设置独占标记,则不会更新内存,且将寄存器Rx的值设置成1。(也就是将操作结果(成功0或失败1)写入Rx寄存器)
这两个指令的精髓就是,无论有多少个处理器,有多少个地方会申请对同一个内存段进行操作,保证只有最早的更新可以成功,这之后的更新都会失败。失败了就证明对该段内存有访问冲突了1。
BNE是一个条件跳转指令。如果上一条指令的结果不为零(即Z标志位为0),那么程序会跳转到标签1b所在的代码位置。在这里,1b是一个标签,b表示向后跳转,1表示跳转到第一个出现的这样的标签
2.1整形原子操作
//1.设置原子变量的值
void atomic_set(atomic_t *v, int i); /* 设置原子变量的值为i */
atomic_t v = ATOMIC_INIT(0); /* 定义原子变量v并初始化为0 */
//2.获取原子变量的值
atomic_read(atomic_t *v); /* 返回原子变量的值*/
//3.原子变量加/减
void atomic_add(int i, atomic_t *v); /* 原子变量增加i */
void atomic_sub(int i, atomic_t *v); /* 原子变量减少i */
//4.原子变量自增/自减
void atomic_inc(atomic_t *v); /* 原子变量增加1 */
void atomic_dec(atomic_t *v); /* 原子变量减少1 */
//5.操作并测试
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
//上述操作对原子变量执行自增、自减和减操作后(注意没有加),测试其是否为0,为0返回true,否则返回false。
//6.操作并返回
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
//上述操作对原子变量进行加/减和自增/自减操作,并返回新的值。
2.2位原子操作
//1.设置位
void set_bit(nr, void *addr);
//上述操作设置addr地址的第nr位,所谓设置位即是将位写为1。
//2.清除位
void clear_bit(nr, void *addr);
//上述操作清除addr地址的第nr位,所谓清除位即是将位写为0。
//3.改变位
void change_bit(nr, void *addr);
//上述操作对addr地址的第nr位进行反置。
//4.测试位
test_bit(nr, void *addr);
//上述操作返回addr地址的第nr位。
//5.测试并操作位
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);
//上述test_and_xxx_bit(nr,void*addr)操作等同于执行test_bit(nr,void*addr)后再执行xxx_bit(nr,void*addr)。
原子变量使用例子
使用原子变量使设备只能被一个进程打开
static atomic_t xxx_available = ATOMIC_INIT(1); /* 定义原子变量*/
static int xxx_open(struct inode *inode, struct file*filp)
{
...
if (!atomic_dec_and_test(&xxx_available)) {
atomic_inc(&xxx_available);
return - EBUSY; /* 已经打开*/
}
...
return 0; /* 成功*/
}
static int xxx_release(struct inode *inode, struct file*filp)
{
atomic_inc(&xxx_available); /* 释放设备*/
return 0;
}