随着硬件的发展,SMP(对称多处理器)已经很普遍,如果内核的调度机制是可抢占的,那么SMP和内核抢占是多线程执行的两种场景。当多个线程同时访问内核的数据结构时,我们就需要对其做串行化处理。
自旋锁和互斥体
访问共享资源的代码区称为临界区。自旋锁(spinlock)和互斥体(mutex, mutual exclusion)是保护内核临界区的两种基本机制。
自旋锁可以确定在同时只有一个线程进入临界区。其他想进入临界区域的线程(执行线程)必须不停的在原地打转,知道第一个线程释放的自旋锁。下面是spinlock的用法
#include <linux/spinlock.h> /*初始化*/ spinlock_t _lock = SPIN_LOCK_UNLOCKED; /*上锁*/ spin_lock(&_lock); /*临界区代码*/ /*释放锁*/ spin_unlock(&_lock);互斥体与自旋锁不一样,当在临界区外等待时,它不会不停的打转,而是使当前的线程进入睡眠状态。如果执行临界区需要很长的时间,那么互斥体更适合自旋锁,这样可是更好的利用cpu,而不是长时间在哪里打转。
那对于这两个怎么取舍?
1.如果临界区需要睡眠,只能使用互斥体,因为在获得自旋锁后进行调度、抢占以及在等待队列上睡眠都是非法的。
2.由于互斥体会在面临竞争的情况下将当前的线程置于睡眠状态,因此,在中断处理函数中只能使用自旋锁。
下面是互斥体的使用方法
#include <linux/mutex.h> static DEFINE_MUTEX(_mutex); mutex_lock(&_mutex); /* 临界区*/ mutex_unlock(&_mutex)
1.进程上下文,单cpu,非抢占式内核
这种是最简单的,不需要加锁
2.进程和中断上下文,单cpu,非抢占式内核
这种情况下,在保护临界区的时候,仅仅只需要对中断禁止。单cpu和非抢占式内核决定进程不会切换,可是在临界区时,进程可能被中断打断,所以需要禁止中断,也就是在进入临界区时保存中断状态,离开临界区后恢复中断状态。
3.进程和中断上下文,单cpu,抢占式内核
这种情况与2相比,内核可抢占,那么就需要对临界区加锁来禁止内核的抢占,中断还是和2一样。
4.进程和中断上下文,SMP,抢占式内核
现在执行在SMP机器上,而且你的内核配置了CONFIG_SMP和CONFIG_PREEMPT。在SMP系统中,获得自旋锁时,仅仅本CPU的中断被禁止,然后进程上下文和中断处理函数可以在不同的CPU上进行,所以非本CPU的中断处理函数需要等待本CPU上的进程上下文代码推出临界区。中断上下文中需要使用spin_lock和spin_unlock。
原子操作
原子操作用于执行轻量级的,仅执行一次的操作,例如修改定时器、有条件的增加值,设置位等。原子操作可是实现操作的串行化,也就不许要加锁处理。原子操作的时间取决了体系结构。
读写锁
自旋锁的变体--读写锁。如果每个执行单元在访问临界区的时候要么是读要么是写数据结构,但是他们都不会同时进行读写操作,那么读写锁很适合。读写锁允许同一时候多个线程进入临界区,但如果一个写线程进入临界区,那么其他的读写进程都必须等待。读写锁使用方法:
rwlock_t _rwlock = RW_LOCK_UNLOCKED; read_lock(&_rwlock); /* 临界区代码*/ read_unlock(&_rwlock); -------------------------------------- rwlock_t _rwlock = RW_LOCK_UNLOCKED; wirte_lock(&_rwlock); /* 临界区代码*/ wirte_unlock(&_rwlock);