Linux并发控制

本文详细介绍了Linux内核中用于处理多核心CPU环境下临界资源竞争的多种并发控制机制,包括原子操作、位原子操作、自旋锁、读写自旋锁、顺序锁、读-复制-更新(RCU)、信号量、互斥体和完成量。这些机制确保了对共享资源的安全访问,防止数据不一致性和死锁的发生。在多线程和中断处理的上下文中,正确使用这些同步原语至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为了解决多核心竞争临界资源问题,Linux提高多种方式用于处理竞态问题。

1. 整型原子操作

头文件:<linux/atomic-fallback.h>

  1. 设置原子变量的值
   //设置原子变量的值为i
   void atomic_set(atomic_t *v, int i);
   //定义原子变量,并初始化为0
   atomic_t v = ATOMIC_INIT(0);
  1. 获取原子变量的值
atomic_read(atomic_t *v);
  1. 原子变量加、减
   void atomic_add(int i, atomic_t *v);
   void atomic_sub(int i, atomic_t *v);
  1. 原子变量自增、自减
   void atomic_inc(atomic_t *v);
   void atomic_dec(atomic_t *v);
  1. 操作并测试

    操作原子变量后,测试原子变量的值是否为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);
  1. 操作并返回

    操作原子变量后,返回原子变量的值

   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. 位原子操作

  1. 设置位
   //设置addr地址的第nr位为1
   void set_bit(nr, void *addr);
  1. 清除位
  //清除addr地址的第nr位为0
  void clear_bit(nr, void *addr);
  1. 改变位
   //addr地址的第nr位取反
   void change_bit(nr, void *addr);
  1. 测试位
   //返回第nr位的值
   int test_bit(nr, void *addr);
  1. 测试并操作位
   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);

以上自旋锁基本功能可以解决的临界资源使用的问题

  1. 多核CPU
  2. 抢占式任务调度单核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();

在多核编程中,如果进程或中断可能访问同一片临界资源

  1. 在进程上下文中调用
   spin_lock_irqsave();
   spin_unlock_irqrestore();
  1. 在中断上下文中调用

    linux2.6后取消了中断嵌套,所以在中断上下文只需要spin_lock即可

   spin_lock();
   spin_unlock();

自旋锁应谨慎使用

  1. 自旋过程很消耗CPU资源
  2. 可能发生死锁

4. 读写自旋锁

比自旋锁粒度更小,可分别对读和写进行锁定

  1. 定义和初始化
   //定义一个读写自旋锁
   rwlock_t lock;
   //初始化锁
   rwlock_init(&lock);
  1. 读锁定
   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);
  1. 读解锁
   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);

对共享资源进行读取之前,应先调用读锁定,完成后再调用读解锁

  1. 写锁定
   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);
  1. 写解锁
   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. 顺序锁

  1. 顺序锁是对读写锁的一种优化
  2. 顺序锁的读执行单元不会写执行单元阻塞
  3. 但是若读过程中,进行了写,则需要重复读,重复读可能执行多次
  4. 其实现方式是,把数据存为一个副本用于读,同时增加一个标志位用于标志读过程中是否进行了写操作
  1. 获得顺序锁
   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);
  1. 释放顺序锁
   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);
  1. 读开始
   unsigned int read_seqbegin(seqlock_t *lock);
   read_seqbegin_irqsave(seqlock_t *lock, flags);
  1. 重读
   int read_seqretry(seqlock_t *lock, unsigned int iv);
   read_seqrettr_irqrestore(seqlock_t *lock, flags);

6. 读-复制-更新(RCU)

Read-Copy-Update

读没有锁

写操作时,先复制一个副本,对这个副本进行写操作,最后把数据的指针指向这个写操作后的副本,这样所有CPU都能获取到最新的数据

适用于大量读,少量写的场景

写操作多进程同步开销较大,读几乎无额外的开销

  1. 读锁定
   rcu_read_lock();
   rcu_read_lock_bh();
  1. 读解锁
   rcu_read_unlock();
   rcu_read_unlock_bh();
  1. 同步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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值