7---Linux设备驱动中的并发控制

基本概念

并发(concurrency)------指的是多个执行单元同时、并行被执行

竞态(race conditions)-------并发的多个执行单元对共享资源的不合顺序地访问,两个以上线程在同一个临界区内同时执行

共享资源-----硬件资源和软件上的全局变量、静态变量等

解决竞态问题-------保证共享资源互斥访问

互斥访问------指一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问

实现互斥访问------用中断屏蔽、原子操作、自旋锁和信号量等机制对临界区进行保护

临界区-----访问和操作共享资源的代码区域

 

中断屏蔽

进入临界区之前屏蔽系统的中断,屏蔽中断期间所有的中断都无法得到处理,可能造成数据丢失甚至系统崩溃,内核执行路径应当尽快地执行完临界区的代码。

local_irq_disable() //屏蔽中断
...
critical section //临界区
...
local_irq_enable() //开中断

原子操作

最小的操作单元,执行过程中不会被别的代码中断,针对位和整型变量

整型原子操作

//设置原子变量的值
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_dec(atomic_t *v); //原子变量减少1

//执行自增/自减操作,为0 则返回true,否则返回false。
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);

//进行自增/自减、加/减操作,返回新的值。
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);

位原子操作

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 位进行反置
test_bit(nr, void *addr);//返回addr 地址的第nr 位
//测试并操作位
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);

原子变量的使用实例,

作用:使设备最多只能被一个进程打开。

1 static atomic_t xxx_available = ATOMIC_INIT(1); /*定义原子变量*/
3 static int xxx_open(struct inode *inode, struct file *filp) {
5 ...
6 if (!atomic_dec_and_test(&xxx_available)) {
8 atomic_inc(&xxx_available);
9 return - EBUSY; /*已经打开*/
10 }
11 ...
12 return 0; /* 成功*/
13 }
14
15 static int xxx_release(struct inode *inode, struct file *filp){
17 atomic_inc(&xxx_available); /*释放设备*/
18 return 0;
19 }

https://blog.csdn.net/liu_sheng_1991/article/details/52291427

自旋锁

       作用:对临界资源进行互斥访问,对代码中的数据信息进行保护。

       工作方式:自旋锁最多只能被一个线程持有,如果某个线程试图获得已被持有(争用)的自旋锁时,该线程会进入忙循环---旋转---等待锁重新可用。如果锁未被争用,该线程会立即得到锁,继续执行。

  • 忙等待,无调度开销
  • 进程抢占被禁止,中断可打断
  • 锁定期间不睡觉
  • 同一时刻,只能被一个线程持有,同一时刻只能有一个线程处于临界区

     自旋锁相关的操作

spinlock_t spin;//定义
spin_lock_init(lock)//初始化 
spin_lock(lock)//获得自旋锁,如果得到即返回该自旋锁,否则,原地等待
spin_trylock(lock)//获得自旋锁,如果得到即返回真,否则,返回假
spin_unlock(lock)//释放


//定义一个自旋锁
spinlock_t lock;
spin_lock_init(&lock);
spin_lock (&lock) ; //获取自旋锁,保护临界区
...//临界区
spin_unlock (&lock) ; //解锁

使用自旋锁注意事项

  • 自旋锁实际上是忙等锁,当锁不可用时,CPU一直循环执行“测试并设置”该锁直到可用而取得该锁,CPU在等待自旋锁时不做任何有用的工作,仅仅是等待。因此,只有在占用锁的时间极短的情况下,使用自旋锁才是合理的。当临界区很大或有共享设备的时候,需要较长时间占用锁,使用自旋锁会降低系统的性能。
  • 自旋锁可能导致系统死锁。引发这个问题最常见的情况是递归使用一个自旋锁,即如果一个已经拥有某个自旋锁的CPU 想第二次获得这个自旋锁,则该CPU 将死锁。此外,如果进程获得自旋锁之后再阻塞,也有可能导致死锁的发生。copy_from_user()、copy_to_user()和kmalloc()等函数都有可能引起阻塞,因此在自旋锁的占用期间不能调用这些函数。

读写自旋锁

       读写自旋锁在写操作方面,只能最多有一个写进程,在读操作方面,同时可以有多个读执行单元(允许读的并发)。当然,读和写也不能同时进行

读写自旋锁涉及的操作

rwlock_t my_rwlock;/*定义*/
rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* 静态初始化*/
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);

rwlock_t lock; //定义rwlock
rwlock_init(&lock); //初始化rwlock
//读时获取锁
read_lock(&lock);
... //临界资源
read_unlock(&lock);
//写时获取锁
write_lock_irqsave(&lock, flags);
... //临界资源
write_unlock_irqrestore(&lock, flags);

顺序锁

       允许读写同时进行,因而更大地提高了并发性,但是,写执行单元与写执行单元之间仍然是互斥的,而且要求被保护的共享资源不含有指针。读执行单元在读操作期间,写执行单元已经发生了写操作,那么,读执行单元必须重新读取数据,以便确保得到的数据是完整的。

顺序锁操作

//获得写顺序锁
void write_seqlock(seqlock_t *sl);
int write_tryseqlock(seqlock_t *sl);
write_seqlock_irqsave(lock, flags)
//write_seqlock_irqsave() = loal_irq_save() + write_seqlock()
write_seqlock_irq(lock)
//write_seqlock_irq() = local_irq_disable() + write_seqlock()
write_seqlock_bh(lock)
//write_seqlock_bh() = local_bh_disable() + write_seqlock()

//释放写顺序锁
void write_sequnlock(seqlock_t *sl);
write_sequnlock_irqrestore(lock, flags)
//write_sequnlock_irqrestore() = write_sequnlock() + local_irq_restore()
write_sequnlock_irq(lock)
//write_sequnlock_irq() = write_sequnlock() + local_irq_enable()
write_sequnlock_bh(lock)
//write_sequnlock_bh() = write_sequnlock() + local_bh_enable()

//使用写顺序锁
write_seqlock(&seqlock_a);
//写操作代码块
write_sequnlock(&seqlock_a);

//读顺序锁开始
unsigned read_seqbegin(const seqlock_t *sl);
read_seqbegin_irqsave(lock, flags)
//read_seqbegin_irqsave() = local_irq_save() + read_seqbegin()

//重读
int read_seqretry(const seqlock_t *sl, unsigned iv);
read_seqretry_irqrestore(lock, iv, flags)
read_seqretry_irqrestore() = read_seqretry() + local_irq_restore()

//使用读顺序锁
do {
seqnum = read_seqbegin(&seqlock_a);
//读操作代码块
...
} while (read_seqretry(&seqlock_a, seqnum));

 信号量(semaphore)

用于保护临界区的一种常用方法,它的使用方式和自旋锁类似。与自旋锁相同,只有得到信号量的进程才能执行临界区代码。但是,与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。

工作方式:如果一个任务试图获得一个不可用(被占用)的信号量时,信号量会将其推进一个等待队列,令其睡眠。这时,CPU重获自由转去执行其他代码。当信号量可用(被释放),处于等待队列的那个任务被唤醒,并获得该信号量。

  • 拿不到就切换进程,有调度开销,CPU利用率高
  • 锁定期间可以睡觉,不用于中断上下文

信号量相关的操作

//定义信号量
struct semaphore sem;

//初始化信号量
void sema_init (struct semaphore *sem, int val);//初始值为sem=val
void init_MUTEX(struct semaphore *sem);//互斥信号量 sem=1
void init_MUTEX_LOCKED (struct semaphore *sem);//同步信号量 sem=0
DECLARE_MUTEX(name)//宏定义初始值为1,用于互斥
DECLARE_MUTEX_LOCKED(name)//宏定义初始值为0,用于同步

//获得信号量
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);//释放信号量sem,唤醒等待者

//使用信号量
DECLARE_MUTEX(mount_sem);//定义信号量
down(&mount_sem);//获取信号量,保护临界区
...
critical section //临界区
...
up(&mount_sem);//释放信号量

完成量

是一种比信号量更好的同步机制,它用于一个执行单元等待另一个执行单元执行完某事。

struct completion my_completion;//定义完成量
init_completion(&my_completion);//初始化完成量
DECLARE_COMPLETION(my_completion);//定义并初始化完成量
void wait_for_completion(struct completion *c);//等待完成量被唤醒
void complete(struct completion *c);//唤醒一个等待的执行单元
void complete_all(struct completion *c);//唤醒等待这个完成量的所有执行单元

读-拷贝-更新(RCU)

对于被RCU保护的共享数据结构,读执行单元不需要获得任何锁就可以访问它

使用RCU的写执行单元在访问它前需首先复制一个副本,然后对副本进行修改,最后使用一个回调机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据,这个时机就是所有引用该数据的CPU 都退出对共享数据的操作的时候。

读执行单元没有任何同步开销,而写执行单元的同步开销则取决于使用的写执行单元间的同步机制。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值