为了解决多核心竞争临界资源问题,Linux提高多种方式用于处理竞态问题。
1. 整型原子操作
头文件:<linux/atomic-fallback.h>
- 设置原子变量的值
//设置原子变量的值为i
void atomic_set(atomic_t *v, int i);
//定义原子变量,并初始化为0
atomic_t v = ATOMIC_INIT(0);
- 获取原子变量的值
atomic_read(atomic_t *v);
- 原子变量加、减
void atomic_add(int i, atomic_t *v);
void atomic_sub(int i, atomic_t *v);
- 原子变量自增、自减
void atomic_inc(atomic_t *v);
void atomic_dec(atomic_t *v);
-
操作并测试
操作原子变量后,测试原子变量的值是否为0,为0返回true,否则返回false
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *c);
int atomic_add_and_test(int i, atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
-
操作并返回
操作原子变量后,返回原子变量的值
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);
示例:一个设备文件同时只能被一个进程打开,使用整形原子操作实现
//能被多个进程同时打开的数量
static atomic_t open_available = ATOMIC_INIT(1);
static int xxx_open(struct inode *inode, struct file *flip){
if(!atomic_read(&open_available)){
//可用数为0
return -EBUSY;
}
//可被打开,可用数减1
atomic_dec(&open_available);
}
static int xxx_release(struct inode *inode, struct file *filp){
//释放文件,可用数加1
stomic_inc(&open_available);
}
2. 位原子操作
- 设置位
//设置addr地址的第nr位为1
void set_bit(nr, void *addr);
- 清除位
//清除addr地址的第nr位为0
void clear_bit(nr, void *addr);
- 改变位
//addr地址的第nr位取反
void change_bit(nr, void *addr);
- 测试位
//返回第nr位的值
int test_bit(nr, void *addr);
- 测试并操作位
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);
3. 自旋锁
自旋锁基础功能
//定义自旋锁
spinlock_t lock;
//初始化自旋锁
spin_lock_init(&lock);
//获得自旋锁,获取成功立即返回,否则一直自选直到获取成功返回
spin_lock(&lock);
//尝试获得自旋锁,获取成功返回true,否则返回false,不进行自旋
spin_trylock(&lock);
//释放自旋锁
spin_unlock(&lock);
以上自旋锁基本功能可以解决的临界资源使用的问题
- 多核CPU
- 抢占式任务调度单核CPU
但是还可能受到中断和底半部中断的影响,以下是中断和底半部中断相关的操作
//开中断
local_irq_enable();
//关中断
local_irq_disable();
//关底半部
local_bh_disable();
//开底半部
local_bh_enable();
//关中断保存状态字
local_irq_save();
//开中断保存状态字
local_irq_restore();
从而衍生了自旋锁的整套机制
//spin_lock() + local_irq_disable()
spin_lock_irq();
//spin_unlock() + local_irq_enable()
spin_unlock_irq();
//spin_lock() + local_irq_save()
spin_lock_irqsave();
//spin_unlock() + local_irq_restore()
spin_unlock_irqrestore();
//spin_lock() + local_bh_disable()
spin_lock_bh();
//spin_unlock() + local_bh_enable()
spin_unlock_bh();
在多核编程中,如果进程或中断可能访问同一片临界资源
- 在进程上下文中调用
spin_lock_irqsave();
spin_unlock_irqrestore();
-
在中断上下文中调用
linux2.6后取消了中断嵌套,所以在中断上下文只需要spin_lock即可
spin_lock();
spin_unlock();
自旋锁应谨慎使用
- 自旋过程很消耗CPU资源
- 可能发生死锁
4. 读写自旋锁
比自旋锁粒度更小,可分别对读和写进行锁定
- 定义和初始化
//定义一个读写自旋锁
rwlock_t lock;
//初始化锁
rwlock_init(&lock);
- 读锁定
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);
- 写解锁
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);
对共享资源进行写之前,应先调用写锁定,完成后再调用写解锁
5. 顺序锁
- 顺序锁是对读写锁的一种优化
- 顺序锁的读执行单元不会写执行单元阻塞
- 但是若读过程中,进行了写,则需要重复读,重复读可能执行多次
- 其实现方式是,把数据存为一个副本用于读,同时增加一个标志位用于标志读过程中是否进行了写操作
- 获得顺序锁
void write_seqlock(seqlock_t *lock);
int write_tryseqlock(seqlock_t *lock);
write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);
write_seqlock_irq(seqlock_t *lock);
write_seqlock_bh(seqlock_t *lock);
- 释放顺序锁
void write_sequnlock(seqlock_t *lock);
write_sequnlock_irqrestore(seqlock_t *lock, flags);
write_sequnlock_irq(seqlock_t *lock);
write_sequnlock_bh(seqlock_t *lock);
- 读开始
unsigned int read_seqbegin(seqlock_t *lock);
read_seqbegin_irqsave(seqlock_t *lock, flags);
- 重读
int read_seqretry(seqlock_t *lock, unsigned int iv);
read_seqrettr_irqrestore(seqlock_t *lock, flags);
6. 读-复制-更新(RCU)
Read-Copy-Update
读没有锁
写操作时,先复制一个副本,对这个副本进行写操作,最后把数据的指针指向这个写操作后的副本,这样所有CPU都能获取到最新的数据
适用于大量读,少量写的场景
写操作多进程同步开销较大,读几乎无额外的开销
- 读锁定
rcu_read_lock();
rcu_read_lock_bh();
- 读解锁
rcu_read_unlock();
rcu_read_unlock_bh();
- 同步RCU
synchronize_rcu();
7. 信号量
信号量与进程的PV操作相对应
//定义信号量
struct semaphore sem;
//初始化信号量,设置初值
void sema_init(struct semaphore *sem, int val);
//获得信号量,阻塞直到获取成功
void down(struct semaphore *sem);
//获得信号量,阻塞直到获取成功,阻塞过程进程不会进入休眠,可以在中断中使用
int down_interrucptible();
//释放信号量
void up(struct semaphore *sem);
8. 互斥体
互斥体几乎和信号量一样
//定义互斥体
struct mutex mtx;
//初始化
mutex_init(struct mutex *mtx);
//获取互斥体
void metex_lock(struct mutex *mtx);
int mutex_lock_interruptible(struct mutex *mtx);
int mutex_trylock(struct mutex *mtx);
//释放互斥体
void mutex_unlock(struct mutex *mtx);
9. 完成量
Linux提高的完成量(Completion),用于一个执行单元等待另一个执行单元完成某件事情。
//定义完成量
struct completion cmp;
//初始化
void init_completion(completion *cmp);
void reinit_completion(completion *cmp);
//等待完成量
void wait_for_completion(completion *cmp);
//唤醒完成量
void complete(completion *cmp);
void complete_all(completion *cmp);