Linux设备驱动中的并发控制
产生竞态的情况:
(1)对称多处理器(SMP)的多个CPU
(2)单CPU的进程与抢占它的进程
(3)中断与进程之间
解决竞态途径:互斥访问
临界区:访问共享资源的代码区
互斥途径:中断屏蔽、原子操作、自旋锁、信号量、互斥体
中断屏蔽
local_irq_disable() /*屏蔽中断*/
...
critical section /*临界区 */
...
local_irq_enable() /*开中断 */
中断屏蔽只能禁止和使能本CPU内的中断,不能解决SMP多CPU的竞态问题,不推荐使用。
/*禁止中断的操作以外,保存目前 CPU 的中断位信息*/
local_irq_save(flags)
local_irq_restore(flags)
/*只禁止中断的底半部*/
local_bh_disable()
local_bh_enable()
原子操作
1.设置原子变量的值
void atomic_set(atomic_t *v, int i); //设置原子变量的值为 i
atomic_t v = ATOMIC_INIT(0); //定义原子变量 v 并初始化为 0
2.获取原子变量的值
atomic_read(atomic_t *v); //返回原子变量的值
3.原子变量加/减
void atomic_add(int i, atomic_t *v); //原子变量增加 i
void atomic_sub(int i, atomic_t *v); //原子变量减少 i
4.原子变量自增/自减
void atomic_inc(atomic_t *v); //原子变量增加 1
void atomic_dec(atomic_t *v); //原子变量减少 1
5.操作并测试
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);
上述操作对原子变量执行自增、自减和减操作后(注意没有加)测试其是否为 0,为 0 则返回 true,否则返回 false。
6.操作并返回
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 xxx_available = ATOMIC_INIT(1); /*定义原子变量,赋值为1*/ static int xxx_open(struct inode *inode, struct file *filp) { ... if (!atomic_dec_and_test(&xxx_available)) /*测试值为0则返回true否则为false*/ { atomic_inc(&xxx_available); return - EBUSY; /*已经打开*/ } ... return 0; /* 成功 */ } static int xxx_release(struct inode *inode, struct file *filp) { atomic_inc(&xxx_available); /* 释放设备 */ return 0; }
自旋锁
spinlock_t lock; /*定义*/
spin_lock_init(&lock); /*初始化*/
spin_lock (&lock) ; /*获取自旋锁,保护临界区*/
....../*临界区*/
spin_unlock (&lock) ; /*解锁*/
1 int xxx_count = 0;/*定义文件是否被打开*/ 2 static int xxx_open(struct inode *inode, struct file *filp) 3 { 4 ... 5 spinlock(&xxx_lock); 6 if (xxx_count)/*已经打开*/ 7 { 8 spin_unlock(&xxx_lock); 9 return - EBUSY; 10 } 11 xxx_count++;/*增加使用计数*/ 12 spin_unlock(&xxx_lock); 13 ... 14 return 0; /* 成功 */ 15 } 16 static int xxx_release(struct inode *inode, struct file *filp) 17 { 18 ... 19 spinlock(&xxx_lock); 20 xxx_count--; /*减少使用计数*/ 21 spin_unlock(&xxx_lock); 22 return 0; 23 }
xxx_count为一个全局变量,当一个进程获得自旋锁,count++,其他进程获取自旋锁是count值不为0,return - EBUSY
自旋锁与中断屏蔽衍生:
spin_lock_irq() = spin_lock() + local_irq_disable()
spin_unlock_irq() = spin_unlock() + local_irq_enable()
spin_lock_irqsave() = spin_unlock() + 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()
信号量
DECLARE_MUTEX(mount_sem);//定义并初始化信号量
down(&mount_sem);//获取信号量,保护临界区
...
critical section //临界区
...
up(&mount_sem);//释放信号量
初始化信号量的方法:
void sema_init (struct semaphore *sem, int val); //初始化信号量,并设置信号量 sem 的值为 val。尽管信号量可以被初始化为大于 1 的值从而成为一个计数信号量,但是它通常不被这样使用。
void init_MUTEX(struct semaphore *sem); //初始化一个用于互斥的信号量,它把信号量 sem 的值设置为 1,等同于sema_init (struct semaphore *sem, 1)。
void init_MUTEX_LOCKED (struct semaphore *sem); //初始化一个信号量,但它把信号量 sem 的值设置为 0,等同于sema_init (struct semaphore *sem, 0)。
此外,下面两个宏是定义并初始化信号量的“快捷方式”。
DECLARE_MUTEX(name)
DECLARE_MUTEX_LOCKED(name)
前者定义一个名为 name 的信号量并初始化为 1,后者定义一个名为 name 的信号量并初始化为 0。
1 static DECLARE_MUTEX(xxx_lock);//定义互斥锁 2 static int xxx_open(struct inode *inode, struct file *filp) 3 { 4 ... 5 if (down_trylock(&xxx_lock)) //获得信号量返回值为0 6 return - EBUSY; //设备忙 7 ... 8 return 0; /* 成功 */ 9 } 10 static int xxx_release(struct inode *inode, struct file *filp) 11 { 12 up(&xxx_lock); //释放打开锁 13 return 0; 14 }
自旋锁和信号量
进程在获取不到自旋锁时会在一个循环中重复“测试并设置”操作,即“自旋”,进程在获取不到信号量时会进入休眠状态等待。
当进程占用资源时间较长采用信号量,临界区访问时间较短时用自旋锁。