一、
什么是并发,为何要使用并发机制
并发(concurrency)指的是多个执行单元同时、并行被执行,而并发的执行单元对共享资源
(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态(race conditions)
并发即是 多件事情同时在执行 只有使这些设备都并发的执行才能满足性能要求。
例:
如果系统上挂10个设备,每个设备都请求,如果是串行顺序执行,
可能一个设备要等较长时间,系统才能响应它
二、静态产生的几种情况
1、对称多处理器(SMP)的多个CPU
2、单CPU内进程与抢占它的进程
一个进程在内核执行的时候可能被另一个高优先级进程打断
3、中断(硬中断,软中断,Tasklet,底半部)与进程之间
中断可以打断正在执行的进程,如果中断处理程序访问进程正在访问的资源,或者出现更高级的中断
三、
如何实现并发
CPU是顺序的读入指令执行的, 如何来实现并发呢?
例:
当你A先生的业务时,B女士来个电话,你接完电话后继续做A的事。
如何你切换足够快,即在A,B的忍耐时间内。 感觉就像在并发执行一样。
使用中断的方式可实现并发 //中断: 类似于接电话,打断当前执行,等处理完后,返回再接着执行。
四、
为什么要进行并发控制
并发会导致竞态的发生。 //竞态: 一个资源被并发执行的任务同时访问时,就会出现竞争。此时的状态就是竞态。
需保证一个资源在一个时间只能被一个任务访问,才能避免混乱。即并发控制
五、
并发机制使用场合
三个并发: 进程 中断 CPU
1. 中断屏蔽的使用场合 //用于 中断和中断间。进程 和 中断 竞争时
当有中断处理程序访问共享资源的时候。
2. 原子操作的使用场合 //用于 进程间 中断间 综合竞争时 (信号量 中断屏蔽等都是基于原子操作来实现的)
只使用于共享资源为一个变量的操作的情况
3. 自旋锁的使用场合 //用于 多CPU 竞争时, (尽量不用,因有CPU死掉的风险)
多CPU的情况下
在临界区代码运行时间比较短的情况。
4. 信号量互斥体的使用场合 //用于 进程间竞争时 (它不能防止中断的打断,故要防止中断打断,虚与中断屏蔽混合使用)
临界区代码运行时间比较长的情况。
当锁持有的时间较长的时候,优先使用信号量。
1注意: 中断里不能使用信号量。 因为中断不能睡眠。
六、
如何进行并发控制
采用互斥机制对并发进行控制 //互斥: 共享资源被某任务访问时,别的任务禁止访问。
1、
中断屏蔽
使用禁止中断方式,避免中断的影响
local_irq_disable //屏蔽中断
临界区代码 //临界区: 访问共享资源的代码区域
local_irq_enable //开中断
以上函数只能禁止和使能CPU内的中断,并不能解决SMP多CPU引发的竟态
注:
1。屏蔽中断的时间尽量短,否则会造成别的中断延时过久,甚至丢失,最好能采有屏蔽指定中断的方式
disable_irq(int irq); //屏蔽指定中断号的中断
enable_irq(int irq); //使能某中断号的中断
2。常需保存当前的中断状态,便于恢复 ,与 local_irq_disable,local_irq_enable 相比除了禁止中断操作外,还保存中断信息,
用local_irq_save(flags); 代替local_irq_disable
用local_irq_restore(flags) 代替 local_irq_enable
3、如果只想禁止中断的底半部
用 local_bh_disable ()
用 local_bh_enable ()
2、
原子操作
并发中不被打断的最小单元,与CPU架构密切相关
(1)整型原子操作
定义于#include<asm/atomic.h>
分为 定义,获取,加减,测试,返回。
void atomic_set(atomic_t *v,int i); //设置原子变量v的值为i
atomic_t v = ATOMIC_INIT(0); //定义原子变量v,并初始化为0;
atomic_read(atomic_t* v); //返回原子变量v的值;
void atomic_add(int i, atomic_t* v); //原子变量v增加i;
void atomic_sub(int i, atomic_t* v);
void atomic_inc(atomic_t* v); //原子变量增加1;
void atomic_dec(atomic_t* v);
int atomic_inc_and_test(atomic_t* v); //先自增1,然后测试其值是否为0,若为0,则返回true,否则返回false;
int atomic_dec_and_test(atomic_t* v);
int atomic_sub_and_test(int i, atomic_t* v); //先减i,然后测试其值是否为0,若为0,则返回true,否则返回false;
注意:只有自加,没有加操作
int atomic_add_return(int i, atomic_t* v); //v的值加i后返回新的值;
int atomic_sub_return(int i, atomic_t* v);
int atomic_inc_return(atomic_t* v); //v的值自增1后返回新的值;
int atomic_dec_return(atomic_t* v);
(2)位原子操作
定义于#include<asm/bitops.h>
分为 设置,清除,改变,测试
void set_bit(int nr, volatile void* addr); //设置地址addr的第nr位,所谓设置位,就是把位写为1;
void clear_bit(int nr, volatile void* addr); //清除地址addr的第nr位,所谓清除位,就是把位写为0;
void change_bit(int nr, volatile void* addr); //把地址addr的第nr位反转;
int test_bit(int nr, volatile void* addr); //返回地址addr的第nr位;
int test_and_set_bit(int nr, volatile void* addr); //测试并设置位;若addr的第nr位非0,则返回true; 若addr的第nr位为0,则返回false;
int test_and_clear_bit(int nr, volatile void* addr); //测试并清除位;
int test_and_change_bit(int nr, volatile void* addr); //测试并反转位;
上述操作等同于先执行test_bit(nr,voidaddr)然后在执行xxx_bit(nr,voidaddr)
(3)示例
{//---hello_atomic/hello.c 实现设备只被一个进程打开
#include <asm/atomic.h>
static atomic_t hello_atomic = ATOMIC_INIT(1); //定义原子变量hello_atomic ,并初始化为1
static int hello_open(struct inode *inode,struct file *file)
{
if (!atomic_dec_and_test(&hello_atomic)) {
// atomic_dec_and_test表示原子变量自减一,并测试是否为了零,如果为零返回真
//当已open过,还未release时,再次open , hello_atomic为0, 再减一不为零,!atomic_dec_and_test 成立
atomic_inc(&hello_atomic); //原子变量加一,恢复自减前状态
return - EBUSY; //已经被打开
}
//当第一次被open时, hello_atomic为1 , !atomic_dec_and_test 不成立, 正常打开
printk("hello open \n");
return 0;
}
static int hello_release(struct inode *inode,struct file *file)
{
atomic_inc(&hello_atomic); //释放设备
printk("hello release\n");
return 0;
}
}
3、
自旋锁 spinlock (cpu自旋 类似while循环)
cpu自旋死循环空转CPU 等待释放锁, 耗资源, 适用于锁持有时间小于睡眠唤醒时间场合
进程递归获得锁时,会导致死锁
{//---hello_spinlock/hello.c
static DEFINE_SPINLOCK(hello_spinlock);
static int hello_resource = 1;
static int hello_open(struct inode *inode,struct file *file)
{
spin_lock(&hello_spinlock); //还可和中断屏蔽合用 spin_lock_irq
if(hello_resource == 0) //为避免死机, spin_try_lock
{
spin_unlock(&hello_spinlock);
return - EBUSY;
}
hello_resource--;
spin_unlock(&hello_spinlock);
printk("hello open \n");
return 0;
}
static int hello_release(struct inode *inode,struct file *file)
{
spin_lock(&hello_spinlock);
hello_resource++;
spin_unlock(&hello_spinlock);
printk("hello release\n");
return 0;
}
}
4、
信号量
进程当中用于并发互斥和,资源的计数。
相对于自旋锁,信号量会睡眠,仅能用于进程中
//---hello_semaphore/hello.c
#include <linux/semaphore.h>
static DECLARE_MUTEX(hello_semlock); //定义一个初始值为一的信号量
static int hello_open(struct inode *inode,struct file *file)
{
if (down(&hello_semlock)) /* 获得打开锁*/
{
return - EBUSY; /* 设备忙*/
}
printk("hello open \n");
return 0;
}
static int hello_release(struct inode *inode,struct file *file)
{
up(&hello_semlock); /* 释放打开锁*/
printk("hello release\n");
return 0;
}
5、
互斥体 用得最多
互斥体是专门用来做互斥的, 和二元的信号量类似,
struct mutex my_mutex; /* 定义mutex */
mutex_init(&my_mutex); /* 初始化mutex */
mutex_lock(&my_mutex); /* 获取mutex */
.../* 临界资源*/
mutex_unlock(&my_mutex); /* 释放mutex */
}