**
并发与竞争
**
一.简介
linux系统是一个多任务的操作系统,存在多个任务同时访问同一片内存区域,这些任务可能会相互影响覆盖内存中的数据,造成内存数据混乱。这个问题会导致系统panic。但总结一下有以下几个原因:
a. 多线程并发访问
b.内核抢占试访问
c.中断程序并发访问
d.smp多核间并发访问
并发访问带来的问题就是竞争。如果多个线程同时操作临界区的公共资源,线程之间就存在竞争关系,为了防止出现以上问题,我们在编写去佛那个的时候要考虑到并发与竞争。
二.作用
保护共享资源,防止并发访问。比如一个驱动设备的结构体,也比如环形缓冲。保护的一般是全局变量,设备结构体。
三:方式
1. 原子操作
是一种不能在分割、拆分的一种操作,一般用来作用与变量,或者位操作。比如a=3,经过编译成汇编后,如下;
LDR R0,=0x30
LDR R1, = 3
STR R1.[R0]
如果在加一个线程,也像a中写入10,在执行时,可能会出现汇编交叉,和我们理想流程不一致。
1->原子整形API函数
atomic_t a=ATOMIC_INIT(0);//定义原子变量a,并初始化为0
atomic_set(&a,10);//设置a=10;
atomic_read(&a);//读取a的值;
atomic_inc(&a);//a+1;a=11;
2->原子位操作
不同与atomic_t,没有数据结构体,只能直接对内存操作。
2.自旋锁
原子锁只能作用于变量或者bit保护,对于复杂的临界区,比如设备结构体变量,原子操作就不能胜任了。
当一个线程要访问某个资源,首先要获取对应的锁,锁只能被一个线程持有,只要此线程不释放持有的锁,那么其他线程就不能获取此锁。对于自旋锁,如果被A持有,线程B就会处于循环–旋转–等待状态,B线程不会进入休眠状态(或者)说做其他处理,就一直转圈。
缺点:从上来看,B线程会一直处于自选状态,这样会浪费时间,降低系统的性能;所以自旋锁的持有时间不能太长,只适合轻量级加锁,如果需要更长时间的场景,需要换其他方法。
1-> api
spainlock_t lock 定义自旋锁
spin_lock_init 初始化自旋锁
spin_lock 加锁
spin_unlock 释放锁
注意事项:
a.自旋锁在使用期间一定不能调用任何引起休眠和阻塞的api函数,否则会出现死锁;
b.会自动禁止抢占。
c. 如果线程A 持有自旋锁,发生中断,中断抢走cpu控制权,中断也要获得自旋锁,但这个锁被A占用,中断就会一直自旋,等待锁有效。在中断不执行玩,线程A是不可能执行的。会出现僵死的现象。
2-> spin_lock_irq //禁止本地中断,并获取自旋锁。
spin_unlock_irq // 激活本地中断,并释放自旋锁。
spin_lock_irqsave //保护中断状态,禁止本地中断,并获取自旋锁。
spin_unclick_Irqrestore// 将中段恢复到之前的状态, 激活本地中断,并释放自旋锁
3.信号量
信号量是kernl的一种信号同步的机制,常常用于对共享资源的访问,能够提高处理器的使用效率。
特点:
a.相比于自旋锁,信号量可以使线程进入睡眠状态。
b. 不能用于中断,因为信号量会引起休眠,中断不能休眠。
c.信号量需要休眠,并且切换线程,开销大于优势。
1->api
struct semaphore sem;//定义信号量
sema_init(&sem,1); 申请信号量
down(&sem);申请信号量
up(&sem); 释放信号量
4.互斥锁
将信号量的值设为1就可以使用信号量进行互斥,互斥体是linux内核提供比信号量更专业的机制来进行互斥。
互斥体表示一次只能有一个线程来访问共享资源,不能递归申请互斥体。
注意事项:
a. mutex 可以导致休眠,不能用于中断
b. 和信号量一样,mutex保护的临界区可以调用引起api阻塞的函数
c. 不能递归上锁和解锁。
1->api 函数
struct mutex lock;//定义互斥体
mutex_init(&lock); // 初始化互斥体
mutex_lock(&lock); //上锁
mutex_unlock(&lock); //解锁