linux内核并发控制-互斥体、自旋锁、中断屏蔽

    在linux内核中,主要的竞态发生于如下几种情况:

1.对称多处理器(SMP)的核间并发

cpu0的进程/中断与cpu1的进程\中断之间的并行。

2.单cpu内进程与抢占它的进程

进程与抢占它的进程访问共享资源类似于SMP的多个cpu

3.中断与进程之间(核间或核内并发)

中断可以打断正在执行的进程,如果中断服务程序访问进程正在访问的资源,则竞态也会发生。linux2.6.35之后就取消了中断的嵌套,所以核内没有中断嵌套引起的并发,核间可能有中断与中断的并行。

解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问是指一个执行单元在访问共享资源时,其他的执行单元被禁止访问。访问共享资源的代码区域被称为临界区。中断屏蔽、自旋锁、互斥体是常用的互斥手段。

一 中断屏蔽

中断屏蔽local_irq_disable()和中断使能local_irq_enablle()都只能禁止和使能本cpu内的中断。所以中断屏蔽可以使得核内的中断与进程并发不再发生,由于linux进程调度依赖中断来实现,进程与抢占它的进程之间的并发也避免了。但不能解决SMP多cpu引发的竞态。所以屏蔽中断来避免竞态不是一种正确的做法。

local_irq_disable()//屏蔽中断
...
critical section//临界区
...
local_irq_enable()//使能中断

二 自旋锁

spin_lock():任何一个核拿到自旋锁,该核上的进程抢占调度就被禁止了,但是没有禁止其他核的抢占调度,由于锁的存在,可以保证临界区不受别的cpu或本cpu内的抢占进程打扰。

但是得到锁的代码路径在执行临界区时,还可能收到中断和底半部的影响。

spin_lock_irq=spin_lock+local_irq_disable 锁+关闭本核中断

spin_unlock_irq=spin_unlock+local_irq_enable

spin_lock_irqsave=spin_lock+local_irq_save

spin_locj_irqrestore=spin_unlock+local_irq_restore

spin_lock_bh = spin_lock+local_bh_disable

spin_unlock_bh = spin_unlock+local_bh_enable

如果进程和中断可能访问同一片资源,一般需要在进程上下文中使用spin_lock_irqsave/spin_unlock_irqrestore,避免核内进程与中断并发。在中断上下文中使用spin_lock/spin_unlock,避免核间中断与进程/中断并发。

自旋锁使用注意:占用锁的时间应该很短,即临界区域应很简短;自旋锁锁定期间不能调用可能引起进程调度的函数如copy_from_user/copy_to_user/kmalloc/msleep,其实就是进程获得锁之后,自己不能阻塞,因为阻塞后进程无法再调度;

spin_lock_t lock;
spin_lock_init(lock);
spin_lock(lock)
...
临界区
...
spin_unlock

if (spin_trylock(lock)) {
    ...//获得锁
    临界区
    spin_unlock(lock);
}
else {
    //没有获得锁,不需要解锁
}

//禁止本核进程调度,屏蔽本核中断,保存状态
spin_lock_irqsave(lock,flags)
...
临界区//不要有可能引起阻塞的代码copy_from_user,copy_to_user,kmalloc,msleep
...
spin_lock_irqrestore(lock,flags)

三 读写自旋锁

只有读与读可以并发,读写不能并发,写与写不能并发。与自旋锁比较,区分临界区的读写操作。

读操作
read_lock(lock)-->read_unlock
read_lock_irqsave-->read)unlock_irqrestore

写操作
write_lock--->write_unlock
write_lock_irqsave-->write_lock_irqrestore

四 顺序自旋锁

只有写与写是互斥的,读与写不互斥,读操作期间可能发生了写操作,故需判断是否发生了写操作,有则重读。

写执行单元

write_seqlock-->write_sequnlock
write_seqtrylock-->
write_seqlock_irqsave-->
write_seqlock_irq-->
write_seqlock_bh-->

读执行单元
unsigned read_seqbegin(seqlock)返回一个顺序号
读完需判断是否需要重读
read_seqretry(lock, seqnum)
读操作用法:
do {
   seqnum = read_seqbegin(lock);
   ...
} while (read_seqretry(lock, seqnum));

五 互斥体

mutex_lock与spin_lock相比,mutex_lock没获得锁会睡眠,发生进程上下文切换。进程上下文切换开销大,所以互斥体一般用于进程占用资源时间较长时。互斥体是进程级的,一般不能用于中断(因为会睡眠),一定要用可以用mutex_trylock,获取不到避免阻塞,中断可以用自旋锁。

struct mutex my_mutex;
mutex_init(&my_mutex);

mutex_lock(&my_mutex);//获取不到,睡眠,不可被信号打断
...
mutex_unlock(&my_mutex);

mutex_lock_interruptible(&my_mutex)//获取不到,睡眠,可被信号打断

mutex_trylock(&ny_mutex)//尝试获取,获取不到不会睡眠。


阅读更多
个人分类: camera
上一篇android开发之多线程编程Thread和Runnable使用
下一篇AXI总线简介
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭