自旋锁的思考:http://bbs.chinaunix.net/thread-2333160-1-1.html

近期在看宋宝华的《设备驱动开发具体解释》第二版。看到自旋锁的部分,有些疑惑。所以来请教下大家。

以下是我參考一些网络上的资料得出的一些想法,不知正确与否。记录下来大家讨论下:

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

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

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

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

(2) 关于抢占式内核与非抢占式内核:

          在非抢占式内核中,假设一个进程在内核态执行,其仅仅有在下面两种情况会被切换:

          1.  其执行完毕(返回用户空间)

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



          在抢占式内核中,假设一个进程在内核态执行。其仅仅有在下面四种情况会被切换:

          1.  其执行完毕(返回用户空间)

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

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

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



         在宋宝华的书中,有提到在使用自旋锁时。要避免用来保护“包括引起堵塞的代码”。由于堵塞意味着要进行进程的切换。这点让我非常迷惑。由于在可抢占式内核中使用自旋锁,是“禁止内核抢占”的,既然“禁止内核抢占”怎么又会发生进程的切换呢?

         如今我是这么想的:禁止内核抢占仅仅是关闭“可抢占标志”。而不是禁止进程切换。显式使用schedule或进程堵塞(此也会导致调用schedule)时,还是会发生进程调度的。



         这里补充一些想法:宋宝华的书上说,在使用自旋锁保护临界区时。如临界区中因“包括引起堵塞代码”而引发堵塞,从而引起进程切换后,若还有一进程企图获得本自旋锁。死锁会发生。

         个人感觉。唯独在多cpu。内核可抢占的情况会发生死锁。而在单cpu。内核可抢占或不可抢占的情况,不会发生死锁,但此时自旋锁失效(即无法实现保护临界区的功能)。这是由于多cpu可抢占内核实现了“自旋”,所以会导致死锁。而单cpu可抢占或不可抢占内核,没有实现“自旋”。不过“禁止内核抢占”,因此不会发生死锁。可是会发生无保护的反复进入临界区的情况(即无法实现保护临界区的功能)。



以上观点仅仅是个人想法,不当之处,还请各位指出,谢谢。

五、自旋锁(spinlock)

自旋锁与相互排斥锁有点类似,仅仅是自旋锁不会引起调用者睡眠。假设自旋锁已经被别的运行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

因为自旋锁使用者一般保持锁时间很短,因此选择自旋而不是睡眠是很必要的,自旋锁的效率远高于相互排斥锁。

信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠。因此仅仅能在进程上下文使用(_trylock的变种可以在中断上下文使用),而自旋锁适合于保持时间很短的情况,它可以在不论什么上下文使用。

假设被保护的共享资源仅仅在进程上下文訪问,使用信号量保护该共享资源很合适,假设对共巷资源的訪问时间很短,自旋锁也能够。可是假设被保护的共享资源须要在中断上下文訪问(包含底半部即中断处理句柄和顶半部即软中断)。就必须使用自旋锁。

自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是能够被抢占的。自旋锁仅仅有在内核可抢占或SMP的情况下才真正须要,在单CPU且不可抢占的内核下,自旋锁的全部操作都是空操作。

跟相互排斥锁一样,一个运行单元要想訪问被自旋锁保护的共享资源,必须先得到锁,在訪问完共享资源后,必须释放锁。假设在获取自旋锁时,没有不论什么运行单元保持该锁,那么将马上得到锁。假设在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。

不管是相互排斥锁。还是自旋锁,在不论什么时刻,最多仅仅能有一个保持者,也就说,在不论什么时刻最多仅仅能有一个运行单元获得锁。

自旋锁的API有:

spin_lock_init(x)

该宏用于初始化自旋锁x。自旋锁在真正使用前必须先初始化。该宏用于动态初始化。

DEFINE_SPINLOCK(x)

该宏声明一个自旋锁x并初始化它。该宏在2.6.11中第一次被定义,在先前的内核中并没有该宏。

SPIN_LOCK_UNLOCKED

该宏用于静态初始化一个自旋锁。

DEFINE_SPINLOCK(x)等同于spinlock_t x = SPIN_LOCK_UNLOCKEDspin_is_locked(x)

该宏用于推断自旋锁x是否已经被某运行单元保持(即被锁),假设是,返回真,否则返回假。

spin_unlock_wait(x)

该宏用于等待自旋锁x变得没有被不论什么运行单元保持,假设没有不论什么运行单元保持该自旋锁,该宏马上返回。否则将循环在那里。直到该自旋锁被保持者释放。

spin_trylock(lock)

该宏尽力获得自旋锁lock,假设能马上获得锁,它获得锁并返回真,否则不能马上获得锁。马上返回假。它不会自旋等待lock被释放。

spin_lock(lock)

该宏用于获得自旋锁lock,假设可以立即获得锁,它就立即返回,否则,它将自旋在那里,直到该自旋锁的保持者释放。这时,它获得锁并返回。总之。仅仅有它获得锁才返回。

spin_lock_irqsave(lock, flags)

该宏获得自旋锁的同一时候把标志寄存器的值保存到变量flags中并失效本地中断。

spin_lock_irq(lock)

该宏类似于spin_lock_irqsave,仅仅是该宏不保存标志寄存器的值。

spin_lock_bh(lock)

该宏在得到自旋锁的同一时候失效本地软中断。

spin_unlock(lock)

该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用。假设spin_trylock返回假,表明没有获得自旋锁,因此不必使用spin_unlock释放。

spin_unlock_irqrestore(lock, flags)

该宏释放自旋锁lock的同一时候。也恢复标志寄存器的值为变量flags保存的值。

它与spin_lock_irqsave配对使用。

spin_unlock_irq(lock)

该宏释放自旋锁lock的同一时候,也使能本地中断。它与spin_lock_irq配相应用。

spin_unlock_bh(lock)

该宏释放自旋锁lock的同一时候,也使能本地的软中断。

它与spin_lock_bh配对使用。

spin_trylock_irqsave(lock, flags) 

该宏假设获得自旋锁lock。它也将保存标志寄存器的值到变量flags中,而且失效本地中断,假设没有获得锁,它什么也不做。

因此假设可以马上获得锁。它等同于spin_lock_irqsave,假设不能获得锁。它等同于spin_trylock。

假设该宏获得自旋锁lock,那须要使用spin_unlock_irqrestore来释放。

spin_trylock_irq(lock)

该宏类似于spin_trylock_irqsave,仅仅是该宏不保存标志寄存器。

假设该宏获得自旋锁lock,须要使用spin_unlock_irq来释放。

spin_trylock_bh(lock)

该宏假设获得了自旋锁。它也将失效本地软中断。假设得不到锁。它什么也不做。因此,假设得到了锁,它等同于spin_lock_bh,假设得不到锁,它等同于spin_trylock。假设该宏得到了自旋锁。须要使用spin_unlock_bh来释放。

spin_can_lock(lock)

该宏用于推断自旋锁lock是否可以被锁,它实际是spin_is_locked取反。假设lock没有被锁,它返回真,否则,返回假。该宏在2.6.11中第一次被定义,在先前的内核中并没有该宏。

获得自旋锁和释放自旋锁有好几个版本号,因此让读者知道在什么样的情况下使用什么版本号的获得和释放锁的宏是很必要的。

假设被保护的共享资源仅仅在进程上下文訪问和软中断上下文訪问,那么当在进程上下文訪问共享资源时。可能被软中断打断,从而可能进入软中断上下文来对被保护的共享资源訪问。因此对于这样的情况,对共享资源的訪问必须使用spin_lock_bh和spin_unlock_bh来保护。

当然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也能够。它们失效了本地硬中断,失效硬中断隐式地也失效了软中断。可是使用spin_lock_bh和spin_unlock_bh是最恰当的。它比其它两个快。

假设被保护的共享资源仅仅在进程上下文和tasklet或timer上下文訪问。那么应该使用与上面情况同样的获得和释放锁的宏,由于tasklet和timer是用软中断实现的。

假设被保护的共享资源仅仅在一个tasklet或timer上下文訪问,那么不须要不论什么自旋锁保护,由于同一个tasklet或timer仅仅能在一个CPU上执行,即使是在SMP环境下也是如此。实际上tasklet在调用tasklet_schedule标记其须要被调度时已经把该tasklet绑定到当前CPU,因此同一个tasklet决不可能同一时候在其它CPU上执行。

timer也是在其被使用add_timer加入到timer队列中时已经被帮定到当前CPU,所以同一个timer绝不可能执行在其它CPU上。

当然同一个tasklet有两个实例同一时候执行在同一个CPU就更不可能了。

假设被保护的共享资源仅仅在两个或多个tasklet或timer上下文訪问,那么对共享资源的訪问仅须要用spin_lock和spin_unlock来保护,不必使用_bh版本号,由于当tasklet或timer执行时,不可能有其它tasklet或timer在当前CPU上执行。

假设被保护的共享资源仅仅在一个软中断(tasklet和timer除外)上下文訪问,那么这个共享资源须要用spin_lock和spin_unlock来保护,由于相同的软中断能够同一时候在不同的CPU上执行。

假设被保护的共享资源在两个或多个软中断上下文訪问,那么这个共享资源当然更须要用spin_lock和spin_unlock来保护,不同的软中断可以同一时候在不同的CPU上执行。

假设被保护的共享资源在软中断(包含tasklet和timer)或进程上下文和硬中断上下文訪问,那么在软中断或进程上下文訪问期间。可能被硬中断打断,从而进入硬中断上下文对共享资源进行訪问。因此。在进程或软中断上下文须要使用spin_lock_irq和spin_unlock_irq来保护对共享资源的訪问。

而在中断处理句柄中使用什么版本号。需依情况而定,假设仅仅有一个中断处理句柄訪问该共享资源,那么在中断处理句柄中仅须要spin_lock和spin_unlock来保护对共享资源的訪问就能够了。

由于在运行中断处理句柄期间,不可能被同一CPU上的软中断或进程打断。可是假设有不同的中断处理句柄訪问该共享资源。那么须要在中断处理句柄中使用spin_lock_irq和spin_unlock_irq来保护对共享资源的訪问。

在使用spin_lock_irq和spin_unlock_irq的情况下,全然能够用spin_lock_irqsave和spin_unlock_irqrestore代替,那详细应该使用哪一个也须要依情况而定。假设能够确信在对共享资源訪问前中断是使能的,那么使用spin_lock_irq更好一些。

由于它比spin_lock_irqsave要快一些。可是假设你不能确定是否中断使能,那么使用spin_lock_irqsave和spin_unlock_irqrestore更好,由于它将恢复訪问共享资源前的中断标志而不是直接使能中断。

当然。有些情况下须要在訪问共享资源时必须中断失效。而訪问完后必须中断使能,这种情形使用spin_lock_irq和spin_unlock_irq最好。

须要特别提醒读者,spin_lock用于阻止在不同CPU上的运行单元对共享资源的同一时候訪问以及不同进程上下文互相抢占导致的对共享资源的非同步訪问。而中断失效和软中断失效却是为了阻止在同一CPU上软中断或中断对共享资源的非同步訪问。

參考资料

Kernel Locking Techniques,http://www.linuxjournal.com/article/5833

Redhat 9.0 kernel source tree

kernel.org 2.6.12 source tree

Linux 2.6内核中新的锁机制--RCU(Read-Copy Update),

http://www.ibm.com/developerworks/cn/linux/l-rcu/

Unreliable Guide To Locking.

Linux内核的同步机制---自旋锁的更多相关文章

  1. Linux内核同步机制--自旋锁【转】

    本文转载自:http://www.cppblog.com/aaxron/archive/2013/04/12/199386.html 自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已 ...

  2. Linux内核的同步机制

    本文详细的介绍了Linux内核中的同步机制:原子操作.信号量.读写信号量和自旋锁的API,使用要求以及一些典型示例 一.引言 在现代操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实象多进程 ...

  3. linux内核级同步机制--futex

    在面试中关于多线程同步,你必须要思考的问题 一文中,我们知道glibc的pthread_cond_timedwait底层是用linux futex机制实现的. 理想的同步机制应该是没有锁冲突时在用户态 ...

  4. linux 内核的另一个自旋锁 - 读写锁

    除spinlock外,linux 内核还有一个自旋锁,名为arch_rwlock_t.它的头文件是qrwlock.h,包含在spinlock.h,头文件中对它全称为"Queue read/w ...

  5. Linux内核同步:自旋锁

    linux内核--自旋锁的理解 自旋锁:如果内核配置为SMP系统,自旋锁就按SMP系统上的要求来实现真正的自旋等待,但是对于UP系统,自旋锁仅做抢占和中断操作,没有实现真正的“自旋”.如果配置了CON ...

  6. Linux 内核同步之自旋锁与信号量的异同【转】

    转自:http://blog.csdn.net/liuxd3000/article/details/8567070 Linux 设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发访问会导 ...

  7. Linux内核抢占实现机制分析【转】

    Linux内核抢占实现机制分析 转自:http://blog.chinaunix.net/uid-24227137-id-3050754.html [摘要]本文详解了Linux内核抢占实现机制.首先介 ...

  8. Linux 下的同步机制

    2017-03-10 回想下最初的计算机设计,在单个CPU的情况下,同一时刻只能由一个线程(在LInux下为进程)占用CPU,且2.6之前的Linux内核并不支持内核抢占,当进程在系统地址运行时,能打 ...

  9. Linux内核态抢占机制分析(转)

    Linux内核态抢占机制分析  http://blog.sina.com.cn/s/blog_502c8cc401012pxj.html 摘 要]本文首先介绍非抢占式内核(Non-Preemptive ...

随机推荐

  1. C++ Primer 学习笔记_60_重载操作符与转换 --赋值、下标、成员訪问操作符

    重载操作符与转换 --赋值.下标.成员訪问操作符 一.赋值操作符 类赋值操作符接受类类型形參,通常该形參是对类类型的const引用,但也能够是类类型或对类类型的非const引用.假设未定义这个操作符, ...

  2. Android之后台服务判断本应用Activity是否处于栈顶

    在Android开发中,我们经常想知道是否自己的服务处于后台运行中,因为在后台运行的服务器优先级会降低,也就极有可能会被系统给回收掉,有什么好办法呢?Google推荐我们将服务运行到前台,如何知道服务 ...

  3. TPL异步并行编程之任务超时

    此处参考自阿涛的博文:http://www.cnblogs.com/HelloMyWorld/p/5526914.html 一 自己定义 基本的思路: net中异步操作由于是交给线程来实现,因此不可能 ...

  4. 用VC实现竖写汉字的方法

    中国人自古就有自右至左.从上到下书写汉字的习惯.而当我们在自己所编写的应用程序中使用输出函数输出的总是自左至右的横排文字.有没有可能在我们的应用程序中实现竖写汉字的效果呢?笔者偶然发现了一种利用VC实 ...

  5. Ubuntu通过源代码编译安装Octave 4.0

    本教程/笔记,意在指导在Ubuntu及其它Linux系统上怎样通过源代码安装Octave. Octave简单介绍 Octave是GNU旗下取代matlab的数学工具软件,语法与matlab高度兼容.而 ...

  6. 应用程序初始化正常(0xc015002)失败解决方法

    VS2005 sidebyside manifest error Microsoft.VC80.MFC Microsoft.VC80.CRT Microsoft.VC80.MFCLOC msvcr80 ...

  7. VC 中与字符串相关的宏 _T、TEXT,_TEXT、L 的作用(简单明了)

    一. 在字符串前加一个L作用:    如  L"我的字符串"    表示将ANSI字符串转换成unicode的字符串,就是每个字符占用两个字节.   strlen("as ...

  8. rsync使用指南

    考虑到服务器数据的安全,我考虑增加一台备份服务器,通过数据同步,达到较好的冗余. linux下有非常好的一个命令rsync可以实现差异备份,下面就说说它的用法:ubuntu缺省安装的安装中,rsync ...

  9. JavaScript面向对象编程(10)高速构建继承关系之对象拷贝

    前面的样例我们是通过构造器创建对象.而且希望该对象继承来自另外一个构造器的对象 我们也能够直接面向一个对象来达成继承的目的.使用下属步骤: 1.拷贝一个对象 2.给新对象加入属性 /** * 通过拷贝 ...

  10. codeforces.com/contest/325/problem/B

    http://codeforces.com/contest/325/problem/B B. Stadium and Games time limit per test 1 second memory ...