一、并发和竞态场景
(1)对称多处理器的多CPU
(2)单CPU内进程与抢占它的进程
(3)中断(硬中断,软中断,Tasklet,底半部)与进程之间
二、并发和竞态根因
1、编译乱序
高性能编译器具有乱序优化能力,以提升Cache命中率和CPU的Load/Store单元的工作效率。
2、执行乱序
高级处理器具有“乱序执行”策略,以提高Cache命中率。
三、并发和竞态解决方案
1、中断屏蔽
1.1 使用场景:只能解决单核中断相关并发问题。
1.2 常用操作:
local_irq_disable() //屏蔽中断
local_irq_enable() //开启中断
local_irq_save(flags) //屏蔽中断并保存CPU的中断位信息(CPSR)
local_irq_restore(flags) //开启中断并恢复CPU的中断位信息(CPSR)
1.3 使用模板:
local_irq_disable() //屏蔽中断
... //临界区代码
local_irq_enable() //开启中断
2、原子操作
2.1 使用场景:保证对一个整形数据的修改是排他的。
2.2 常用操作
(1)整形原子操作:
void atomic_set(atomic_t *v,int i); //设置原子变量的值为i
atomic_t v= ATOMIC_INIT(0); //定义原子变量v并初始化为0
atomic_read(atomic_t *v); //返回原子变量的值
void atomic_add(int i,atomic_t *v); //原子变量加i
void atomic_sub(int i,atomic_t *v); //原子变量减i
void atomic_inc(atomic_t *v); //原子变量加1
void atomic_sub(atomic_t *v); //原子变量减1
int atomic_inc_and_test(atomic_t *v); //自增后测试是否为0,为0返回true,非0返回false
int atomic_dec_and_test(atomic_t *v); //自减后测试是否为0,为0返回true,非0返回false
int atomic_sub_and_test(int i,atomic_t *v); //减去i后测试是否为0,为0返回true,非0返回false
int atomic_add_return(int i,atomic_t *v); //增加i后返回新的值
int atomic_sub_return(int i,atomic_t *v); //减去i后返回新的值
int atomic_inc_return(int i,atomic_t *v); //自增1后返回新的值
int atomic_dec_return(int i,atomic_t *v); //自减1后返回新的值
(2)位原子操作:
void set_bit(nr,void *addr); //设置addr地址的第nr位为1
void clear_bit(nr,void *addr); //清空addr地址的第nr位为0
void change_bit(nr,void *addr); //反转addr地址的第nr位,1->0,0->1
test_bit(nr,void *addr); //返回addr地址的第nr位
int test_and_set_bit(nr,void *addr); //返回地址addr的第nr位之后再设置其为1
int test_and_clear_bit(nr,void *addr); //返回地址addr的第nr位之后再清空其为0
int test_and_change_bit(nr,void *addr); //返回地址addr的第nr位之后再对其进行翻转
2.3 使用模板:
static atomic_t xxx_available=ATOMIC_INIT(1); //定义原子变量
static int xxx_open(stuct inode *inode,struct file *filp)
{
...
if(!atomic_dec_and_test(&xxx_available)) //将原子变量减1后测试是否为0,为0返回true,否则返回false
{
atomic_inc(&xxx_available); //返回非0,表示后续代码有其他进程占用,恢复原子变量后退出
return - EBUSY;
}
...
return 0;
}
static int xxx_release(struct inode *inode,struct file *filep) //恢复原子变量的值
{
atomic_inc(&xxx_available);
return 0;
}
3、自旋锁
3.1 使用场景:使用自旋锁,可能会降低性能;可能导致死锁;锁定期间,不能调用引起进程调度的函数;若进程中使用了自旋锁,则中断服务程序中需要使用spine_lock();
3.2 分类
3.2.1 普通自旋锁(读和读互斥,读和写互斥,写和写互斥)
(1)常用操作
spinlock_t lock; //定义自旋锁
spin_lock_init(lock); //初始化自旋锁
spin_lock(lock); //获得自旋锁
spin_trylock(lock); //尝试获得自旋锁,如能立即获得锁,获得锁并返回true,否则返回false,返回false后不再原地打转
spin_unlock(lock); //释放自旋锁
(2)使用模板
(2.1)普通自旋锁
spinlock_t lock; //定义一个自旋锁
spin_lock_init(&lock); //初始化一个自旋锁
spin_lock(&lock); //获取自旋锁,进入代码临界区
...
spin_unlock(&lock); //解锁
(2.2)使用自旋锁使得设备只能被单进程打开
int xxx_count=0 //定义引用计数
static int xxx_open(stuct inode *inode,struct file *filp)
{
...
spin_lock(&xxx_lock); //加锁
if(xxx_count) //若引用计数不为0,即后续代码有其他进程占用
{
spin_unlock(&xxx_lock); //释放锁
return - EBUSY;
}
xxx_count++;
spin_unlock(&xxx_lock);
...
return 0;
}
static int xxx_release(struct inode *inode,struct file *filep) //恢复原子变量的值
{
...
spinlock(&xxx_lock);
xxx_count--;
spin_unlock(&xxx_lock);
return 0;
}
3.2.2 考虑中断介入的自旋锁(读和读互斥,读和写互斥,写和写互斥)
(1)常用操作
spin_lock_irq()=spin_lock()+local_irq_disable(); //获取自旋锁+屏蔽中断
spin_unlock_irq()=spin_unlock()+local_irq_enable(); //释放自旋锁+使能中断
spin_lock_irqsave()=spin_lock()+local_irq_save(); //获取自旋锁+屏蔽中断后存储中断位
spin_unlock_irqrestore()=spin_unlock()+local_irq_restore(); //释放自旋锁+使能中断后恢复中断位
spin_lock_bh()=spin_lock()+local_bh_disable(); //获取自旋锁+屏蔽底半部中断
spin_unlock_bh()=spin_unlock()+local_bh_enable(); //获取自旋锁+使能底半部中断
(2)使用模板
同3.2.1(2)略
3.2.3 读写自旋锁(读和读不互斥,读和写互斥,写和写互斥)
(1)常用操作
rwlock_t my_rwlock; //定义读写锁
rwlock_init(&my_rwlock); //动态初始化
//读锁定
void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock,unsigned long flags);
void read_lock_irq(rwlock_t *lock);
void read_lock_bh(rwlock_t *lock);
//读解锁
void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock,unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);
//写锁定
void write_lock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock,unsigned long flags);
void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
int write_trylock(rwlock_t *lock);
//写解锁
void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock,unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);
(2)使用模板
rwlock_t lock; //定义锁
rwlock_init(&lock); //初始化锁
read_lock(&lock); //获取读锁
… //代码临界区
read_unlock(&lock); //释放读锁
write_lock_irqsave(&lock,flags); //获取写锁
… //代码临界区
write_unlock(&lock); //释放写锁
3.2.4 顺序锁(读和读不互斥,读和写不互斥,写和写互斥)
说明:如果在读执行单元执行期间,写执行单元已经发生了写操作,那么,读执行单元必须重新获取数据,以确保得到的数据是完整的。
(1)常用操作
(1.1)写执行单元
//获得顺序锁
void write_seqlock(seqlock_t *sl);
int write_tryseqlock(seqlock_t *sl);
write_seqlock_irqsave(lock,flags)=local_irq_save()+write_seqlock()
write_seqlock_irq(lock)=local_irq_disable()+write_seqlock()
write_seqlock_bh(lock)=local_bh_disable()+write_seqlock()
//释放顺序锁
void write_sequnlock(seqlock_t *s1);
write_sequnlock_irqrestore(lock,flags)=local_irq_restore()+write_sequnlock()
write_sequnlock_irq(lock)=local_irq_enable()+write_sequnlock()
write_sequnlock_bh(lock)=local_bh_enable()+write_sequnlock()
(1.2)读执行单元
//读开始
unsigned read_seqbegin(const seqlock_t *sl);
read_seqbegin_irqsave(lock,flags)=local_irq_save()+read_seqbegin();
//重读
int read_seqretry(const seqlock_t *sl,unsigned iv);
read_seqretry_irqresotre(lock,iv,flags)=read_seqretry()+local_irq_restore();
(2)使用模板
(2.1)写执行单元使用顺序锁
write_seqlock(&seqlock_a); //获取写锁
... //代码临界区
write_sequnlock(&seqlock_a); //获取写锁
(2.2)读执行单元使用顺序锁
do{
seqnum=read_seqbegin(&seqlock_a);
}while(read_seqretry(&seqlock_a,seqnum));
3.2.5 R-C-U(读-复制-更新,读和读不互斥,读和写不互斥且开销小,写和写互斥且开销大)
(1)常用操作
(1.1)使用rcu读
//读锁定
rcu_read_lock()
rcu_read_lock_bh()
//读解锁
rcu_read_unlock()
rcu_read_unlock_bh()
(1.2)使用rcu写(挂接回调)
//同步rcu,指导当前CPU上所有的读执行单元完成读临界区后,写执行单元才可以执行下一步操作
synchronize_rcu();
//挂接回调
void call_rcu(struct rcu_head *head,void (*func)(struct rcu_head *rcu)); //将函数挂接在rcu回调函数链上,然后立即返回。挂接的回调函数会在一个宽限期结束后执行。
rcu_assign_pointer(p,v); //给rcu保护的指针赋值
rcu_deference(p); //获取rcu保护的指针,之后读端只能在read_lock()/read_unlock()中的代码临界区引用该指针
rcu_access_pointer(p); //读端获取指针p的值
(1.3)rcu保护的链表操作
static inline void list_add_rcu(struct list_head *new,struct list_head *head); //将new插入rcu保护的链表head的开头
static inline void list_tail_rcu(struct list_head *new,struct list_head *head); //将new插入rcu保护的链表head的末尾
static inline void list_del_rcu(struct list_head *entry); //删除rcu保护的指定的链表中的元素entry
static inline void list_replace_rcu(struct list_head *old,struct list_head *new); //使用新的链表元素new替换旧的链表元素old
list_for_each_entry(pos,head); //遍历rcu保护的链表head
(2)使用模板
(2.1 )使用rcu进行读
rcu_read_lock() //获取读锁
... //代码临界区
rcu_read_unlock() //释放读锁
(2.2) 使用rcu进行写(挂接回调)
//写端,视作发布gp
struct foo{
int a;
int b;
int c;
};
struct foo *gp=NULL;
p=kmalloc(size(*p),GFP_KERNEL);
p->a=1;
p->b=2;
p->c=3;
rcu_assign_pointer(gp,p);
//读端,视作订阅gp
rcu_read_lock();
p=rcu_deference(p);
if(p!=NULL)
{
do_something_with(p->a,p->b,p->c);
}
rcu_read_unlock();
(2.3)RCU保护的链表操作
//写端,视作发布gp
struct foo{
struct list_head_list;
int a;
int b;
int c;
};
LIST_HEAD(head);
struct foo *gp=NULL;
p=kmalloc(size(*p),GFP_KERNEL);
p->a=1;
p->b=2;
p->c=3;
list_add_rcu(&p->list,&head);
//读端,视作订阅gp
rcu_read_lock();
list_for_each_entry_rcu(p,head,list){
do_something_with(p->a,p->b,p->c);
}
rcu_read_unlock();
4、信号量
4.1 使用场景
(1)如果信号量S的值大于0,该进程继续进行
(2)如果信号量S的值等于0,将该进程置位等待状态,排入信号量的等待队列,知道V操作唤醒之
4.2 常用操作
struct semaphore sem; //定义一个信号量sem
void sema_init(struct semaphore *sem,int val); //初始化信号量
void down(struct semaphore * sem); //获取信号量,会导致睡眠,不能用在中断中
int down_interruptible(struct semaphore * sem); //获取信号量,导致进程睡眠,睡眠会被信号打断,信号也会导致该函数返回非0
int down_trylock(struct semaphore *sem); //尝试获得信号量,如果能够获得,则获得信号量并返回0,否则返回非0,不会导致睡眠,可以在中断中使用
void up(struct semaphore * sem); //释放信号量,唤醒等待者
4.3 使用模板
类似自旋锁,略。
5、互斥体
5.1 使用场景:用于多个进程之间对资源的互斥。适用于进程切换开销较大的场景。不适用中断/软中断场景(因为互斥体只存在于进程上下文)。
5.2 常用操作
void mutex_lock(struct mutex *lock); //获取互斥体,会导致睡眠,不能用在中断中
int mutex_lock_interruptible(struct mutex *lock); //获取互斥体,导致进程睡眠,睡眠会被信号打断,信号也会导致该函数返回非0
int mutex_trylock(struct mutex *lock); //尝试获取互斥体,获取不到mutex互斥体的时候不会引起睡眠
void mutex_unlock(struct mutex *look); //释放互斥体
5.3 使用模板:
struct mutex my_mutex; //定义互斥体
mutex_init(&my_mutex); //初始化互斥体
mutex_lock(&my_mutex); //获取互斥体
... //代码临界区
mutex_unlock(&my_mutex); //释放互斥体
6、完成量
6.1 使用场景:用于一个执行单元等待另一个执行单元执行完某事
6.2 常用操作
stuct completion my_completion; //定义完成量
init_completion(&my_completion); //初始化完成量
reinit_completion(&my_completion); //重新初始化完成量的值为0
void wait_for_completion(struct completion *c); //等待完成量
void complete(struct completion *c); //唤醒完成量
void complete_all(struct completion *c); //唤醒完成量