Linux内核中的同步机制:原子操作、信号量、读写信号量和自旋锁的API。
一、引言
在现代操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实象多进程多线程编程一样也需要一些同步机制来同步各执行单元对共享数据的访问。尤其是在多处理器系统上,更需要一些同步机制来同步不同处理器上的执行单元对共享的数据的访问。
在主流的Linux内核中包含了几乎所有现代的操作系统具有的同步机制,这些同步机制包括:原子操作、信号量(semaphore)、读写信号量(rw_semaphore)、spinlock、BKL(Big Kernel Lock)、rwlock、brlock(只包含在2.4内核中)、RCU(只包含在2.6内核中)和seqlock(只包含在2.6内核中)
二、原子操作
所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位,因此这里的原子实际是使用了物理学里的物质微粒的概念。
原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。
三、信号量
1、概念
信号量是一种休眠锁,当一个进程获取不到某个信号量,进入休眠状态,放弃CPU,调度器调度其他进程执行。当先获取到信号量的进程释放信号量,因获取不到信号量而休眠的进程会被唤醒,继续执行。
2、使用方法
(1)定义信号量
struct semaphore led_sem;
(2)初始化
static inline void sema_init(struct semaphore *sem, int val)
(3)获取信号量
void down(struct semaphore *sem);---获取不到,休眠,不能被信号唤醒
int __must_check down_interruptible(struct semaphore *sem);---获取不到,休眠,可以被信号唤醒
(4)释放信号量
void up(struct semaphore *sem);
3、特点
- 信号量适用于锁会被长时间持有的情况。
- 持有信号量锁的线程可以睡眠,而持有自旋锁的线程是不允许睡眠的。
- 信号锁不会禁止内核抢占。
- 最重要的信号锁同时允许任意数量的锁持有者,而自旋锁在同一时刻最多只允许一个任务持有他。
四、自旋锁
1、概念
顾名思义,自旋,表示原地打转。当进程A获得自旋锁,可以进入临界区,访问临界资源,此时进程B也想获得自旋锁去访问临界资源,但是自旋锁已经被进程A占用,进程B只能“自旋”。自旋不是休眠,它没有放弃CPU的使用权,是一个忙等的状态。
2、基本概念
(1)临界资源:共享的,但是不能同时访问的资源。
(2)临界区:访问临界资源的代码段。
3、自旋锁的使用
(1)定义自旋锁
static DEFINE_SPINLOCK(wdt_lock);
(2)获取自旋锁(上锁)
static inline void spin_lock(spinlock_t *lock)------获取成功,继续往下执行,否则原地打转
(3)释放自旋锁
static inline void spin_unlock(spinlock_t *lock)
4、自旋锁使用的注意事项
- 在中断处理程序中,可以使用自旋锁,不会造成中断处理程序的休眠阻塞。
- 自旋锁保护的临界资源,访问时间不能太长,否则会造成其他想获取锁的进程 占着CPU空转,浪费CPU资源。
- 一个进程获取到自旋锁A后,不要尝试再次获取自旋锁A,否则会造成死锁。
- 在释放一个自旋锁之前,不要尝试获取另外一个自旋锁,否则也可能死锁。
五、互斥锁
1、概念
互斥锁也是休眠锁,可以认为是一个二值信号量,只有上锁和解锁两种情况
2、互斥锁的使用
(1)定义互斥锁
struct mutex xxx_mutex;
(2)初始化
mutex_init(&xxx_mutex);
(3)加锁
void __sched mutex_lock(struct mutex *lock)
(4)解锁
void __sched mutex_unlock(struct mutex *lock)
六、自旋锁和互斥锁的应用区别
- 自旋锁是忙等锁,不休眠;互斥锁是休眠锁
- 自旋锁可以用于中断处理程序;互斥锁或信号量不能
- 自旋锁持有时间要短,持有期间不能休眠;信号量或互斥锁可以长时间持有,并持有期间可以休眠
- 使用时都应注意避免死锁
- 自旋锁持有期间,内核不能抢占;信号量持有期间可以被抢占