在现代操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实象多进程多线程编程一样也需要一些同步机制来同步各执行单元对共享数据的访问。尤其是在多处理器系统上,更需要一些同步机制来同步不同处理器上的执行单元对共享的数据的访问。
在主流的Linux内核中包含了几乎所有现代的操作系统具有的同步机制,这些同步机制包括:原子操作、信号量(semaphore)、读写信号量(rw_semaphore)、spinlock、BKL(Big Kernel Lock)、rwlock、brlock(只包含在2.4内核中)、RCU(只包含在2.6内核中)和seqlock(只包含在2.6内核中)。
原子操作
原子操作就是指某一个操作在执行过程中不可以被打断,要么全部执行,要不就一点也不执行。
原子整数操作只对atomic_t类型的数据进行操作,不能对C语言的int进行操作,
原子操作API包括:
原子读,返回原子类型的变量v的值;
设置原子类型的变量v的值为i;
给原子类型的变量v增加值i;
从原子类型的变量v中减去i;
从原子类型的变量v中减去i,并判断结果是否为0;
对原子类型变量v原子地增加1;
对原子类型的变量v原子地减1;
对原子类型的变量v原子地减1,并判断结果是否为0;
对原子类型的变量v原子地增加1,并判断结果是否为0;
对原子类型的变量v原子地增加I,并判断结果是否为负数;
对原子类型的变量v原子地增加i,并且返回指向v的指针;
从原子类型的变量v中减去i,并且返回指向v的指针;
对原子类型的变量v原子地增加1并且返回指向v的指针;
对原子类型的变量v原子地减1并且返回指向v的指针;
自旋锁
Linux自旋锁保证了任意时刻只能有一个执行线程进入临界区,其他试图进入临界区的线程将一直进行尝试(即自旋),直到获得该锁。
自旋锁的本质是对内存区域的一个整数的操作,任何线程进入临界区之前都必须检查该整数,可用则进入,都则一直忙循环等待。
Linux自旋锁主要应用与多核处理器中,单CPU中不会进行自旋锁操作。
何时使用自旋锁?不允许睡眠的上下文且临界区操作较短时使用自旋锁。
BKL(Big Kernel Lock)
BKL即全局内核锁,也称大内核锁,它是一个全局自旋锁。大内核锁也是用来保护临界区资源的,避免出现多个处理器上的进程同时访问同一区域,整个内核中只有一个大内核锁。
BKL是一个名为kernel_flag的自旋锁,持有该锁的进程仍可以睡眠,当睡眠时持有的锁将被自动释放,该进程被唤醒时重新持有该锁。Linux允许一个进程可以递归的持有BKL,BKL是一个递归锁。
自旋锁加锁的对象一般是一个全局变量,大内核锁加锁的对象是一段代码,里面可能包含多个全局变量。
mutex(互斥锁)
定义在:/linux/include/linux/mutex.h
struct mutex {
atomic_t count;
spinlock_t wait_lock;
struct list_head wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
struct thread_info *owner;
const char *name;
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
互斥锁主要用于实现内核中的互斥访问功能。内核互斥锁是在原子API之上实现的,但这对于内核用户是不可见的。
互斥锁不能进行递归锁定或解锁。一个互斥锁对象必须通过其API初始化,而不能使用memset或复制初始化。一个任务在持有互斥锁的时候是不能结束的。
atomic_t count:指示互斥锁的状态:1没有上锁,可以获得;0被锁定,不能获得;负数被锁定,且可能在该锁上有等待进程初始化为没有上锁。
spinlock_t wait_lock:等待获取互斥锁中使用的自旋锁;在获取互斥锁的过程中,操作会在自旋锁的保护中进行;初始化为为锁定;
struct list_head wait_list:等待互斥锁的进程队列。
顺序锁
顺序锁为写者赋予更高的优先级,写者永远不会等待读者。
顺序锁的数据结构中除了有spinlock外,还有一个顺序号。