`
hulianwang2014
  • 浏览: 691881 次
文章分类
社区版块
存档分类
最新评论
  • bcworld: 排版成这样,一点看的欲望都没有了
    jfinal

Linux device drives 5(并发与竟态)

 
阅读更多

竟态会导致对共享数据的非控制访问。发生这种错误模式的访问,会产生非预期结果。

内核提供了许多可延迟代码执行的机制,比如workqueue(工作队列)、tasklet(小任务)、以及timer(定时器),而且内核代码可以是抢占式的。对于竟态的发生时很有可能的,虽然竟态一般都是小概率时间,但是带来的危害却很大。

对于这种竟态问题,只要有可能就应该避免资源的共享。

处理并发和竟态的办法就是把这个共享的数据变成原子操作,仅有一个进程来执行。信号量就出来了。一个信号量就是一个整数值,它和一对函数联合使用。这一对函数通常称为P和V操作,希望进入临界区的进程竟在相关的信号量上调用P操作,如果信号量大于0,则该值会减小1,而进程可以继续,相反如果信号量值为0(或更小),进程必须等待直到其他进程来释放信号量。对信号量进行解锁通过调用V操作,该函数增加信号量的值,并在必要时唤醒等待的进程。

信号量用于互斥时候,它的值应该初始化为1,表明仅仅有一个进程能够访问,共享单一访问,就成了互斥,一定要区别 互斥和信号量的概念。

信号量和互斥体

Linux内核支持信号量的实现。内核代码必须包含<asm/semaphore.h>相关的类型是struct semaphore

/*
*      初始化信号量函数
*      @param sem 需要初始化的信号量指针
*      @param val 把信号量初始化的初始化值
*      返回值是void
*/
voidsemaphore_init(struct semaphore *sem, int val)
初始化互斥体
DECLARE_MUTEX(name);//初始化一个互斥体
DECLARE_MUTEX_LOCKED(name);//该互斥体处于锁定状态
P操作函数
void down(strcutsemaphore *sema);       //    不可中断
intdown_interruptible(struct semaphore *sema);//可被中断
intdown_trylock(struct semaphore *sema); //如果不能获得就会立即返回一个非0值
Linux 的V操作(释放信号量)
void up(structsemaphore *sema);

正确使用锁的机制是①明确要保护的资源②确保每一个对这些资源的访问使用正确的锁定

读写信号量

一些任务只需要读取受保护的数据结构,而其他的的必须要做出修改。Linux内核提供了一种特殊的信号量,称为rwsem(读写信号量),使用rwsem的代码必须包含 <linux/rwsem.h>

void init_rwsem(structrw_semaphore *sema);//初始化信号量
读取借口
voiddown_read(struct rw_semaphore *sema);
intdown_read_trylock(struct rw_semaphore *sema);
voidup_read(struct rw_semaphore *sema);
读写信号量对于读操作可以共享
voiddown_write(struct rw_semaphore *sema);
intdown_write_trylock(struct rw_semaphore *sema);
void up_write(structrw_semaphore *sema);

一个rw_semaphore可允许一个写入或者无限多个读取者拥有该信号量。写入者的优先级会高些,在写入者完成之前,是不允许读取操作的,这样如果一直有写入者,就会导致读取者一直等待,就是所说的饿死。

completion(完成)接口,completion是一种轻量级的机制,它允许一个线程告诉另外一个线程某个工作已经完成,为了使用completion,代码必须包含<asm/completion.h>

init_completion(strcutcompletion *sema)//初始化一个信号量
voidwait_for_completion(struct completion *c);//等待前一个完成
wait_for_completion是一个非中断的等待。
voidcomplete(struct completion *sema)//完成后通知另外的等待的(阻塞在wait_for_completion)
void complete_all(structcompletion *sema)//通知所有等待该信号量的线程
complete仅仅会唤醒一个线程。而complete_all会唤醒所有等待该信号量的线程

自旋锁

自旋锁可以在不能休眠的代码中执行

完整定义:一个自旋锁是一个互斥设备,他只能有两个值,锁定或者解锁。它通常的实现位某个整数值宏的单个位。希望获得某特定锁的代码测试相关的位。如果锁可以使用,则锁定位被设置,而代码继续进入临界区;相反,如果锁被其他人获得,则代码进入忙循环并重复检查这个锁,知道该锁可用为止,这个循环就是自旋锁的自旋部分。

自旋锁所要包含的文件是<linux/spinlock.h>

spinlock_t mylock= SPIN_LOCK_UNLOCKED;//初始化
voidspin_lock_init(spinlock_t *lock);//动态初始化
voidspin_lock_(spinlock_t *lock);//加锁
voidspin_unlock(spinlock_t *lock);//解锁

适用于自旋锁的核心规则是:任何拥有自旋锁的代码必须都是原子的,它不能睡眠,实际上,它不能因为任何原因放弃处理器

自旋锁和信号量区别

自旋锁和信号量是解决互斥问题的基本手段,无论是单处理系统还是多处理系统,它们可以不需修改代码地进行移植。那么,这两个手段应该如何选择呢?这就要考虑临界区的性质和系统处理的要求。
从严格意义上说,信号量和自旋锁属于不同层次的互斥手段,前者的实现有赖于后者。
信 号量是进程级的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来争夺资源的。如果竞争不上,会有上下文切换,进程可以去睡眠,但CPU不会停,会接着运行其他的执行路径。从概念上说,这与单CPU或多CPU没有直接的关系,只是在信号量本身的实现上,为了 保证信号量结构存取的原子性,在多CPU中需要自旋锁来互斥。但是值得注意的是上下文切换需要一定时间,并且会使高速缓冲失效,对系统性能影响是很大的。 因此,只有当进程占用资源很长时间时,用信号量才是不错的选择。
当所要保护的临界区比较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间。但是CPU得不到自旋锁会在那里空转直到锁成功为止,所以要求锁不能在临界区里停留很长时间,否则会降低系统的效率。

综上,自旋锁是一种保护数据结构或代码片段的原始方式,主要用于SMP中,用于CPU同步,在某个时刻只允许一个进程访问临界区内的代码。它的实现是基于 CPU锁定数据总线的指令。为保证系统效率,自旋锁锁定的临界区一般比较短。在单CPU系统中,使用自旋锁的意义不大,还容易因为递归调用自旋锁造成死锁。


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics