kernel synchronization - Jokacer/Learn GitHub Wiki

临界区:访问和操作共享数据的代码段。

如果两个执行线程可能处于同一个临界区同时执行则会引起竞争,避免并发和防止竞争称为同步(synchronization)。可以采用加锁的方式避免竞争,为了防止在加锁时引起的竞争情况,锁采用原子操作实现。

内核中可能造成并发执行的原因:

  1. 中断
  2. 软中断和tasklet
  3. 内核抢占
  4. 睡眠以及与用户空间的同步
  5. 对称多处理--两个或多个处理器可以同时执行代码

中断处理程序中能避免并发访问的安全代码--中断安全代码

在多对称处理的机器中能避免并发访问的安全代码--SMP安全代码

在内核抢占时能避免并发访问的安全代码--抢占安全代码

死锁:一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源,但所有的资源都已经被占用,所有的线程都在相互等待,但永远不会释放已经占有的资源,于是任何线程都无法继续,死锁形成。

比如自死锁,一个执行线程视图获得自己已经持有的锁,那么它将等待锁被释放,然而它正在等待这个锁没有机会释放,则引起自死锁。为了预防死锁有一些简单的规则可供参考:

  • 顺序加锁。使用嵌套锁时按相同的顺序获取锁这样可以阻止致命拥抱类型的死锁
  • 防止发生饥饿
  • 不要重复请求同一个锁
  • 设计简单--越复杂的加锁方案越容易引起死锁

自旋锁:在短期内进行轻量级加锁,最多只能被一个可执行线程只有,试图获得已被争用的自旋锁的线程会一直进行忙循环-旋转-等待锁可重新用,由于被争用的自旋锁会使得请求它的线程等待锁可重用,所有特别浪费处理器时间。

自旋锁使用形式:

DEFINE_SPINLOCK(mr_lock);
spin_lock(&mr_lock);
/*临界区.....*/
spin_unlock(&mr_lock,flags);

加锁时间不长且代码不睡眠则选择自旋锁,加锁时间长或者在持有锁时可能睡眠则使用信号量加锁

中断处理程序使用自旋锁时在获取锁之前一定要禁止本地中断,防止中断处理程序打断正持有锁的内核代码,引起死锁

#define DEFINE_SPINLOCK(x)	spinlock_t x = __SPIN_LOCK_UNLOCKED(x)

DEFINE_SPINLOCK(mr_lock);
unsigned long flags;
spin_lock_irqsave(&mr_lock);
/*.....*/
spin_unlock_irqrestore(&mr_lock,flags);

有时候锁的用途可以明确区分读写两个场景,可以使用读写锁对数据进行保护,该锁对读者共享,对写者互斥,这种机制比较照顾读者,大量的读者会使得挂起的写者处于饥饿状态。

顺序锁(seq锁)也用于读写共享数据,主要靠一个序列计数器,当数据被写入时会获得一个锁并且序列值增加,在读取数据之前和之后 序列号都被读取,如果读取的序列号相同则该操作中没有被写锁打断,如果读取的值是偶数表明没有写操作发生。这样seq锁对写者更有利,读者不会影响写者,最理想的选择是:

  • 数据存在很多读者
  • 数据写者很少
  • 希望写优先于读,不允许读者让写者饥饿
  • 数据简单

自旋锁提供一种快速简单的锁实现方法,如果加锁时间不长且代码不会睡眠可选择自旋锁,否则最好使用信号量完成。信号量是一种睡眠锁,在一个任务试图获得一个被占用的信号量时,信号量会推进一个等待队列,然后让其睡眠,当该信号量被释放后,处于等待队列中的任务被唤醒,获得该信号量,信号量的特性有:

  • 由于睡眠、维护等待队列以及唤醒需要花费开销,适用于锁会被长时间占用的情况
  • 执行线程在锁被争用时会睡眠,所以只能在进程上下文中才能获得信号量锁,不能在中断上下文中哦你进行调度
  • 占用信号量时不能使用自旋锁

信号量使用:

/*声明mr_sem信号量*/
static DECLARE_MUTEX(mr_sem);
/*试图获取信号量*/
if(down_interruptible(&mr_sem)){
      /*信号被接收,信号量未获取*/
}
/*临界区....*/
      /*释放给定信号量*/
up(&mr_sem);

读写信号量和读写自旋锁差不多,信号量的引用计数为1,只对写者互斥,只要没有写者,并发持有读锁的读者数量不限,与读写自旋锁一样,除非代码能够明白无误的将读和写分割开,否则最好不要使用。

互斥体(mutex)是指任何可以睡眠的强制互斥锁,比如计数为1的信号量。mutex不同于信号量,使用场景相对而言更严格、更定向:

  • 任何时刻中只有一个任务可以持有mutex
  • 给mutex上锁者必须负责解锁
  • 不允许递归上锁和解锁式不被允许的
  • 不能在中断或者下半部使用
  • mutex只能通过官方api进行管理,不能拷贝、手动初始化或者重复初始化

信号量和互斥体很相似,除非mutex妨碍了开发者使用否则首选mutex,发现无法满足其约束条件时再考虑信号量。

需求 建议
低开销加锁 优先自旋锁
短期锁定 优先自旋锁
长期加锁 优先互斥体
中断上下文加锁 自旋锁
持有锁需要睡眠 使用互斥体