LINUX内核之普通自旋锁
@CopyLeft by ICANTH,I Can do ANy THing that I CAN THink!~
Author:WenHui,WuHan University,2012-6-15
PDF版阅读地址:http://www.docin.com/p1-424285718.html
普通自旋锁
自旋锁最常见的使用场景是创建一段临界区 :
static DEFINE_SPINLOCK(xxx_lock);
unsigned long flags;
spin_lock_irqsave(&xxx_lock, flags);
... critical section here ..
spin_unlock_irqrestore(&xxx_lock, flags);
自旋锁使用时值得注意的是:对于采用使用自旋锁以保证共享变量的存取安全时,仅当系统中所有涉及到存取该共享变量的程序部分都采用成对的spin_lock、和spin_unlock来进行操作才能保证其安全性。
NOTE! The spin-lock is safe only when you _also_ use the lock itself to do locking across CPU's, which implies that EVERYTHING that touches a shared variable has to agree about the spinlock they want to use.
在Linux2.6.15.5中,自旋体数据结构如下:
当配置CONFIG_SMP时,raw_spinlock_t才是一个含有slock变量的结构,该slock字段标识自旋锁是否空闲状态,用以处理多CPU处理器并发申请锁的情况;当未配置CONFIG_SMP时,对于单CPU而言,不会发生发申请自旋锁,故raw_lock为空结构体。
当配置CONFIG_SMP和CONFIG_PREEMPT时,spinlock_t才会有break_lock字段,break_lock字段用于标记自旋锁竞争状态,当break_lock = 0时表示没有多于两个的执行路径,当break_lock = 1时表示没有其它进程在忙等待该锁。当在SMP多CPU体系架构下有可能出现申请不到自旋锁、空等的情况,但LINUX内核必须保证在spin_lock的原子性,故在配置CONFIG_PREEMPT时必须禁止内核抢占。
字段 |
描述 |
spin_lock_init(lock) |
一个自旋锁时,可使用接口函数将其初始化为锁定状态 |
spin_lock(lock) |
用于锁定自旋锁,如果成功则返回;否则循环等待自旋锁变为空闲 |
spin_unlock(lock) |
释放自旋锁lock,重新设置自旋锁为锁定状态 |
spin_is_locked(lock) |
判断当前自旋锁是否处于锁定状态 |
spin_unlock_wait(lock) |
循环等待、直到自旋锁lock变为可用状态 |
spin_trylock(lock) |
尝试锁定自旋锁lock,如不成功则返回0;否则锁定,并返回1 |
spin_can_lock(lock) |
判断自旋锁lock是否处于空闲状态 |
spin_lock和spin_unlock的关系如下:
可见,在UP体系架构中,由于没有必要有实际的锁以防止多CPU抢占,spin操作仅仅是禁止和开启内核抢占。
LINUX 2.6.35版本,将spin lock实现更改为 ticket lock。spin_lock数据结构除了用于内核调试之外,字段为:raw_spinlock rlock。
ticket spinlock将rlock字段分解为如下两部分:
Next是下一个票号,而Owner是允许使用自旋锁的票号。加锁时CPU取Next,并将rlock.Next + 1。将Next与Owner相比较,若相同,则加锁成功;否则循环等待、直到Next = rlock.Owner为止。解锁则直接将Owner + 1即可。
spin_lock和spin_unlock的调用关系如下:
普通自旋锁源码分析
源程序文件目录关系图
在/include/linux/spinlock.h中通过是否配置CONFIG_SMP项判断导入哪种自旋锁定义及操作:
004 /*
005 * include/linux/spinlock.h - generic spinlock/rwlock declarations
007 * here's the role of the various spinlock/rwlock related include files:
009 * on SMP builds:
011 * asm/spinlock_types.h: contains the arch_spinlock_t/arch_rwlock_t and the
012 * initializers
014 * linux/spinlock_types.h:
015 * defines the generic type and initializers
017 * asm/spinlock.h: contains the arch_spin_*()/etc. lowlevel
018 * implementations, mostly inline assembly code
022 * linux/spinlock_api_smp.h:
023 * contains the prototypes for the _spin_*() APIs.
025 * linux/spinlock.h: builds the final spin_*() APIs.
027 * on UP builds:
029 * linux/spinlock_type_up.h:
030 * contains the generic, simplified UP spinlock type.
031 * (which is an empty structure on non-debug builds)
033 * linux/spinlock_types.h:
034 * defines the generic type and initializers
036 * linux/spinlock_up.h:
037 * contains the arch_spin_*()/etc. version of UP
038 * builds. (which are NOPs on non-debug, non-preempt
039 * builds)
041 * (included on UP-non-debug builds:)
043 * linux/spinlock_api_up.h:
044 * builds the _spin_*() APIs.
046 * linux/spinlock.h: builds the final spin_*() APIs.
047 */
082 /*
083 * Pull the arch_spin*() functions/declarations (UP-nondebug doesnt need them):
084 */
085 #ifdef CONFIG_SMP
086 # include <asm/spinlock.h>
087 #else
088 # include <linux/spinlock_up.h>
089 #endif
064 typedef struct spinlock {
065 union {
066 struct raw_spinlock rlock;
075 };
076 } spinlock_t;
282 static inline void spin_lock(spinlock_t *lock)
283 {
284 raw_spin_lock(&lock->rlock);
285 }
169 #define raw_spin_lock(lock) _raw_spin_lock(lock)
322 static inline void spin_unlock(spinlock_t *lock)
323 {
324 raw_spin_unlock(&lock->rlock);
325 }
222 #define raw_spin_unlock(lock) _raw_spin_unlock(lock)
UP体系架构
/include/linux/spinlock_api_up.h
052 #define _raw_spin_lock(lock) __LOCK(lock)
021 /*
022 * In the UP-nondebug case there's no real locking going on, so the
023 * only thing we have to do is to keep the preempt counts and irq
024 * flags straight, to suppress compiler warnings of unused lock
025 * variables, and to add the proper checker annotations:
026 */
027 #define __LOCK(lock) \
028 do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)
052 #define _raw_spin_lock(lock) __LOCK(lock)
preempt_disable在未配置CONFIG_PREEMPT时为空函数,否则禁止内核抢占。而__acquire()用于内核编译过程中静态检查。(void)(lock)则是为避免编译器产生lock未被使用的警告。
spin_unlock函数在UP体系架构中最终实现方式为:
039 #define __UNLOCK(lock) \
040 do { preempt_enable(); __release(lock); (void)(lock); } while (0)
SMP体系架构-Tickect Spin Lock的实现方式
3、wait until my_ticket = slock.owner
但该锁将导致一个问题:8个bit将只能最多表示255个CPU来竞争该锁。故系统通过的方式,将实现两个tickect_spin_lock和ticket_spin_unclock的版本:
058 #if (NR_CPUS < 256)
059 #define TICKET_SHIFT 8
106 #else
107 #define TICKET_SHIFT 16
SMP体系架构-SPIN LOCK (ticket_shif 8)
046 #ifdef CONFIG_INLINE_SPIN_LOCK
047 #define _raw_spin_lock(lock) __raw_spin_lock(lock)
048 #endif
/include/linux/spinlock_api_smp.h:
140 static inline void __raw_spin_lock(raw_spinlock_t *lock)
141 {
142 preempt_disable();
143 spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
144 LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
145 }
在__raw_spin_lock中,首先禁止内核抢占,调用LOCK_CONTENED宏
391 #define LOCK_CONTENDED(_lock, try, lock) \
392 do { \
393 if (!try(_lock)) { \
394 lock_contended(&(_lock)->dep_map, _RET_IP_); \
395 lock(_lock); \
396 } \
397 lock_acquired(&(_lock)->dep_map, _RET_IP_); \
398 } while (0)
136 static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
137 {
138 __acquire(lock);
139 arch_spin_lock(&lock->raw_lock);
140 }
149 static inline int do_raw_spin_trylock(raw_spinlock_t *lock)
150 {
151 return arch_spin_trylock(&(lock)->raw_lock);
152 }
在X86平台下,do_raw_spin_lock和do_raw_spin_trylock实现为两个函数:
/arch/x86/include/asm/spinlock.h
188 static __always_inline void arch_spin_lock(arch_spinlock_t *lock)
189 {
190 __ticket_spin_lock(lock);
191 }
192
193 static __always_inline int arch_spin_trylock(arch_spinlock_t *lock)
194 {
195 return __ticket_spin_trylock(lock);
196 }
058 #if (NR_CPUS < 256)
059 #define TICKET_SHIFT 8
061 static __always_inline void __ticket_spin_lock(arch_spinlock_t *lock)
062 {
063 short inc = 0x0100;
064
065 asm volatile (
066 LOCK_PREFIX "xaddw %w0, %1\n"
067 "1:\t"
068 "cmpb %h0, %b0\n\t"
069 "je 2f\n\t"
070 "rep ; nop\n\t"
071 "movb %1, %b0\n\t"
072 /* don't need lfence here, because loads are in-order */
073 "jmp 1b\n"
074 "2:"
075 : "+Q" (inc), "+m" (lock->slock)
076 :
077 : "memory", "cc");
078 }
066行:LOCK_PREFIX在UP上为空定义,而在SMP上为Lock,用以保证从066行~074行为原子操作,强制所有CPU缓存失效。xaddw指令用法如下:
068行:比较inc中自己的Next是否与Owner中ticket相等,若相等则获取自旋锁使用权、结束循环。
070行 ~ 073行:如果Owner不属于自己,则执行空语句,并重新读取slock中的Owner,跳回至068行进行判断。
009 /*
010 * Alternative inline assembly for SMP.
011 *
012 * The LOCK_PREFIX macro defined here replaces the LOCK and
013 * LOCK_PREFIX macros used everywhere in the source tree.
014 *
015 * SMP alternatives use the same data structures as the other
016 * alternatives and the X86_FEATURE_UP flag to indicate the case of a
017 * UP system running a SMP kernel. The existing apply_alternatives()
018 * works fine for patching a SMP kernel for UP.
019 *
020 * The SMP alternative tables can be kept after boot and contain both
021 * UP and SMP versions of the instructions to allow switching back to
022 * SMP at runtime, when hotplugging in a new CPU, which is especially
023 * useful in virtualized environments.
024 *
025 * The very common lock prefix is handled as special case in a
026 * separate table which is a pure address list without replacement ptr
027 * and size information. That keeps the table sizes small.
028 */
029
030 #ifdef CONFIG_SMP
031 #define LOCK_PREFIX_HERE \
032 ".section .smp_locks,\"a\"\n" \
033 ".balign 4\n" \
034 ".long 671f - .\n" /* offset */ \
035 ".previous\n" \
036 "671:"
037
038 #define LOCK_PREFIX LOCK_PREFIX_HERE "\n\tlock; "
039
040 #else /* ! CONFIG_SMP */
041 #define LOCK_PREFIX_HERE ""
042 #define LOCK_PREFIX ""
043 #endif
032行“.section .smp_locks, a”,表示以下代码生成在.smp_locks段中,而“a”代表——allocatable。
033行~034行 “.previous”伪指令,表示恢复以前section,即代码段。故在038行将导致在代码段生成lock指令。
spin_lock,无论如何,首先调用LOCK执行原子性操作、声明ticket;而trylock则首先进行slock.Next == slock.Owner的判断,降低第二次比较调用LOCK的概率。
080 static __always_inline int __ticket_spin_trylock(arch_spinlock_t *lock)
081 {
082 int tmp, new;
083
084 asm volatile("movzwl %2, %0\n\t"
085 "cmpb %h0,%b0\n\t"
086 "leal 0x100(%" REG_PTR_MODE "0), %1\n\t"
087 "jne 1f\n\t"
088 LOCK_PREFIX "cmpxchgw %w1,%2\n\t"
089 "1:"
090 "sete %b1\n\t"
091 "movzbl %b1,%0\n\t"
092 : "=&a" (tmp), "=&q" (new), "+m" (lock->slock)
093 :
094 : "memory", "cc");
095
096 return tmp;
097 }
085行 比较tmp.next == tmp.owner,判断当前自旋锁是否空闲。
“movl (%0 + 0x100),%1”,此时new = { tmp.Next + 1, tmp.Owner }。
087行 若tmp.next != tmp.owner,即自旋锁不空闲,则跳到089行将0赋值给tmp并返回。
“tmp.Next == lock.slock.Next ? lock.slock = new : tmp = lock.slock”
若Next未发生变化,则将lock.slock更新为new,实质上是将slock的Next+1。
090行 执行sete指令,若cmpxchgw或cmpb成功则将new的最低字节%b1赋值为1,否则赋值为0. sete的解释为:
Sets the byte in the operand to 1 if the Zero Flag is set, otherwise sets the operand to 0.
091行 movzbl(movz from byte to long)指令将%b1赋值给tmp最低字节,且其它位补0.即将tmp置为0或1.
SMP体系架构-SPIN UNLOCK (ticket_shif 8)
/include/linux/spinlock_api_smp.h
046 #ifdef CONFIG_INLINE_SPIN_LOCK
047 #define _raw_spin_lock(lock) __raw_spin_lock(lock)
048 #endif
149 static inline void __raw_spin_unlock(raw_spinlock_t *lock)
150 {
151 spin_release(&lock->dep_map, 1, _RET_IP_);
152 do_raw_spin_unlock(lock);
153 preempt_enable();
154 }
spin_unlock即最终调用do_raw_spin_unlock对自旋锁进行释放操作。
136 static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
137 {
138 __acquire(lock);
139 arch_spin_lock(&lock->raw_lock);
140 }
对于x86的IA32平台,arch_spin_lock实现如下:
/arch/x86/include/asm/spinlock.h
198 static __always_inline void arch_spin_unlock(arch_spinlock_t *lock)
199 {
200 __ticket_spin_unlock(lock);
201 }
058 #if (NR_CPUS < 256)
059 #define TICKET_SHIFT 8
099 static __always_inline void __ticket_spin_unlock(arch_spinlock_t *lock)
100 {
101 asm volatile(UNLOCK_LOCK_PREFIX "incb %0"
102 : "+m" (lock->slock)
103 :
104 : "memory", "cc");
105 }
101行 将lock->slock的Owner + 1,表示可以让下一个拥有牌号的CPU加锁。
030 #if defined(CONFIG_X86_32) && \
031 (defined(CONFIG_X86_OOSTORE) || defined(CONFIG_X86_PPRO_FENCE))
032 /*
033 * On PPro SMP or if we are using OOSTORE, we use a locked operation to unlock
034 * (PPro errata 66, 92)
035 */
036 # define UNLOCK_LOCK_PREFIX LOCK_PREFIX
037 #else
038 # define UNLOCK_LOCK_PREFIX
039 #endif
参考资料
自旋锁
《spinlocks.txt》,/Documentation/spinlocks.txt
《Ticket spinlocks》,http://lwn.net/Articles/267968/
《Linux x86 spinlock实现之分析》,http://blog.csdn.net/david_henry/article/details/5405093
《Linux 内核 LOCK_PREFIX 的含义》,http://blog.csdn.net/ture010love/article/details/7663008
《The Intel 8086 / 8088/ 80186 / 80286 / 80386 / 80486 Instruction Set》:http://zsmith.co/intel.html
转载于:https://www.cnblogs.com/icanth/archive/2012/06/15/2550379.html
LINUX内核之普通自旋锁相关推荐
- 探秘最新Linux内核中的自旋锁
一.前言 目前最新内核中的自旋锁已经进化成queued spinlock,因此需要一篇新的自旋锁文档来跟上时代.此外,本文将不再描述基本的API和应用场景,主要的篇幅将集中在具体的自旋锁实现上.顺便说 ...
- Linux内核同步机制--自旋锁【转】
本文转载自:http://www.cppblog.com/aaxron/archive/2013/04/12/199386.html 自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已 ...
- 自旋锁 Linux内核,Linux内核中的自旋锁
自旋锁不会引起睡眠,当一个进程在访问内核无法获取自旋锁时,会进入忙循环,一直等待下去. 实例: Pcilynx.c (\linux-2.6.30.4\drivers\ieee1394) 1 声明 st ...
- linux内核 task_struct 中自旋锁的应用
自旋锁是用于保护短的代码片段,其中只包含少量C语句,因此会很快执行完毕.大多数内核数据结构都有自身的自旋锁,在处理结构中的关键成员时,必须获得相应的自旋锁. 定义自旋锁 struct task_str ...
- linux 进程调度 运行队列 自旋锁,linux内核进程调度(自旋锁)
2.1首先让我们了解,操作系统分为两类:一类是实时操作系统,一类是分时操作系统.它们的共同特点是都是多任务的 .多任务操作系统分为两类:非抢占式多任务和抢占式多任务. 非抢占式多任务,就是指进程不断的 ...
- linux为什么要禁止内核抢占,为什么linux在内核代码持有自旋锁后禁用内核抢占?...
我是 Linux的新手,正在阅读Rubini& amp;的Linux设备驱动程序.科比特.我对一个与自旋锁相关的陈述感到困惑;这本书说明 If a nonpreemptive uniproce ...
- 内核并发控制---读写自旋锁 (来自网易)
定义在头文件linux/spinlock.h或linux/rwlock.h中; 自旋锁并不关心其锁定的临界区究竟会进行怎么样的操作,不管是读还是写,都一视同仁,即便是多个执行单元同时并发地读取临界区资 ...
- linux进程睡眠编程,linux内核编程-可睡眠锁之SRCU
linux内核编程-可睡眠锁之SRCU DEFINE_SRCU DEFINE_SRCU_STATIC static修饰的定义只能在该c文件中使用,用于保护命名空间. 如果一个函数里面使用了static ...
- 内核同步机制——自旋锁
由于关键代码区可以跨越了多个函数或数据结构,需要有更通用的同步方法:锁. 内核中最常见的一种锁就是自旋锁.相同的锁可用于多处. 自旋锁可用在不可睡眠的场景,如中断处理函数.自旋锁是一种互斥设备,只有两 ...
最新文章
- charles抓包工具的中文乱码解决方法
- java中我爱你_Java线程学习(转)
- 【项目管理】ITTO-采购管理
- 转载:c+string类详解
- 手机全部零件名称图片_【直播课】一条记录中显示多张图片
- macos big sur u盘安装_老款macbook机型欺骗补丁强制安装macOS11 Big Sur图文详解
- Linux(2) vi和vim编辑器
- 2016年1月书单推荐
- 程序-进程-线程 ( 多线程 )
- pdfdom将pdf转成html,使用pdfdom将pdf转为html(示例代码)
- mysql 授权类型_MySQL-02-授权及数据类型
- 为什么redux要返回一个新的state引发的血案
- virtualbox win7虚拟机启动exe提示“DX11 could not switch resolution”解决方案
- 论文研读 —— 7. Very Deep Convolutional Networks for Large-Scale Image Recognition (3/3)
- 用html5制作古诗,古诗词书签制作
- 购物网站HTML(首页)
- 专访Dan Kohn:阡陌交迭,云原生布局开源生态构建及深度应用
- The provisioning profile is expiring soon
- python wifi模块
- c语言正则表达式解析json,JSON解析使用正则表达式
热门文章
- 吴恩达机器学习作业(五):支持向量机
- android action bar 风格,自定义ActionBar风格和样式
- c语言程序设计现代方法快速排序,C语言实现快速排序改进版
- c语言中entern int x y,关于extern和int main()的结构有关问题
- MySQL 事务 | ACID、四种隔离级别、并发带来的隔离问题、事务的使用与实现
- 管道实现父子进程的信息传递(一)【fork函数、pipe函数、write/read操作、wait函数】
- C++:16---强制类型转换和类型转换
- (十四)nodejs循序渐进-高性能游戏服务器框架pomelo之开发Treasures游戏
- boost库linux编译安装
- django框架学习文档_Python四大主流网络编程框架,你知道么?