竞争和并发
造成并发的情况?
1、中断程序并发访问
2、抢占式并发访问。2.6版本后Linux支持抢占,正在执行的进程随时可以被抢占
3、多处理器并发访问。多核处理器之间存在并发访问。
发生并发时要保护什么?
1、需要保护共享资源,共享资源是:全局变量,或者是驱动的设备结构体
原子操作:
描述
原子操作是指该操作在执行完之前不会被任何事件打断;
其实就是用一个原子整型变量;
需要包含头文件:<linux/atomic.h>
原子整型API:
例
#include <linux/atomic.h>
//定义一个原子变量,并初始化;
static atomic64 v= ATOMIC_INIT(1);
static int flag=1;
static int cdev_test_open(struct inode *node, struct file *file)
{
//自减并判断
if(! atomic64_dec_and_test(&v))
{
atomic64_inc(&v);
return -1;
}
flag =0;
spin_unlock(&spinlock);
printk("cdev_test_open \n");
return 0;
}
static int cdev_test_release(struct inode *node, struct file *file)
{
atomic64_inc(&v);
flag =1;
spin_unlock(&spinlock);
printk("cdev_test_release \n");
return 0;
}
自旋锁 spinlock:
为了保护共享资源提出的一种锁机制,内核中比较常见,已原地等待的方式解决资源冲突,当资源被占用后,其他线程获取不到,只能原地打转。
需要包含头文件:<linux/spinlock.h>
API:
使用步骤:
1、申请自旋锁
2、进入临界区
3、退出临界区释放自旋锁
注意事项:
1、自旋锁里的临界区代码不能太多,会占用系统资源
2、临界区不能使用休眠的函数
3、终端中也可以使用自旋锁;
自旋锁的死锁
在多核CPU 或者支持抢占的系统中,被自锁的临界区不能调用任何能够引起睡眠或者阻塞的函数否则容易发生死锁;
使用自旋锁会禁止抢占,单核CPU中 A进程获得自旋锁,并且进入休眠状态,B进程也想获得自旋锁,但此时被A占用 获取不到,CPU的抢占被禁止了,进程B无法调度出去,A进程就会一直不能释放自旋锁 就导致 死锁的产生;
当进程A获得自旋锁,如果产生中断,并且在中断里也要访问共享资源,此时中断无法获得自选锁 只能等待,产生死锁;可以使用spin_lock_irqsave来禁止中断并获取 自旋锁;
如何避免死锁
1、如果在中断里使用自旋锁 使用函数 spin_lock_irqsave 或者spin_unlock_irqsave函数来申请,防止执行临界区里的代码被中断打断
2、避免在一个函数里多次获得自旋锁
3、临界区代码不能太长。不能休眠、不能阻塞;
例
#include <linux/spinlock.h>
static struct spinlock_t spinlok;
static int flag=1;
static int cdev_test_open(struct inode *node, struct file *file)
{
spin_lock(&spinlock);
if(flag != 1){
spin_unlock(&spinlock);
return -1;
}
flag =0;
spin_unlock(&spinlock);
printk("cdev_test_open \n");
return 0;
}
static int cdev_test_release(struct inode *node, struct file *file)
{
spin_lock(&spinlock);
if(flag != 1){
spin_unlock(&spinlock);
return -1;
}
flag =1;
spin_unlock(&spinlock);
printk("cdev_test_release \n");
return 0;
}
信号量:
描述
信号量是调用者获取不到资源时,调用者将会休眠;可以在临界区时间比较长时使用,信号量又叫睡眠锁;
信号量本质时一个全局变量;当有线程访问资源时信号量减一;访问完 加1;
需要包含头文件:<linux/semaphore.h>
API
注意事项:
1、信号量的值不能小于0
2、访问共享资源时,信号量执行减一操作,退出共享资源,执行加1
3、信号量为0时,访问共享资源的线程必须等待,直到信号量大于0
4、信号量会引起休眠,中断里不能使用信号量
5、共享资源持有的时间比较久,一般使用信号量而不是自旋锁
6、在同时使用信号量和自旋锁时,要先获取信号量 再使用自旋锁,因为信号量会导致休眠,所以不能放在自旋锁的后面 ;
例
#include <linux/semaphore.h>
//定义一个信号量
static struct semaphore semlock;
static int flag=1;
static int cdev_test_open(struct inode *node, struct file *file)
{
//down(&semlock);
//可以被打断
if(down_interrupt(&semlock))
{
return -1;
}
printk("cdev_test_open \n");
return 0;
}
static int cdev_test_release(struct inode *node, struct file *file)
{
up(&semlock);
printk("cdev_test_release \n");
return 0;
}
static int cdev_init (void)
{
//初始化信号量
sema_init(&semlock,1);
return 0;
}
互斥锁:
同一个资源同一时间只能有一个访问者访问,其他的访问者只能等访问结束才能访问,这就是互斥,实现这种共呢个叫做互斥锁;
互斥锁和信号量值为1的情况很类似,但是互斥锁更简洁高效;
需要包含头文件 <linux/mutex.h>
API:
注意事项:
1、互斥锁会导致休眠,不能在中断里使用;
2、同一时刻只能有一个线程持有互斥锁,并且只有持有者可以解锁
3、不允许递归上锁和解锁
例
#include <linux/mutex.h>
//定义一个信号量
static struct mutex mutexlock;
static int flag=1;
static int cdev_test_open(struct inode *node, struct file *file)
{
mutex_lock(&smutexlock);
//可以被打断
if(down_interrupt(&semlock))
{
return -1;
}
printk("cdev_test_open \n");
return 0;
}
static int cdev_test_release(struct inode *node, struct file *file)
{
mutex_unlock(&smutexlock);
printk("cdev_test_release \n");
return 0;
}
static int cdev_init (void)
{
//初始化信号量
mutex_init(&smutexlock);
return 0;
}