本文来自:我爱研发网(52RD.com) - R&D大本营
详细出处:http://www.52rd.com/Blog/Archive_Thread.asp?SID=7179

spinlock的设计和实现

作者:admin
在Linux的内核中,spin lock用在多处理器环境中。当一个CPU访问一个临界资源
(critical section)的时候,需要预先取得spin lock,如果取不到的话,它就在空循环
等待,直到另外的CPU释放spin lock。由于涉及到多个处理器,spin lock的效率非常重要。
因为在等待spin lock的过程,处理器只是不停的循环检查,并不执行其他指令。但即使这样,
一般来说,spn lock的开销还是比进程调度(context switch)少得多。这就是spin lock
被广泛应用在多处理器环境的原因。

1. spin lock的数据结构

/* include/asm-i386/spinlock.h */ 

typedef struct { 
volatile unsigned int lock; 
} spinlock_t; 

spin lock的数据结构很简单,只是一个整数变量lock, 如果lock等于1的话,表示
这个spin lock是自由的;如果lock小于等于0的话,则表示spin lock已经被其他CPU所
获取。

2. spin lock的实现

#define spin_lock_string 
"n1:t" 
"lock ; decb %0nt" 
"js 2fn" 
".section .text.lock,"ax"n" 
"2:t" 
"cmpb $0,%0nt" 
"rep;nopnt" 
"jle 2bnt" 
"jmp 1bn" 
".previous"  #define spin_unlock_string 
"movb $1,%0" 
:"=m" (lock->lock) : : "memory"  static inline void spin_lock(spinlock_t *lock) 

__asm__ __volatile__( 
spin_lock_string 
:"=m" (lock->lock) : : "memory"); 
}  static inline void spin_unlock(spinlock_t *lock) 

char oldval = 1;  __asm__ __volatile__( 
spin_unlock_string 
); 
}

如果将上面的语句转化成纯汇编的话,则是这样:

spin_lock(lock) 

1: 
lock ; decb %0 
js 2f  .section .text.lock, "ax" 
2: cmpb $0,%0 
rep;nop 
jle 2b 
jmp 1b 
.previous 

其中%0就是函数参数传进来的lock->lock,下面详细地解释一下每一条
汇编指令:
* lock ; decb %0
decb将lock->lock减1,它前边的lock指令表示在执行decb的时候,要锁住
内存总线(memory bus),另外的CPU不能访问内存,以保证decb指令的原子性。
注意,decb并不是原子操作(atomic operation),它需要将变量从内存读出来,
放入寄存器(register),减1,再写入内存。如果在这时候另外的CPU也进行同样的操作的
时候,那么decb的执行结果就会不确定,也就是说,操作的原子性遭到了破坏。

* js 2f
如果decb的结果小于0,表示无法取得spin lock,则跳到标签为2的指令(f表示向前跳)。
如果decb的结果等于0,表示已经获得spin lock,执行下一条指令,则跳出整段代码,函数返回。
注意, "j2 2f"的下一条指令并不是"cmpb $0,%0"。

* .section .text.lock, "ax"
.previous
从.section到.previous的这一段代码被用来检测spin lock何时被释放。linux定义了一个
专门的区(.text.lock)来存放这段代码。它们和前边的"js 2f"并不在一个区(section)里,
    所以说"js 2f"的下一条指令并不是"cmpb $0,%0"。
    之所以定义成一个单独的区,原因是在大多数情况下,spin lock是能获取成功的,从.section
    到.previous的这一段代码并不经常被调用,如果把它跟别的常用指令混在一起,会浪费指令
    缓存的空间。从这里也可以看出,linux内核的实现,要时时注意效率。

* 2: cmpb $0,%0
  rep;nop
jle 2b
jmp 1b
检查lock->lock,和0比较,如果小于等于0(jle 2b),则跳回到标签2的指令,重新比较
(b表示往回跳)。如果大于0,表示spin lock已经被释放,则往回跳回到标签1,重新试图
     取得spin lock。

  * rep;nop
这是一条很有趣的指令:),咋一看,这只是一条空指令,但实际上这条指令可以降低CPU的运行
    频率,减低电的消耗量,但最重要的是,提高了整体的效率。因为这段指令执行太快的话,会生成
    很多读取内存变量的指令,另外的一个CPU可能也要写这个内存变量,现在的CPU经常需要重新
    排序指令来提高效率,如果读指令太多的话,为了保证指令之间的依赖性,CPU会以牺牲流水线
    执行(pipeline)所带来的好处。从pentium 4以后,intel引进了一条pause指令,专门
    用于spin lock这种情况,据intel的文档说,加上pause可以提高25倍的效率!

spin_unlock(lock)
  * movb $1,%0
spin_unlock的实现很简单,只是重新将lock->lock置1就行了。

  还有一个问题我想谈的是,在linux 2.3以前,spin lock是用"lock; btrl $0,%0"来实现
加锁的,但是后来的版本只使用了简单的mov指令,执行时间从22个时钟周期降低到1个时钟周期。
但是最开始linus本人不同意这种做法,因为他以为由于intel芯片的指令重排序,会使斯spin lock
的实现不稳定,但后来intel里的一个工程师出来澄清了linus的错误。这也许是open source的好处吧。

  spin lock的实现看起来简单,但是细微之处却很复杂,如果大家需要进一步理解,请细细读一下
kernel的mail list和intel关于pentium的文档。

tielian ps:
nop指令前加rep前缀意思是:Spin-Wait and Idle Loops
p4有一个新指令pause opcode也是0f390h

spinlock一边连逻辑一边连控制器的更多相关文章

  1. 第三百零四节,Django框架,urls.py模块,views.py模块,路由映射与路由分发以及逻辑处理——url控制器

    Django框架,urls.py模块,views.py模块,路由映射与路由分发以及逻辑处理——url控制器 这一节主讲url控制器 一.urls.py模块 这个模块是配置路由映射的模块,当用户访问一个 ...

  2. 二 Django框架,urls.py模块,views.py模块,路由映射与路由分发以及逻辑处理——url控制器

    Django框架,urls.py模块,views.py模块,路由映射与路由分发以及逻辑处理——url控制器 这一节主讲url控制器 一.urls.py模块 这个模块是配置路由映射的模块,当用户访问一个 ...

  3. 十、EnterpriseFrameWork框架的分层架构及意义(控制器、业务对象、实体、Dao之间关系)

    本章内容主要包括两个方面,一.是框架分层(控制器.业务对象.实体.Dao)的详细说明,二.是对比常用三层结构的区别和优势: 本文要点: 1.框架中的各个分层详细说明 2.对比常用三层结构的区别和优势 ...

  4. iOS控制器瘦身-面向超类编程

    今天写这篇文章的目的,是提供一种思路,来帮助大家解决控制器非常臃肿的问题,对控制器瘦身. 滴滴 老司机要开车了 如果手边有项目,不妨打开工程看一下你的控制器代码有多少行,是不是非常多?再看一下tabl ...

  5. iOS开发 - 一个天真的搜索控制器的独白

    文/Azen(简书作者)原文链接:http://www.jianshu.com/p/6d5327111511著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. 正文 一.关于横向模块开发 ...

  6. angular.js 中的作用域 数据模型 控制器

    1.angular.js 作为后起之秀的前端mvc框架,他于传统的前端框架都不同,我们再也不需要在html中嵌入脚本来操作对象了.它抽象出了数据模型,控制器及视图. 成功解耦了应用逻辑,数据模型,视图 ...

  7. laravel6.0控制器-资源控制器

    控制器:控制器用来处理业务的,不应该处理逻辑,如果是小项目可以把逻辑写到控制器里,大点的项目应该抽离出来业务处理层如下:services业务处理层:比如:获取值,验证值,异常捕获命名规则:控制器名:用 ...

  8. [2018-01-12] laravel--路由(路由与控制器)

    路由只用来接收请求 目前我们大致了解了laravel,在开始一个Http程序需要先定义路由.之前的例子中,我们的业务逻辑都是在路由里实现的,这对于简单的网站或web应用没什么问题,当我们需要扩大规模, ...

  9. Laravel 控制器 Controller

    一.控制器存在的意义 路由可以分发请求:路由中还可以引入 html 页面:我们可以在 route/web.php 中搞定一切了:但是如果把业务逻辑都写入到路由中:那路由将庞大的难以维护:于是控制器就有 ...

随机推荐

  1. Atitti dbutil获取多个返回结果集的解决

    Atitti dbutil获取多个返回结果集的解决 1.1. 多个select默认只返回第一个resultset1 1.2. 调用存储过程,也是返回第一个select的1 1.3. 如果insert前 ...

  2. [svc]linux性能监控

    参考 w - Show who is logged on and what they are doing. [root@n1 ~]# w # w - Show who is logged on and ...

  3. 淘宝分布式数据层TDDL

    剖析淘宝 TDDL ( TAOBAO DISTRIBUTE DATA LAYER ) 注:原文:http://gao-xianglong.iteye.com/blog/1973591   前言 在开始 ...

  4. HTML5学习笔记(二十):JavaScript中的标准对象

    这里提到的标准对象指ECMAScript中定义的对象,无论JavaScript运行那种环境(浏览器.Node.js)下都存在的对象. typeof 在JavaScript的世界里,一切都是对象. 但是 ...

  5. 一文读懂Redis持久化

    Redis 是一个开源( BSD 许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件.它支持的数据类型很丰富,如字符串.链表.集合.以及散列等,并且还支持多种排序功能. 什么叫持久 ...

  6. Linux/CentOS关闭图形界面(X-window)和启用图形界面命令

    1.在图像界面关闭x window:1.1 shell中运行 init 3  进入文本模式,同时会关闭相关的服务(Xserver 肯定关闭)1.2 Alt+Ctrl+F1~F6到字符界面,root登陆 ...

  7. 数据加密 - TDE透明数据加密原理

    首先需要确定你需要加密的列,Oracle 10g数据库将为包含加密列的表创建一个私密的安全加密密钥(表秘钥), 然后采用你指定的加密算法(AES或3DES)加密指定列的明文数据.此时,保护表的加密密钥 ...

  8. cxf、struts、spring中web.xml过滤url问题解决方案

    利用struts2自带的正则匹配,应该说这算是最官方的解决方案了 在struts.properties中加正则匹配 struts.action.excludePattern=/webservice/. ...

  9. 使用TCP协议的NAT穿透技术(转)

    其实很早我就已经实现了使用TCP协议穿透NAT了,但是苦于一直没有时间,所以没有写出来,现在终于放假有一点空闲,于是写出来共享之. 一直以来,说起NAT穿透,很多人都会被告知使用UDP打孔这个技术,基 ...

  10. Windows下GUI编程——窗口

    windows下创建一个基于GUI的窗口程序很简单,使用MFC或者Win32 API都可以实现.本文简单整理下windows API创建GUI应用程序的基本编码框架. 比较常见的窗口包括:桌面窗口.应 ...