Linux 网络协议栈之内核锁—— 自旋锁在抢占(非抢占)单核和多核中的作用

Posted zqixiao_09

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 网络协议栈之内核锁—— 自旋锁在抢占(非抢占)单核和多核中的作用相关的知识,希望对你有一定的参考价值。

一、自旋锁的实现

linux上的自旋锁有三种实现:

1. 在单cpu,不可抢占内核中,自旋锁为空操作。

2. 在单cpu,可抢占内核中,自旋锁实现为“禁止内核抢占”,并不实现“自旋”。

3. 在多cpu,可抢占内核中,自旋锁实现为“禁止内核抢占” + “自旋”。


关于抢占式内核与非抢占式内核:

a、非抢占式内核

如果一个进程在内核态运行,其只有在以下两种情况会被切换:

1.其运行完成(返回用户空间)

2.主动让出cpu(即主动调用schedule或内核中的任务阻塞——这同样也会导致调用schedule)

b、抢占式内核

如果一个进程在内核态运行,其只有在以下四种情况会被切换:

1.其运行完成(返回用户空间)

2.主动让出cpu(即主动调用schedule或内核中的任务阻塞——这同样也会导致调用schedule)

3.当从中断处理程序正在执行,且返回内核空间之前(此时可抢占标志premptcount须为0) 。

4.当内核代码再一次具有可抢占性的时候,如解锁及使能软中断等。


    禁止内核抢占只是关闭“可抢占标志”,而不是禁止进程切换。显式使用schedule或进程阻塞(此也会导致调用schedule)时,还是会发生进程调度的。


二、死锁

1、第一种死锁情况

     死锁发生在多核的情况,下面来分析一下:

     首先,对于多核抢占与多核非抢占的情况,在使用自旋锁时,其情况基本是一致的。因为在多核抢占的情况下,使用自旋锁会禁止内核抢占,这样多核抢占就相当于多核非抢占的情况。

     那下面就只分析多核非抢占的情况。

假设系统有A,B两个CPU。

A上正在运行的a进程已获得自旋锁,并在临界区运行。

B上正在运行的b进程企图获得自旋锁,但由于自旋锁已被占用,于是b进程在B CPU上“自旋”空转。

这时,如果在A上的a进程因程序阻塞,而被休眠。接着A会切换运行另一进程c。

若这个进程c也企图获取自旋锁,c进程同样会因为锁已被占用,而在A上“自旋”空转。

这时候,A上的a进程与c进程就形成了死锁。a进程需要被c进程占用的CPU,c进程需要被a进程占用的锁。

至于在单cpu内核上不会出现上述情况,因为单cpu上的自旋锁实际没有“自旋功能”。


2、第二种死锁情况

     想象你的内核代码请求到一个自旋锁并且在它的临界区里做它的事情,在中间某处,你的代码失去了处理器。或许它已调用了一个函数(copy_from_user,假设)使进程进入眠。也或许,内核抢占发威,一个更高优先级的进程将你的代码推到了一边(注意,这都是假设,自旋锁其实不允许这些操作)。此时,正好某个别的线程想获取同一个锁,如果这个线程运行在和你的内核代码不同的处理器上(幸运的情况),那么它可能要自旋等待一段时间(可能很长),当你的代码从休眠中唤醒或者重新得到处理器并释放锁,它就能得到锁。而最坏的情况是,那个想获取锁得线程刚好和你的代码运行在同一个处理器上,这时它将一直持有CPU进行自旋操作,而你的代码是永远不可能有任何机会来获得CPU释放这个锁了,这就是悲催的死锁。 


3、第三种死锁情况

     spin_lock比spin_lock_irq速度快,但是它并不是任何情况下都是安全的。

举个例子:

     进程A中调用了spin_lock(&lock)然后进入临界区,此时来了一个中断(interrupt),该中断也运行在和进程A相同的CPU上,并且在该中断处理程序中恰巧也会spin_lock(&lock)试图获取同一个锁。由于是在同一个CPU上被中断,进程A会被设置为TASK_INTERRUPT状态,中断处理程序无法获得锁,会不停的忙等,由于进程A被设置为中断状态,schedule()进程调度就无法再调度进程A运行,这样就导致了死锁!

   但是如果该中断处理程序运行在不同的CPU上就不会触发死锁。 因为在不同的CPU上出现中断不会导致进程A的状态被设为TASK_INTERRUPT,只是换出。当中断处理程序忙等被换出后,进程A还是有机会获得CPU,执行并退出临界区。所以在使用spin_lock时要明确知道该锁不会在中断处理程序中使用。


三、自旋锁特性

      自旋锁有几个重要的特性:

1、被自旋锁保护的临界区代码执行时不能进入休眠。

2、被自旋锁保护的临界区代码执行时是不能被被其他中断中断。

3、被自旋锁保护的临界区代码执行时,内核不能被抢占。

从这几个特性可以归纳出一个共性:被自旋锁保护的临界区代码执行时,它不能因为任何原因放弃处理器。 

注:所以现代处理器在处理自旋锁时都会设定自旋上限时间以防死锁. 另自旋锁在单核非抢占式CPU上是无效的.被设为空操作,不做任何事. 

     可能你会奇怪,持有自旋锁的进程在执行内核代码时是不能被抢占的,那么为什么在可抢占式系统中有用呢.其实linux在设计可抢占式系统的自旋锁时只是把自旋锁设计为"只是禁止内核抢占",而没有自旋(所以使用自旋锁的代码一定要可以很快执行完,否则进程就一直持着锁不释放,也不可被抢占).可是理解为只是实现了一个不被打扰的原子操作,操作完后释放锁.所以它不能休眠. 

以上是关于Linux 网络协议栈之内核锁—— 自旋锁在抢占(非抢占)单核和多核中的作用的主要内容,如果未能解决你的问题,请参考以下文章

Linux 网络协议栈之内核锁—— 内核抢占

Linux 网络协议栈之内核锁—— Linux内核抢占和进程调度总结

Linux 网络协议栈之内核锁—— 互斥锁读写锁 自旋锁和RCU锁简述

Linux 网络协议栈之内核锁—— RCU锁机制

Linux 网络协议栈之内核锁—— RCU锁机制

Linux 网络协议栈之内核锁—— 进程调度