linux内核,Linux内核同步

1 内核同步介绍

1.1 临界区和竞争条件

临界区(critical region):操作和访问共享数据的代码段。由于多个线程并发访问同一个资源通常是不安全的,为了避免发生并发访问,必须保证临界区代码原子地执行。
竞争条件(race condition):如果2个线程有可能同时处于临界区中,并且他确实发生了。发生的条件叫竞争条件。
同步(synchronization):避免并发和防止竞争条件被称为同步。

1.2 加锁

虽然一些体系结构能够提供简单的原子操作使临界区原子地执行,以避免并发,如算术运算和比较原子操作。但很难对操作和访问复杂数据结构的临界区提供原子操作。
所以我们需要一种方法确保一次有且只有一个线程对该数据结构进行访问和操作,或者当一个线程在对临界区进行标记时,就禁止其他访问。锁提供的就是这种机制。
必须保证加锁和解锁本身是原子操作,不被中断。
要记住,在最开始设计代码时就要考虑加入锁,而不是事后才想到。事后追加锁是非常困难的。
加锁保护的数据而非代码,要给数据而不是代码加锁。

1.3 死锁

死锁:等待一个永远不会释放的共享资源。
防止死锁的一些简单规则:2个或以上的线程需要获取2个或以上的锁时,加锁的顺序必须一致;防止饥饿;不要重复申请同一个锁;锁的设计力求简单。

1.4 争用和扩展性

锁的争用(lock contention):锁被占用时,其他线程试图获取该锁。
锁处于高度争用状态会大大降低系统性能。
加锁粒度的粗细视具体情况折中考虑。
设计初期加锁方案应该简单,仅当需要时再细化加锁方案。精髓在于力求简单。

2 内核同步的方法

内核同步(避免并发和防止竞争)的方法通常有:原子操作、自旋锁、信号量。

2.1 原子操作

原子操作保证指令以原子的方式执行---执行过程不被打断。
1linux内核,Linux内核同步
21linux内核,Linux内核同步

2.2 自旋锁

自旋锁(spin lock):自旋锁最多被一个线程持有,如果一个执行线程试图获取一个被争用的自旋锁,那么该线程就会进行忙循环知道该锁可用。
由于线程获取被争用的自旋锁时,会自旋等待,不会睡眠。所以自旋锁不应该被长期持有,最好小于完成2次上下文切换的耗时。
321linux内核,Linux内核同步

2.3 读写自旋锁

Linux提供了专门的读写自旋锁。这种自旋锁为读和写操作提供不同的锁。一个或多个任务可以并发的持有读锁,但写锁顶多被一个任务持有。
不能把读锁升级为写锁,如下代码会导致写锁不断自旋,等待读锁释放。
read_lock(&mr_rwlock);
write_lock(&mr_rwlock);
如果需要写锁,应当一开始便申请为写锁。如果不清楚使用读锁还是写锁,使用一般的自旋锁即可。
4321linux内核,Linux内核同步
使用Linux读写自旋锁时,最后要考虑的一点这种锁机制照顾读比写多一点。读读锁被持有时,写操作只能等待,而读操作可以继续成功占用该锁。自旋写锁在所有读操作完成之前,无法获取到该锁的。

2.4 信号量

信号量是一种睡眠锁。当一个线程试图获取被争用的信号量时,信号量会将其推荐一个等待队列,然后让其睡眠。当持有信号量的线程释放信号量后,处于等待队列的线程将被唤醒。
因为信号量是一种睡眠锁,所以适用于持锁时间长的情况;因为信号量的开销大于自旋锁(需要睡眠唤醒),所以短时间的持有锁应选择自旋锁。
信号量是一个可被任意数目的线程同时持有的锁。当数目为1时,该锁为互斥锁。
54321linux内核,Linux内核同步

2.5 读写信号量

信号量与读写信号量的关系,与自旋锁和读写自旋锁的关系雷同。略过。

2.6 自旋锁与信号量

654321linux内核,Linux内核同步

2.7 完成变量

如果内核中一个任务需要发信告诉另一个任务发生了某个特定事件,利用完成变量(completion variable)是使两个任务完全同步的简单方法。完成变量不过是信号量的一个变种。
//注:内容来自《Linux内核设计与实现》
Tags:  linux内核

延伸阅读

最新评论

发表评论