1、Linux中的并发与竞争
Linux设备驱动中必须解决的一个问题:多进程对共享资源的并发问题,
多个进程对同一个设备的并发访问,势必会导致对该设备资源的竞争。
竞态发生的几种情况:
1)、对称多处理器的多个CPU;
2)、单CPU内进程与抢占它的进程;
2)、中断(硬中断、软中断、tasklet、底半部)与进程之间
并发的概念:
多个执行单元同时、并行被执行,而并发执行单元对共享资源(硬件资源、
和软件上的全局变量、静态变量等)的访问则很容易导致竞态。
互斥访问的概念:
一个执行单元在访问共享资源的时候,其他执行单元被禁止访问。
临界区的概念;
访问共享资源的代码区;
同步的概念:
两件事情之间有先后顺序之分;
异步的概念:
两件事情的发生没有任何关联;
2、Linux中常用的同步访问技术
主要有中断屏蔽、原子操作、自旋锁和信号量等,中断屏蔽会使得中断得不到响应,
从而导致性能变差,原子操作受限于CPU,只能实现有限几种基本数据类型。
正确使用锁定机制的关键是,明确指定需要保护的资源,并确保每一个对资源的访问
使用正确的锁定。
2.1中断屏蔽
中断屏蔽使得中断与进程之间的并发不再发生;
由于Linux内核的进程调度等操作都依赖中断来实现,内核抢占进程之间的
并发也就得以避免,它适合与自旋锁联合使用。
2.2原子操作
原子操作函数主要有对位和整型变量进行操作。他们的共同点是在任何情况下
操作都是原子的,内核代码可以安全地调用它们而不会被打断。
2.3自旋锁
自旋锁不会进行休眠,而是在“原地打转”,它可在不能休眠的代码中使用,比如
中断处理例程,在正确使用的情况下,自旋锁通常可以提供比信号量更高的性能。
所以自旋锁等待在本质上都是不可中断的,一旦调用了spin_lock,在获得锁之前将一直
处于自旋的状态。任何拥有自旋锁的代码都必须是原子的,它不能休眠。
2.4信号量
当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。信号量是进程
级的,用于进程间对资源的互斥。应该要在xxx_setup_cdev之前调用init_UTEX
而因为down_interruptible()进入休眠的进程能被信号打断,信号也会导致该函
数的返回,以响应系统的某些变化。
Linux设备驱动中必须解决的一个问题:多进程对共享资源的并发问题,
多个进程对同一个设备的并发访问,势必会导致对该设备资源的竞争。
竞态发生的几种情况:
1)、对称多处理器的多个CPU;
2)、单CPU内进程与抢占它的进程;
2)、中断(硬中断、软中断、tasklet、底半部)与进程之间
并发的概念:
多个执行单元同时、并行被执行,而并发执行单元对共享资源(硬件资源、
和软件上的全局变量、静态变量等)的访问则很容易导致竞态。
互斥访问的概念:
一个执行单元在访问共享资源的时候,其他执行单元被禁止访问。
临界区的概念;
访问共享资源的代码区;
同步的概念:
两件事情之间有先后顺序之分;
异步的概念:
两件事情的发生没有任何关联;
2、Linux中常用的同步访问技术
主要有中断屏蔽、原子操作、自旋锁和信号量等,中断屏蔽会使得中断得不到响应,
从而导致性能变差,原子操作受限于CPU,只能实现有限几种基本数据类型。
正确使用锁定机制的关键是,明确指定需要保护的资源,并确保每一个对资源的访问
使用正确的锁定。
2.1中断屏蔽
中断屏蔽使得中断与进程之间的并发不再发生;
由于Linux内核的进程调度等操作都依赖中断来实现,内核抢占进程之间的
并发也就得以避免,它适合与自旋锁联合使用。
local_irq_disable() //屏蔽中断
...
critical section //临界区
...
local_irq_enable() //开中断
2.2原子操作
原子操作函数主要有对位和整型变量进行操作。他们的共同点是在任何情况下
操作都是原子的,内核代码可以安全地调用它们而不会被打断。
#define atomic_set(v, i) (((v)->counter) = (i)) //设定原子变量的值为i
* atomic_set - set atomic variable
* @v: pointer of type atomic_t
* @i: required value
atomic_t v = ATOMIC_INIT(0) //定义原子变量v并初始化为0
#define atomic_read(v) (*(volatile int *)&(v)->counter) //获取原子变量的值
* atomic_set - set atomic variable
* @v: pointer of type atomic_t
* @i: required value
static inline void atomic_add(int i, atomic_t *v) //原子变量增加i
{
atomic_add_return(i, v);
}
static inline void atomic_sub(int i, atomic_t *v) //原子变量减少i
{
atomic_sub_return(i, v);
}
2.3自旋锁
自旋锁不会进行休眠,而是在“原地打转”,它可在不能休眠的代码中使用,比如
中断处理例程,在正确使用的情况下,自旋锁通常可以提供比信号量更高的性能。
所以自旋锁等待在本质上都是不可中断的,一旦调用了spin_lock,在获得锁之前将一直
处于自旋的状态。任何拥有自旋锁的代码都必须是原子的,它不能休眠。
spinlock_t spin; //定义自旋锁
spin_lock_init(&lock); //自旋锁初始化
spin_lock(&lock); //获得自旋锁,保护临界区
...
critical section //临界区
...
spin_unlock //解锁
2.4信号量
当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。信号量是进程
级的,用于进程间对资源的互斥。应该要在xxx_setup_cdev之前调用init_UTEX
//初始化信号量
sema_init(struct semaphore *sem);
//获取信号量
void down(struct semaphore *sem)
* down - acquire the semaphore
* @sem: the semaphore to be acquired
int down_interruptible(struct semaphore *sem)
* down_interruptible - acquire the semaphore unless interrupted
* @sem: the semaphore to be acquired
以上两个函数的区别是,因为down()而进入休眠状态的进程不能被信号打断,
而因为down_interruptible()进入休眠的进程能被信号打断,信号也会导致该函
数的返回,以响应系统的某些变化。
//使用信号量实现设备只能被一个进程打开
static DECLEAR_MUTEX(xxx_lock); //定义互斥锁
static int xxx_open(struct inode *inode, struct file *filp)
{
....
if (down_trylock(&xxx_lock)) //获得打开锁
return -EBUSY;
...
return 0;
}
static int xxx_release(struct inode *inode, struct file *filp)
{
up(xxx_lock); //释放打开锁
return 0;
}