linux内核提供了一套相当完备的内核同步方法。我们将介绍它们的接口、行为及用途。
一、原子操作
原子操作可以保证指令以原子的方式执行——执行过程中不被打断。
内核提供了两种原子操作接口:一组针对整数进行操作;一组针对单独的位进行操作。
1.1 原子整数操作
针对整数的原子操作只能对atomic_t类型的数据进行处理。atomic_t类型定义在<linux/types.h>中:
1 typedef struct { 2 volatile int counter; 3 } atomic_t;
使用原子操作需要的声明都在<asm/atomic.h>中,
定义一个atomic_t类型的数据结构方法如下所示:
atomic_t v; //定义v atomic_t u = ATOMIC_INIT(0); //定义u并将其初始化为0
原子操作如下:
atomic_set(&v,4); //v = 4 原子的 atomic_add(2,&v); //v = v + 2 = 6 原子的 atomic_inc(&v); / / v = v + 1= 7; 原子的 printk("%d ", aotmic_read(&v)); //打印7 将atomic_t 转换为int
int atomic_dec_and_test(atomic_t *v) //用原子整数操作原子的执行一个操作并检查结果
1.2 顺序性和原子性的比较
原子性确保指令执行期间不被打断,要么全部执行完,要么根本不执行。顺序性确保两条或多条指令出现在独立的执行线程中,甚至独立的处理器上,它们本该的执行顺序却依然要保持。
顺序性通过屏障(barrier)指令来实施。
在编写代码时,能使用原子操作,就尽量不要使用复杂的加锁机制。
1.3 64位原子操作
typedef struct{ volatile long counter; }atomic64_t;
1.4 原子位操作
定义在<asm/bitops.h>中,位操作函数是对普通的内存地址进行操作的。它的参数是一个指针和一个位号。
二、自旋锁
情况:先得从一个数据结构中移出数据,然后对其进行格式转换和数据解析,最后再将其加入另一个数据结构中,整个执行过程必须是原子的,在数据被更新完毕之前不允许被其他线程访问。
自旋锁最多只能被一个可执行线程持有;如果一个执行线程试图获得一个被已经持有的自旋锁,那么该线程就会被一直进行忙循环——旋转——等待锁重新使用。要是锁未被争用,请求锁的执行线程便可以立即获得它,继续执行。
一个被争用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理时间),且持有自旋锁的时间最好小于完成两次上下文切换的消耗。
1.4 自旋锁方法
接口定义于<linux/spinlock.h>中,自旋锁的基本使用形式如下:
DEFINE_SPINLOCK(mr_lock); spin_lock(&mr_lock); /*临界区*/ spin_unlock(&mr_lock);
一个时刻只有一个线程位于临界区内,linux内核实现的自旋锁是不可递归的,所以如果你试图获取一个你正持有的锁,你必须自旋,等待你自己释放这个锁;
内核提供的进制中断同时请求锁的接口,如下所示: