转自:http://blog.csdn.net/droidphone/article/details/7395983

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文不打算详细探究spin_lock的详细实现机制,只是最近对raw_spin_lock的出现比较困扰,搞不清楚什么时候用spin_lock,什么时候用raw_spin_lock,因此有了这篇文章。

/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.NET/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/

1.  临界区(Critical Section)

我们知道,临界区是指某个代码区间,在该区间中需要访问某些共享的数据对象,又或者是总线,硬件寄存器等,通常这段代码区间的范围要控制在尽可能小的范围内。临界区内需要对这些数据对象和硬件对象的访问进行保护,保证在退出临界区前不会被临界区外的代码对这些对象进行修改。出现以下几种情形时,我们需要使用临界区进行保护:
  • (1)  在可以抢占(preemption)的系统中,两个线程同时访问同一个对象;
  • (2)  线程和中断同时访问同一个对象;
  • (3)  在多核系统中(SMP),可能两个CPU可能同时访问同一个对象;

2.  自旋锁(spin_lock)

针对单处理器系统,对第一种情况,只要临时关闭系统抢占即可,我们可以使用以下方法:
  1. preempt_disable();
  2. .....
  3. // 访问共享对象的代码
  4. ......
  5. preempt_enable();

同样地,针对单处理器系统,第二种情况,只要临时关闭中断即可,我们可以使用以下方法:

  1. local_irq_disable();
  2. ......
  3. // 访问共享对象的代码
  4. ......
  5. local_irq_enable();

那么,针对多处理器的系统,以上的方法还成立么?答案很显然:不成立。

对于第一种情况,虽然抢占被禁止了,可是另一个CPU上还有线程在运行,如果这个线程也正好要访问该共享对象,上面的代码段显然是无能为力了。

对于第二种情况,虽然本地CPU的中断被禁止了,可是另一个CPU依然可以产生中断,如果他的中断服务程序也正好要访问该共享对象,上面的代码段也一样无法对共享对象进行保护。

实际上,在linux中,上面所说的三种情况都可以用自旋锁(spin_lock)解决。基本的自旋锁函数对是:
  • spin_lock(spinlock_t *lock);
  • spin_unlock(spinlock_t *lock);

对于单处理器系统,在不打开调试选项时,spinlock_t实际上是一个空结构,把上面两个函数展开后,实际上就只是调用preempt_disable()和preempt_enable(),对于单处理器系统,关掉抢占后,其它线程不能被调度运行,所以并不需要做额外的工作,除非中断的到来,不过内核提供了另外的变种函数来处理中断的问题。

对于多处理器系统,spinlock_t实际上等效于内存单元中的一个整数,内核保证spin_lock系列函数对该整数进行原子操作,除了调用preempt_disable()和preempt_enable()防止线程被抢占外,还必须对spinlock_t上锁,这样,如果另一个CPU的代码要使用该临界区对象,就必须进行自旋等待。
对于中断和普通线程都要访问的对象,内核提供了另外两套变种函数:
  • spin_lock_irq(spinlock_t *lock);
  • spin_unlock_irq(spinlock_t *lock);

和:

  • spin_lock_irqsave(lock, flags);
  • spin_lock_irqrestore(lock, flags);

我们可以按以下原则使用上面的三对变种函数(宏):

  • 如果只是在普通线程之间同时访问共享对象,使用spin_lock()/spin_unlock();
  • 如果是在中断和普通线程之间同时访问共享对象,并且确信退出临界区后要打开中断,使用spin_lock_irq()/spin_unlock_irq();
  • 如果是在中断和普通线程之间同时访问共享对象,并且退出临界区后要保持中断的状态,使用spin_lock_irqsave()/spin_unlock_irqrestore();

其实变种还不止这几个,还有read_lock_xxx/write_lock_xxx、spin_lock_bh/spin_unlock_bh、spin_trylock_xxx等等。但常用的就上面几种。

3.  raw_spin_lock

在2.6.33之后的版本,内核加入了raw_spin_lock系列,使用方法和spin_lock系列一模一样,只是参数有spinlock_t变为了raw_spinlock_t。而且在内核的主线版本中,spin_lock系列只是简单地调用了raw_spin_lock系列的函数,但内核的代码却是有的地方使用spin_lock,有的地方使用raw_spin_lock。是不是很奇怪?要解答这个问题,我们要回到2004年,MontaVista
Software, Inc的开发人员在邮件列表中提出来一个Real-Time
Linux Kernel的模型,旨在提升Linux的实时性,之后Ingo
Molnar很快在他的一个项目中实现了这个模型,并最终产生了一个Real-Time preemption的patch。
该模型允许在临界区中被抢占,而且申请临界区的操作可以导致进程休眠等待,这将导致自旋锁的机制被修改,由原来的整数原子操作变更为信号量操作。当时内核中已经有大约10000处使用了自旋锁的代码,直接修改spin_lock将会导致这个patch过于庞大,于是,他们决定只修改哪些真正不允许抢占和休眠的地方,而这些地方只有100多处,这些地方改为使用raw_spin_lock,但是,因为原来的内核中已经有raw_spin_lock这一名字空间,用于代表体系相关的原子操作的实现,于是linus本人建议:
  • 把原来的raw_spin_lock改为arch_spin_lock;
  • 把原来的spin_lock改为raw_spin_lock;
  • 实现一个新的spin_lock;

写到这里不知大家明白了没?对于2.6.33和之后的版本,我的理解是:

  • 尽可能使用spin_lock;
  • 绝对不允许被抢占和休眠的地方,使用raw_spin_lock,否则使用spin_lock;
  • 如果你的临界区足够小,使用raw_spin_lock;

对于没有打上Linux-RT(实时Linux)的patch的系统,spin_lock只是简单地调用raw_spin_lock,实际上他们是完全一样的,如果打上这个patch之后,spin_lock会使用信号量完成临界区的保护工作,带来的好处是同一个CPU可以有多个临界区同时工作,而原有的体系因为禁止抢占的原因,一旦进入临界区,其他临界区就无法运行,新的体系在允许使用同一个临界区的其他进程进行休眠等待,而不是强占着CPU进行自旋操作。写这篇文章的时候,内核的版本已经是3.3了,主线版本还没有合并Linux-RT的内容,说不定哪天就会合并进来,也为了你的代码可以兼容Linux-RT,最好坚持上面三个原则。

自旋锁spin_lock和raw_spin_lock【转】的更多相关文章

  1. 自旋锁spin_lock和raw_spin_lock

    自旋锁spin_lock和raw_spin_lock Linux内核spin_lock.spin_lock_irq 和 spin_lock_irqsave 分析 http://blog.csdn.ne ...

  2. [内核同步]自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave 分析

    转自:http://blog.csdn.net/wh_19910525/article/details/11536279 自旋锁的初衷:在短期间内进行轻量级的锁定.一个被争用的自旋锁使得请求它的线程在 ...

  3. [内核同步]自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave 分析【转】

    转自:https://www.cnblogs.com/x_wukong/p/8573602.html 转自;https://www.cnblogs.com/aaronLinux/p/5890924.h ...

  4. 自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave

    自旋锁和互斥锁的区别是,自旋锁不会引起睡眠,所以可用于不能休眠的代码中(如IRQ) 自旋锁保持期间抢占失效,而信号量保持期间可以被抢占 定义 spinlock_t lock; init #define ...

  5. 漫画|Linux 并发、竞态、互斥锁、自旋锁、信号量都是什么鬼?(转)

    知乎链接:https://zhuanlan.zhihu.com/p/57354304 1. 锁的由来? 学习linux的时候,肯定会遇到各种和锁相关的知识,有时候自己学好了一点,感觉半桶水的自己已经可 ...

  6. 自旋锁spinlock

    1 在单处理器上的实现 单核系统上,不存在严格的并发,因此对资源的共享主要是多个任务分时运行造成的. 只要在某一时段,停止任务切换,并且关中断(对于用户态应用程序,不大可能与中断处理程序抢临界区资源) ...

  7. LINUX内核笔记:自旋锁

    目录 自旋锁作用与基本使用方法? 在SMP和UP上的不同表现? 自旋锁与上下文 使用spin_lock()后为什么不能睡眠? 强调:锁什么? 参考   1.自旋锁作用与基本使用方法? 与其他锁一样,自 ...

  8. spinlock自旋锁de使用

    Linux内核中最常见的锁是自旋锁.一个自旋锁就是一个互斥设备,它只能有两个值:"锁定"和"解锁".如果锁可用,则"锁定"位被设置,而代码继 ...

  9. linux内核--自旋锁的理解

    http://blog.chinaunix.net/uid-20543672-id-3252604.html 自旋锁:如果内核配置为SMP系统,自旋锁就按SMP系统上的要求来实现真正的自旋等待,但是对 ...

随机推荐

  1. 团队项目-第五次Scrum 会议

    时间:10.31 时长:30分钟 地点:教室(主南201) 工作情况 团队成员 已完成任务 待完成任务 解小锐 修复在接受任务时,前端和后端对接中的bug 完成员工信息的简单初始化 陈鑫 完成hire ...

  2. Java 8手动实现一个Collector

    我们看一下Stream中的collect的方法. collect(toList())方法由Stream里的值生成一个列表,是一个及早求值的操作. Stream的of方法使用一个初始值生成新的Strea ...

  3. python 生成csv乱码问题解决方法

    需求背景 最近为公司开发了一套邮件日报程序,邮件一般就是表格,图片,然后就是附件.附件一般都是默认写到txt文件里,但是PM希望邮件里的附件能直接用Excel这种软件打开,最开始想保存为Excel,但 ...

  4. HDU I-最少拦截系统

    http://acm.hdu.edu.cn/showproblem.php?pid=1257 Problem Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦 ...

  5. PHPExcel 导出包含图片excel

    <?php // 这里用的PHPExcel版本号为1.8.0 // 下载地址https://github.com/PHPOffice/PHPExcel 下载ZIP压缩包 // 下载后将Class ...

  6. P4109 [HEOI2015]定价

    题目描述 在市场上有很多商品的定价类似于 999 元.4999 元.8999 元这样.它们和 1000 元.5000 元和 9000 元并没有什么本质区别,但是在心理学上会让人感觉便宜很多,因此也是商 ...

  7. [CF45G]Prime Problem

    题目大意:将$1$到$n(1<n\leqslant6000)$分成若干组数,要求每组数的和均为质数,若存在一种分配方式,输出每个数所在的组的编号,有多组解输出任意一组解,若不存在,输出$-1$ ...

  8. BZOJ2115 [Wc2011] Xor 【线性基】

    2115: [Wc2011] Xor Time Limit: 10 Sec  Memory Limit: 259 MB Submit: 3915  Solved: 1633 [Submit][Stat ...

  9. istringstream()函数的用法

    istringstream()函数的用法 头文件:#include 功能:将一个含有多个空格的字符串分割开来 eg:

  10. java 多线程 原子性

    原子性 原子性:原子操作是不能被线程调度机制中断的操作,一旦操作开始,那么它就一定可以在可能发生的“上下文切换”之前(切换到其他线程执行)执行完毕. 依赖原子性是很棘手且很危险的,除非你是并发专家,否 ...