我本仁慈,奈何苍天不许
分类: LINUX
2013-12-26 16:53:38
Liunx设备驱动中的并发控制
{//?什么是并发,为何要使用并发机制
并发即是 多件事情同时在执行
只有使这些设备都并发的执行才能满足性能要求。
例:
如果系统上挂10个设备,每个设备都请求,如果是串行顺序执行,
可能一个设备要等较长时间,系统才能响应它
}
有哪些情况可能产生并发?
任务(多线程、多进程)、中断、多CPU
{//?如何实现并发
CPU是顺序的读入指令执行的, 如何来实现并发呢?
例:
当你A先生的业务时,B女士来个电话,你接完电话后继续做A的事。
如何你切换足够快,即在A,B的忍耐时间内。 感觉就像在并发执行一样。
使用中断的方式可实现并发 //中断: 类似于接电话,打断当前执行,等处理完后,返回再接着执行。
}
{//?为什么要进行并发控制
并发会导致竞态的发生。 //竞态: 一个资源被并发执行的任务同时访问时,就会出现竞争。此时的状态就是竞态。
需保证一个资源在一个时间只能被一个任务访问,才能避免混乱。即并发控制
}
{//?如何进行并发控制
采用互斥机制对并发进行控制 //互斥: 共享资源被某任务访问时,别的任务禁止访问。
互斥机制:
{//中断屏蔽
使用禁止中断方式,避免中断的影响
local_irq_disable //屏蔽中断
临界区代码 //临界区: 访问共享资源的代码区域
local_irq_enable //开中断
注:
1。屏蔽中断的时间尽量短,否则会造成别的中断延时过久,甚至丢失,最好能采有屏蔽指定中断的方式
disable_irq(int irq); //屏蔽指定中断号的中断
enable_irq(int irq); //使能某中断号的中断
优先使用这一种,因为其他两种会屏蔽所有的中断
2。常需保存当前的中断状态,便于恢复
用local_irq_save(flags); 代替local_irq_disable
用local_irq_restore(flags) 代替 local_irq_enable
}
{//原子操作 只能屏蔽一个变量,而自旋锁却可以屏蔽一片区域,所以才会有自旋锁
并发中不被打断的最小单元
{//---hello_atomic/hello.c 实现设备只被一个进程打开
#include
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;
}
}
}
{//自旋锁 spinlock 自旋锁仅对于多CPU这种情况比较好,平时应尽量避免使用自旋锁,因为很容易出现问题。
死循环空转CPU 等待释放锁, 不睡眠, 适用于锁持有时间小于睡眠唤醒时间场合
{//---hello_spinlock/hello.c
static spinlock_t 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_unlock(&hello_spinlock); //后面不是有解锁的语句吗?对于这里为什么需要 解锁?
//因为是当第二次调用此函数时,同样指向了“spin_lock(&hello_spinlock); //还可和中断屏蔽合用 spin_lock_irq”这句
//话,对相关资源进行了加锁,故就算没有使用资源,同样需要解锁
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;
}
}
{//读写锁 rwlock_t
它是从资源访问的特性上,对锁进行了优化, 读可同时进行 ,读写互斥
比如我们只是读相关资源,我们就没有必要加锁,但是如果要写资源,那么就要加锁了
}
{//顺序锁 seqlock
它是对读写锁的进一步优化,放开了读写互斥, 即仅写间互斥。即使读者正在读的时候也允许写者继续运行
这种方式是采用连续读取两次进行对比的方式来判断该读取的相关数据是否被修改
}
{//无锁设计
如环形缓冲 类似生产者和消费者模型
}
//加锁粒度
}
{//信号量
进程当中用于并发互斥和,资源的计数。
相对于自旋锁,信号量会睡眠,仅能用于进程中
//---hello_semaphore/hello.c
#include
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;
}
}
{//互斥体
互斥体是专门用来做互斥的, 和二元的信号量类似,
struct mutex my_mutex; /* 定义mutex */
mutex_init(&my_mutex); /* 初始化mutex */
mutex_lock(&my_mutex); /* 获取mutex */
.../* 临界资源*/
mutex_unlock(&my_mutex); /* 释放mutex */
}
}
{//?并发机制使用场合
1. 中断屏蔽的使用场合
当有中断处理程序访问共享资源的时候。
2. 原子操作的使用场合
只使用于共享资源为一个变量的操作的情况
3. 自旋锁的使用场合
在临界区代码运行时间比较短的情况。这里面不能有休眠的函数,不能调用可能引起进程调度的函数,比如copy_from_user()、
copy_to_user()、kmalloc()、msleep()等函数,可能导致内核崩溃。
多CPU的情况下
4. 信号量的使用场合(仅用于进程中)
临界区代码运行时间比较长的情况。与自旋锁类似,但是信号量在得不到信号量时,进程不会在原地打转而是进入休眠等待状态。
当锁持有的时间不是很长的时候,优先使用信号量。
1注意: 中断里不能使用信号量。 因为中断不能睡眠。当然,如果一定要使用信号量,则只能通过down_trylock()方式进行,不能获取就立即返回以避免阻塞。
这几种机制优先使用互斥体,因为互斥体能睡眠,互斥体专门用于并发互斥的,
效率比较高,信号量同样也可以,但是信号量还有一个功能就是计数,所以不确定他是用于互斥还是用于计数,有歧义
}
}