Chinaunix首页 | 论坛 | 博客
  • 博客访问: 346366
  • 博文数量: 92
  • 博客积分: 2500
  • 博客等级: 少校
  • 技术积分: 960
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-21 19:38
文章分类

全部博文(92)

文章存档

2010年(71)

2009年(21)

我的朋友

分类: 嵌入式

2010-04-10 11:56:25

Linux设备驱动中的并发控制

Linux 设备驱动开发详解》我主要拜读这部书。

并发和竞争发生在两类体系中:

1.       对称多处理器(SMP)的多个CPU

2.       内核可抢占的单CPU系统

访问共享资源的代码区域称为临界区critical sections,临界区需要以某种互斥机制加以保护。

在驱动程序中,当多个线程同时访问相同的资源critical sections时(驱动程序中的全局变量是一种典型的共享资源),可能会引发"竞态",因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的方法又中断屏蔽、原子操作、自旋锁、信号量。(后面为主要方式)

 

我要着重谈一下:

自旋锁VS信号量

从严格意义上来说,信号量和自旋锁属于不同层次的互斥手段,前者的实现依赖于后者。在多CPU中需要自旋锁来互斥。

信号量是进程级的,用于多个进程之间对资源的互斥,虽然也在内核中,但是该内核执行路径是以进程的身份,代表进程来争夺资源的。如果竞争失败,会切换到下个进程,而当前进程进入睡眠状态,因此,当进程占用资源时间较长时,用信号量是较好的选择。

       当所要保护的临界访问时间比较短时,用自旋锁是非常方便的,因为它节省了上下文切换的时间。但是CPU得不到自旋锁是,CPU会原地打转,直到其他执行单元解锁为止,所以要求锁不能在临界区里停留时间过长。

下面的内容多是网上复制来的:

 

1.       中断屏蔽
在单CPU范围内避免竟态的一中简单方法是在进入临界区之前屏蔽所有的中断。
中断屏蔽的使用方法
local_irq_disable() //
屏蔽中断
...
critical section //
临界区
...
local_irq_enable() //
屏蔽中断
在屏蔽中断期间所有的中断都无法得到处理。

 

2.       原子操作
所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位,因此这里的原子实际是使用了物理学里的物质微粒的概念。

atomic_read(atomic_t * v);      
该函数对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。

atomic_set(atomic_t * v, int i);  
该函数设置原子类型的变量v的值为i

void atomic_add(int i, atomic_t *v);  
该函数给原子类型的变量v增加值i

atomic_sub(int i, atomic_t *v);      
该函数从原子类型的变量v中减去i

int atomic_sub_and_test(int i, atomic_t *v);  
该函数从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。

void atomic_inc(atomic_t *v);               
该函数对原子类型变量v原子地增加1

void atomic_dec(atomic_t *v);          
该函数对原子类型的变量v原子地减1


int atomic_dec_and_test(atomic_t *v);  
该函数对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。

int atomic_inc_and_test(atomic_t *v);  
该函数对原子类型的变量v原子地增加1,并判断结果是否为0,如果为0,返回真,否则返回假。


int atomic_add_negative(int i, atomic_t *v);  
该函数对原子类型的变量v原子地增加I,并判断结果是否为负数,如果是,返回真,否则返回假。

int atomic_add_return(int i, atomic_t *v);  
该函数对原子类型的变量v原子地增加i,并且返回指向v的指针。

int atomic_sub_return(int i, atomic_t *v);  
该函数从原子类型的变量v中减去i,并且返回指向v的指针。

int atomic_inc_return(atomic_t * v);      
该函数对原子类型的变量v原子地增加1并且返回指向v的指针。

int atomic_dec_return(atomic_t * v);      
该函数对原子类型的变量v原子地减1并且返回指向v的指针。

实例:使用原子变量使设备只能被一个进程打开

static atomic_t xxx_available = ATOMIC_INIT(1);/*
定义原子变量*/
static int xxx_open()
{
  
if(!atomic_dec_and_test(&xxx_available )){
       return BUSY;//
已经打开
   }
   return 0;//
成功

}
int release()
{
   atomic_inc(&xxx_available);//
释放设备
}

3.       自旋锁
   "
自旋"就是"在原地打转"。自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处
    1
、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。
    2
、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()copy_from_user()kmalloc()等。
因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。
与自旋锁相关的API主要有:
定义自旋锁  spinlock_t spin;
初始化自旋锁  spin_lock_init(lock) 该宏用于动态初始化自旋锁
lock
 获得自旋锁  spin_lock(lock)  该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将自旋在那里,直到该自旋锁的保持者释放;

spin_trylock(lock)
  该宏尝试获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,否则立即返回假,实际上不再"在原地打转"
 释放自旋锁  spin_unlock(lock)  该宏释放自旋锁lock,它与spin_trylockspin_lock配对使用;
自旋锁一般使用方式:
定义自旋锁
spinlock_t lock;
spin_init(&lock);


spin_lock(&lock);//
获取自旋锁,保护临界区
...//
临界区
spin_unlock(&lock);//
解锁
读写锁
读写锁,是自旋锁的一种衍生锁,为什么要衍生锁了,是因为自旋锁在多个执行单元在同时读写临界资源时都会被锁住,这样最多只能有一个执行单元拥有锁进而对资源进行操作,然而事实并非如此,在很多时候,同时读取临界资源是没有问题的,所以引入读写锁,他解决了读执行单元同时操作临界资源的问题,即允许读并发,但在写单元执行时最多允许一个进程访问临界资源。
一般有如下用法模型:

rwlock_t lock; //
定义

read_init( &lock);

..........//
临界资源

read_unlock(&lock);


//
写时

write_lock_irqsave(&lock,flags);

..........//
临界资源

write_unlock_irqrestroe(&lock,flags);
顺序锁:
顺序锁(seqlock_t),对读写锁的一种优化,使用顺序锁时,读不会被写执行单元阻塞,也就是说,当向一个临界资源中写入的同时,也可以从此临界资源中读取,即实现同时读写,但是同时写不被允许。如果读执行单元在读操作期间,写执行单元已经嗯发生了写操作,那么,读执行单元必须重新开始,这样保证了数据的完整性,当然这种可能是微乎其微。顺序锁的性能是非常好的,同时他允许读写同时进行,大大的提高了并发性。
但是他有一个限制:共享资源不含有指针,因为写执行单元可能使得指针失效,但读执行单元如果正要访问该指针,将导致Oops( 网上搜索这个词的意思是:吃惊的感叹词。我理解为访问该指针将会导致意想不到的结果)
代码段如下:
do{
seqnum=read_seqbegin(&seqlock);
//
读操作代码段
...........

}while(read_seqretry(&seqlock,seqnum);

RCU
Read-Copy Update)读-拷贝-修改
对于被RCU保护的共享数据结构,读者不需要获得任何锁就可以访问它,但写者在访问它时首先拷贝一个副本,然后对副本进行修改,最后使用一个回调(callback)机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据。这个时机就是所有引用该数据的CPU都退出对共享数据的操作。

因此RCU实际上是一种改进的rwlock,读者几乎没有什么同步开销,它不需要锁,不使用原子指令,而且在除alpha的所有架构上也不需要内存栅(Memory Barrier),因此不会导致锁竞争,内存延迟以及流水线停滞。不需要锁也使得使用更容易,因为死锁问题就不需要考虑了。写者的同步开销比较大,它需要延迟数据结构的释放,复制被修改的数据结构,它也必须使用某种锁机制同步并行的其它写者的修改操作。

具体看这个网页:

http://hi.baidu.com/whs08/blog/item/05ac4e097d0aa52c6a60fb42.html

 

阅读(1638) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~