今天的内容回顾的是linux驱动开发中,并发控制的使用,那么问题来了,为什么我们需要进行并发控制?想要了解并发控制的话,那么我们先来认识一下什么是并发。
什么是并发呢?就是多个执行单元同时进行,但是这里要注意,我们所说的同时,只是宏观上面的同时,微观上还是有先后的顺序执行的。
说完并发,那么问题就接踵而至,同时对资源进行访问时,会产生什么情况呢?那就是传说中的竟态了,何谓竟态,就是并发执行的单元对共享资源的访问导致的竞争状态。
并发通常会发生在多CPU之间,单CPU之间进程间的并发,单CPU上进程和中断之间,还有单CPU上中断之间都会出现并发的情况。
现在,你大概知道了我们为什么要进行并发控制了,就是为了提高工作的效率。同时访问我们的共享资源的时候会出现一系列的问题,所以我们要进行相关的限制,那么并发控制有几种呢?
下面我们将对几种常见的并发控制机制进行相关的介绍:
常见的有以下几种:中断屏蔽,自旋锁,信号量,互斥锁,还有原子操作。
1.中断屏蔽
中断屏蔽不会产生进程调度,当前的进程会一直的执行,一般我们要求中断屏蔽的时间要尽可能的短,这样可以提高我们的效率,有些复杂的中断我们还将分为中断上半部和中断下半部,上半部放一些不能被打断执行的操作,下半部我们放一些可以被中断的操作,这样会使中断的利用率更高,关于中断的操作我会在之后继续的更新,这里只是简单的提一下。
如何在linux下实现中断屏蔽,我们经常使用以下几个函数来实现中断屏蔽:
//关闭中断 然后去访问共享的资源
local_irq_disable();
//使能中断
local_irq_enable();
//保存当前的CPSR值并且关闭中断 然偶访问共享资源
local_irq_save();
//恢复中断之前的状态
local_irq_restore();
2.自旋锁
自旋锁是一种满目等待的锁,使用自旋锁的时候,他会关闭抢占,此时不会有进程的调度,在多CPU使用自旋锁时,会有个全局的标志位来控制进程间的并发,使用自旋锁的时候不允许有关可以睡眠的函数使用,因为这样会很浪费我们的CPU资源,自旋锁使用的场合通常是代码量很少的场合,他会不断的在那里等待条件的满足。
如何实现自旋锁:
//定义自旋锁
spinlock_t lock;
//初始化自旋锁
void spin_lock_init(spinlock_t * lock);
//获得自旋锁
void spin_lock(spinlock_t *lock) ; 或者 int spin_trylock(spinlock_t *lock);//尝试获得锁,若获得返回真,否则返回假。
// ... ... 执行相关的操作
//释放自旋锁
void spin_unlock(spinlock_t *lock);
3.信号量(PV操作)
就是通常所说的PV操作,即申请资源和释放资源,他和自旋锁类似,只有得到信号量的进程,才可以访问共享资源,不同的是,当获取不到信号量时,信号量不会进行自旋的等待而是进入休眠等待状态,使用信号量时,当有些资源不能使用的时候,会引起进程的阻塞,我们需要注意的是,信号量不能使用在中断处理函数中。
如何实现信号量:
//定义信号量
struct semaphore sem;
//初始化信号量
sema_init(struct semaphore *sem,int value);
//获取信号量 有两种方式 先来说所第一种 如不能获取信号则进入不可中断的等待状态
void down(struct semaphore *sem);
//第二种获取信号量的方式 若不能获得信号量 进入可以被中断的等待状态
void down_interrupt(struct semaphore *sem);
//释放信号量 唤醒所有等待当前信号量的进程
void up(struct semaphore *sem);
4.互斥锁
互斥锁是一种睡眠锁,他比信号量的效率更高,它的使用方法和信号量一样,唯一不同的是,信号量常用于P,V操作,当然也可以用作互斥,而互斥锁只是用来进行互斥的。
如何使用互斥锁:
//定义互斥锁
struct mutex_lock mlock;
//初始化互斥锁
mutex_init(struct mutex_lock * lock);
//获得互斥锁
mutex_lock(struct mutex_lock * lock);
//释放互斥锁
mutex_unlock(struct mutex_lock *lock);
5.原子操作
原子操作一般针对的是单个变量值的修改,使用这些方式会让变量的值修改的时候由原子特性。我们经常会在linux内核中使用,他提供了好多的函数,有些是把修改变量和判断放在一个函数中去做,另外原子操作是不能被中断的操作。
如何实现原子操作:
//定义原子变量
typedef struct { int counter; } atomic_t;
atomic_t v;
//设置原子变量的值为 i
void atomic_set(atomic_t *v,int i);
//获得原子变量的值
#define atomic_read(v) (*(volatile int * )&(v)->counter)
//原子变量的加和减
void atomic_add(int i ,atomic_t *v); //原子变量的值加 i
void atomic_dec(int i ,atomic_t *v);//原子变量的值减i
//原子变量的自增与自jian
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 * 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);
好了,以上就是对linux下驱动中的并发控制的简单介绍,我们可以再闭上眼睛回忆一下都有哪些并发控制呢?
对的,回答对了,有中断屏蔽,自旋锁,信号量,互斥锁,还有原子操作。