一、自旋锁(spinlock)和互斥体(mutex)是保护内核临界区的两种基本机制。
1)自旋锁:
自旋锁可以确保在同一时间只有一个线程进入临界区。其他想进入临界区的线程必须不断地在原地打转,知道第一个线程释放自旋锁。
#include<linux/spinlock.h>
spinlock_t mylock = SPIN_LOCK_UNLOCKED;
spin_lock(&mylock);
...........
spin_unlock(&mylock);
2)互斥体:
与自旋锁不同的是,互斥体在进入一个被占用的临界区之前不会原地打转,而是是当前进程进入睡眠状态。如果要等待的时间较长,互斥体比自旋锁更适合,多于两次进程切换时间都可被认为是长时间。
#include <linux/mutex.h>
static DEFINE_NUTEX(mymutex);
mutex_lock(&mymutex);
...............
mutex_unlock(mymutex);
互斥体接口代替了旧的信号量接口(semaphore)。互斥体接口是从-rt树演化而来的,在2.6.16内核中被融入主线内核。
尽管如此,旧的信号量仍然在内核和驱动程序中广泛使用。信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex)。信号量接口的基本用法如下:
#include <asm/semaphore>
static DECLARE_MUTEX(mysem);
down(&mysem);
......................
up(&mysem);
二、原子操作
所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它是最小的执行单位,不可能有比它更小的执行单位,因此这里的原子实际是使用了物理学里的物质微粒的概念。
原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。
原子操作主要用于实现资源计数,很多引用计数(refcnt)就是通过原子操作实现的。原子类型定义如下:
typedef struct { volatile int counter; } atomic_t;
volatile修饰字段告诉gcc不要对该类型的数据做优化处理,对它的访问都是对内存的访问,而不是对寄存器的访问。
原子操作API包括:
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。
void atomic_inc(atomic_t *v); 该函数对原子类型变量v原子地增加1。
void atomic_dec(atomic_t *v); 该函数对原子类型的变量v原子地减1。
三、读—写锁
另一个特定的并发保护机制是自旋锁的读—写锁变体。如果每个执行单元在访问临界区的时候要么是读要么是写共享的数据结构,但是它们不会同时进行读写操作,那么这种锁是最好的选择。允许多个读线程同时进入临界区。读自旋锁可以这样定义:
rwlock_t myrwlock = RW_LOCK_UNLOCKED;
read_lock (&myrwlock);
...........................
read_unlock (&myrwlock);
但是,如果一个写线程进入了临界区,那么其他的读和写都不允许进入。写锁的用法如下:
rwlock_t myrwlock = RW_LOCK_UNLOCKED;
write_lock (&myrwlock);
............................
write_unlock(&myrwlock);