【linux】spinlock 的实现
一、什么是spinlock
spinlock又称自旋锁,是实现保护共享资源而提出一种锁机制。自旋锁与互斥锁比较类似,都是为了解决对某项资源的互斥使用
无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名
二、spinlock的原理
跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题:死锁和过多占用cpu资源
- a. 在用户态尝试竞争一个共享资源. 如果竞争不到, 则不断尝试竞争. 但是不借助内核提供的mutex等变量机制. 因为涉及到内核,就意味这效率低下
- b. 要想在用户态实现竞争一个共享资源, 必须借助cpu提供的原子操作指令. 如果是SMP多cpu,还需要lock指令锁总线
- c. 为了避免在长时间竞争却一直得不到资源导致的不断尝试浪费cpu, 在每两次尝试之间间隔一段时间. 并且随着尝试次数的增加,间隔时间也增加.间隔期间可以让cpu稍加休息(注意,绝不是让出cpu),这依赖于cpu提供pausse指令. (当然如果cpu没有提供pause也没关系,只是会很消耗电力资源)PAUSE指令提升了自旋等待循环(spin-wait loop)的性能
- d. 在等待相当长时间还是得不到锁之后,只好让出cpu. 但必须让出很小一会. 否则就不叫自旋锁了
如何让出cpu,却有可以很快的回来? 内核提供了 sched_yield()函数,sched_yield()主要功能: 简单的讲,可以使用另一个级别等于或高于当前线程的线程先运行。如果没有符合条件的线程,那么这个函数将会立刻返回然后继续执行当前线程的程序,如果系统不支持sched_yield, nginx被迫使用了usleep()休息1u秒.
三、spinlock的适用情况
自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁
信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。另外格外注意一点:自旋锁不能递归使用。
四、spinlock与mutex对比
spinlock不会使线程状态发生切换,mutex在获取不到锁的时候会选择sleep
mutex获取锁分为两阶段,第一阶段在用户态采用spinlock锁总线的方式获取一次锁,如果成功立即返回;否则进入第二阶段,调用系统的futex锁去sleep,当锁可用后被唤醒,继续竞争锁。
Spinlock优点:没有昂贵的系统调用,一直处于用户态,执行速度快
Spinlock缺点:一直占用cpu,而且在执行过程中还会锁bus总线,锁总线时其他处理器不能使用总线
Mutex优点:不会忙等,得不到锁会sleep
Mutex缺点:sleep时会陷入到内核态,需要昂贵的系统调用
五、关于spinlock的定义以及相应的API
自旋锁定义: linux/Spinlock.h
typedef struct spinlock {
union { //联合
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
#define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct{
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
定义和初始化
spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
void spin_lock_init(spinlock_t *lock);
自旋锁操作
//加锁一个自旋锁函数
void spin_lock(spinlock_t *lock); //获取指定的自旋锁
void spin_lock_irq(spinlock_t *lock); //禁止本地中断获取指定的锁
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags); //保存本地中断的状态,禁止本地中断,并获取指定的锁
void spin_lock_bh(spinlock_t *lock) //安全地避免死锁, 而仍然允许硬件中断被服务 //释放一个自旋锁函数
void spin_unlock(spinlock_t *lock); //释放指定的锁
void spin_unlock_irq(spinlock_t *lock); //释放指定的锁,并激活本地中断
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags); //释放指定的锁,并让本地中断恢复到以前的状态
void spin_unlock_bh(spinlock_t *lock); //对应于spin_lock_bh //非阻塞锁
int spin_trylock(spinlock_t *lock); //试图获得某个特定的自旋锁,如果该锁已经被争用,该方法会立刻返回一个非0值,
//而不会自旋等待锁被释放,如果成果获得了这个锁,那么就返回0.
int spin_trylock_bh(spinlock_t *lock);
//这些函数成功时返回非零( 获得了锁 ), 否则 0. 没有"try"版本来禁止中断. //其他
int spin_is_locked(spinlock_t *lock); //和try_lock()差不多
六、nginx中的实现
在nginx中 spinlock的使用场景是,nginx借助spinlock的技术,实现了用户态的进程间的mutex. 由于spinlock是阻塞的
#define ngx_shmtx_lock(mtx) ngx_spinlock((mtx)->lock, ngx_pid, 1024)
具体分析一个spinlock的开源实现
// 输入参数
// lock:一个整形变量的指针
// value:将lock设置新的值
// spin: 自旋的次数. 该值越大会尝试更多次获得锁. 然后才会转入让内核调度线程暂时让出cpu.
void
ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin)
{
#if (NGX_HAVE_ATOMIC_OPS)
ngx_uint_t i, n;
for ( ;; ) {
if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
return;
}
// 为何只在多个cpu的时候才多尝试spin几次
// 呵呵,很简单,如果是单核的话,既然自己没有拿到锁,那说明别的线程/进程正在使用锁,就这么一个cpu,咱就不占着自旋了,否则别人没机会得到cpu,更不会释放锁了.
if (ngx_ncpu > 1) {
for (n = 1; n < spin; n <<= 1) {
// 空转的时间随着n的变大而变大
for (i = 0; i < n; i++) {
ngx_cpu_pause(); // 在空转的同时, 降低cpu功耗,提高效率
}
if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
return;
}
}
}
// 已经尝试这么久了还没有得到锁, 让cpu忙别人的事情吧. 让出cpu等待一下.
ngx_sched_yield();
}
#else
#if (NGX_THREADS)
#error ngx_spinlock() or ngx_atomic_cmp_set() are not defined !
#endif
#endif
} #if (NGX_HAVE_SCHED_YIELD)
#define ngx_sched_yield() sched_yield()
#else
#define ngx_sched_yield() usleep(1)
#endif
7、PHP扩展中的实现
/* GCC support */ #include <stdlib.h>
#include "spinlock.h" extern int ncpu; void spin_lock(atomic_t *lock, int which)
{
int i, n; for ( ;; ) { if (*lock == 0 &&.
__sync_bool_compare_and_swap(lock, 0, which)) {
return;
} if (ncpu > 1) { for (n = 1; n < 129; n << 1) { for (i = 0; i < n; i++) {
__asm("pause");
} if (*lock == 0 &&.
__sync_bool_compare_and_swap(lock, 0, which)) {
return;
}
}
} sched_yield();
}
} void spin_unlock(atomic_t *lock, int which)
{
__sync_bool_compare_and_swap(lock, which, 0);
}
参考文章
https://www.ibm.com/developerworks/cn/linux/l-cn-mcsspinlock/
http://www.cnblogs.com/biyeymyhjob/archive/2012/07/21/2602015.html
http://www.360doc.com/content/11/0302/14/3038654_97459411.shtml
http://ifeve.com/practice-of-using-spinlock-instead-of-mutex/
http://www.cnblogs.com/FrankTan/archive/2010/12/11/1903377.html
【linux】spinlock 的实现的更多相关文章
- 【原创】linux spinlock/rwlock/seqlock原理剖析(基于ARM64)
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- Linux内核原子(1) - spinlock的实现
spinlock的数据结构spinlock_t定义在头文件linux/spinlock_types.h里面: typedef struct { raw_spinlock_t raw_lock; #if ...
- Linux并发与同步专题 (2)spinlock
关键词:wfe.FIFO ticket-based.spin_lock/spin_trylock/spin_unlock.spin_lock_irq/spin_lock_bh/spin_lock_ir ...
- spinlock in linux kernel
spinlock in linux kernel 作为一种锁机制, spinlock可以制造一段临界区, 同一时刻只有一个线程能进入这个临界区, 从而达到保护数据的目的. semaphore, mut ...
- 嵌入式Linux驱动学习之路(二十七)字符设备驱动的另一种写法
之前讲的字符设备驱动程序,只要有一个主设备号,那么次设备号无论是什么都会和同一个 struct file_operations 结构体对应. 而本节课讲的是如何在设备号相同的情况下,让不同的次设备号对 ...
- 嵌入式Linux驱动学习之路(二十六)DM9000C网卡驱动程序
基于DM9000C的原厂代码修改dm9000c的驱动程序. 首先确认内存的基地址 iobase. 确定中断号码. 打开模块的初始化函数定义. 配置内存控制器的相应时序(结合DM9000C.C的手册). ...
- 嵌入式Linux驱动学习之路(二十五)虚拟网卡驱动程序
一.协议栈层次对比 设备无关层到驱动层的体系结构 1).网络协议接口层向网络层协议提供提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过 ...
- 嵌入式Linux驱动学习之路(十七)驱动程序分层分离概念-平台设备驱动
平台设备驱动: 包含BUS(总线).DEVICE.DRIVER. DEVICE:硬件相关的代码 DRIVER:比较稳定的代码 BUS有一个driver链表和device链表. ①把device放入bu ...
- 嵌入式Linux驱动学习之路(十五)按键驱动-定时器防抖
在之前的定时器驱动程序中,我们发现在连续按下按键的时候,正常情况下应该是一次按下对应一次松开.而程序有时候会显示是两次按下,一次松开.这个问题是因为在按下的时候,因为是机械按键,所以电压信号会产生一定 ...
随机推荐
- 服务器唯一id生成规则
在使用hashCode的时候,发现会出现相同id,虽然几率很小.虽然发现并不是hashCode的原因,而是其他逻辑的问题. 但是还是试着自己写了一个id生成器,有些id是int的,比如说任务id:有些 ...
- Scrum 项目 6.0
-------------------------6.0------------------------------------ sprint演示 1.坚持所有的sprint都结束于演示. 团队的成果 ...
- Scrum团队成立3.0
博客园 首页 新随笔 联系 订阅 管理 随笔 - 23 文章 - 0 评论 - 26 0428-Scrum团队成立3.0 ------------------------------3.0---- ...
- ok6410 android driver(9)
In this essay, I will write the JNI to test our leds device. If you don't know how to create a jni p ...
- easyui-treegrid节点选择
easyui-treegrid本身不能实现选中父节点子节点全选,必须通过另外的方法来实现,这里说下如何通过修改节点样式添加checkbox来实现级联选择效果 首先需要格式化节点的样式 formatte ...
- Microsoft.DirectX.DirectSound.dll和Microsoft.DirectX.dll引用,导致项目无法调试问题
最近在做录音功能,用到了Microsoft.DirectX.DirectSound.dll和Microsoft.DirectX.dll两个dll,但是引入后,无法调试项目 具体解决方法: 修改app. ...
- ThinkPHP去掉URL中的index.php
我的环境是apache+ubuntu 1,先确认你有没mod_rewrite.so模块 /usr/lib/apache2/modules/mod_rewrite.so 然后在httpd.conf最后一 ...
- tomcat学习笔记一:安装和配置
安装 配置环境: win7 + centos7虚拟机 安装步骤: 到http://tomcat.apache.org/下载最新的tomcat安装包(8.0.24) 移到对应的目录并解压 安装问题: 安 ...
- C# Winform 窗体美化
using System; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using Sys ...
- Thumbnailator压缩图片
Thumbnailator是一款不可多得的处理图片的第三方工具包,它写法简单到让人无法相信,Java本身也有处理图片压缩的方法,但是代码冗长到让人痛不欲生,在篇末会给出Java本身的实现方式,做下对比 ...