spinlock的数据结构spinlock_t定义在头文件linux/spinlock_types.h里面:

   typedef struct {
raw_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
} spinlock_t;

其中抛开debug的数据成员,最核心的成员就是raw_lock,这是一个和处理器架构相关的结构。

比如X86中定义是(arch/x86/include/asm/spinlock_types.h):

    typedef struct raw_spinlock {
unsigned int slock;
} raw_spinlock_t;

ARM中的定义是(arch/arm/include/asm/spinlock_types.h):

    typedef struct {
volatile unsigned int lock;
} raw_spinlock_t;

在单核处理器中,raw_spinlock_t被定义为空结构体(linux/spinlock_types_up.h):

   typedef struct { } raw_spinlock_t;

不管是什么体系结构,kernel都是根据结构体里的slock或者lock的值来判断当前的锁是被占用还是空闲,并且做出相应的动作(单核处理器除外)。

在linux/spinlock.h中定义了spinlock操作的API。

spinlock的思想就是在SMP环境中,保护共享的数据结构;也就是CPU-A正在访问(读写)共享数据的期间,其他CPU不能访问同样的共享数据,这样就保证了SMP-safe。每个线程在访问共享数据的之前,都需要获取spin lock,如果锁正被其他线程所占有,那么获取锁的线程则“空转”CPU以等待其他线程释放锁;spin lock相对于信号量这样的锁机制的好处就是,节约了2次context switch的开销,所以如果线程等待锁的时间小于2次context switch的时间,系统性能从spin lock获得的提升就越多。

spin lock除了考虑SMP-safe以外,还要考虑两种伪并发情况,就是中断(interrupt)和抢占(preemption),就是要保证interrupt-safe和preempt-safe。

如果在中断处理程序中,因为要访问共享变量而使用spin lock,则要避免dead-lock出现。比如,CPU0上线程A获取了锁1,在获取和释放锁之间CPU0上发生软中断进入中断处理程序,中断处理程序也尝试去获取spin lock,但是由于同一CPU0上的lock holder线程A在中断处理程序退出之前无法被调度而释放锁,所以在CPU0上就出现dead-lock;但是如果软中断是发生在其他CPU比如CPU1上,则是没有问题的,因为发现在CPU1上的中断不会中断CPU0上lock holder线程A的执行。所以要保证interrupt-safe,就要在获取锁之前disable本地CPU中断。

kernel文档spinlocks.txt里面有相关的描述:

112	The reasons you mustn't use these versions if you have interrupts that
113 play with the spinlock is that you can get deadlocks:
114
115 spin_lock(&lock);
116 ...
117 <- interrupt comes in:
118 spin_lock(&lock);
119
120 where an interrupt tries to lock an already locked variable. This is ok if
121 the other interrupt happens on another CPU, but it is _not_ ok if the
122 interrupt happens on the same CPU that already holds the lock, because the
123 lock will obviously never be released (because the interrupt is waiting
124 for the lock, and the lock-holder is interrupted by the interrupt and will
125 not continue until the interrupt has been processed).
126
127 (This is also the reason why the irq-versions of the spinlocks only need
128 to disable the _local_ interrupts - it's ok to use spinlocks in interrupts
129 on other CPU's, because an interrupt on another CPU doesn't interrupt the
130 CPU that holds the lock, so the lock-holder can continue and eventually
131 releases the lock).

然后就是preempt-safe。

spin_lock_init

spin_lock_init的实现是一个宏,对spinlock_t类型的lock做一个初始化。

   # define __SPIN_LOCK_UNLOCKED(lockname) \
(spinlock_t) { .raw_lock = __RAW_SPIN_LOCK_UNLOCKED, \
SPIN_DEP_MAP_INIT(lockname) }
94#define SPIN_LOCK_UNLOCKED      __SPIN_LOCK_UNLOCKED(old_style_spin_init)
104# define spin_lock_init(lock)                                   \
105 do { *(lock) = SPIN_LOCK_UNLOCKED; } while (0)

其中raw_lock被初始化为宏__RAW_SPIN_LOCK_UNLOCKED,很明显,这个宏也会是体系结构相关的,在X86它被定义为:

   #define __RAW_SPIN_LOCK_UNLOCKED        { 0 }

也就是说,在X86中如果无符号整型变量slock的值是0,为则UNLOCKED的状态。

spin_lock

如果能确定被保护的共享变量在interrupt中是不会被访问的,那么可以忽略interrupt-safe,用简单也更有效率的spin_lock

UP的环境中,spin_lock的实现是没有lock操作的,spin_lock仅仅保证在线程在临界区中(也就是spin_lockspin_unlock之前的section)是不会被抢占的preempt的。

UPspin_lock的实现是在linux/spinlock_api_up.h中:

#define _spin_lock(lock)                        __LOCK(lock)

  /*
22 * In the UP-nondebug case there's no real locking going on, so the
23 * only thing we have to do is to keep the preempt counts and irq
24 * flags straight, to suppress compiler warnings of unused lock
25 * variables, and to add the proper checker annotations:
26 */
#define __LOCK(lock) \
do { preempt_disable(); __acquire(lock); (void)(lock); } while ()

preempt_disable()禁止在临界区中线程被抢占。

(void)(lock)是避免编译器的报警。

smp版的spin_lock的实现在spinlock_api_smp.h中:

  static inline void __spin_lock(spinlock_t *lock)
{
preempt_disable();
spin_acquire(&lock->dep_map, , , _RET_IP_);
LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
} #define LOCK_CONTENDED(_lock, try, lock) \
lock(_lock)

也就是说,核心的操作就是_raw_spin_lock(_lock)。

 # define _raw_spin_lock(lock)           __raw_spin_lock(&(lock)->raw_lock)

__raw_spin_lock的是一个和arch相关的实现了,在内核2.5.24上的X86平台上(asm-x86/spinlock_64.h):

  25static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
asm volatile(
"\n1:\t"
LOCK_PREFIX " ; decl %0\n\t"
"jns 2f\n"
"3:\n"
"rep;nop\n\t"
"cmpl $0,%0\n\t"
"jle 3b\n\t"
"jmp 1b\n"
"2:\t" : "=m" (lock->slock) : : "memory");
}

实际上,spinlock的实现就是检查lock->slock的值来判断锁的free or busy状态,所以不同的CPU对锁进行的decl或者incl指令必须是原子的,否则会出现多个CPU同时认为锁是free而进入临界区或者所有CPU都认为锁是busy而dead-lock的后果;在x86平台上,LOCK_PREFIX用前缀保证对lock->slock的原子性。

LOCK_PREFIX的实现可以参见 http://wenku.baidu.com/view/13dbbe1fb7360b4c2e3f642b.html

'rep;nop'是什么指令呢?我们反汇编看看:

#include <stdio.h>

static inline void rep_nop(void)
{
asm volatile("rep; nop" ::: "memory");
} int main(void)
{
rep_nop();
return ;
} [yzhang2@sles10sp3 ~]$gcc -c asm.c [yzhang2@sles10sp3 ~]$objdump -s -d asm.o <rep_nop>:
: push %rbp
: e5 mov %rsp,%rbp
: f3 pause
: c9 leaveq
: c3 retq

原来'rep;nop'指令被解释为了'pause'指令。

这段代码的具体逻辑就是:

decl %0就是将lock->slock减1,如果锁是空闲的,计算结果是0,根据‘jns 2f'则跳转到2,退出函数获取锁;如果锁是被占有的,结果是负数,则运行'rep;nop',然后再次比较lock->slock,如果其大于0,说明锁已经被释放,跳到1重新尝试获得锁;否则继续等待。

但是这样的自旋锁不能保证获取锁的fairness,所以在2.6.25以后引入了FIFO ticket spinlock

  static __always_inline void __ticket_spin_lock(raw_spinlock_t *lock)
{
int inc = 0x00010000;
int tmp; asm volatile(LOCK_PREFIX "xaddl %0, %1\n"
"movzwl %w0, %2\n\t"
"shrl $16, %0\n\t"
"1:\t"
"cmpl %0, %2\n\t"
"je 2f\n\t"
"rep ; nop\n\t"
"movzwl %1, %2\n\t"
/* don't need lfence here, because loads are in-order */
"jmp 1b\n"
"2:"
: "+r" (inc), "+m" (lock->slock), "=&r" (tmp)
:
: "memory", "cc");
}

在执行完xaddl以后,%0的值是(lock->slock),%1的值是(inc+lock->slock),也就是把slock值的next域加1。

movzwl是将(lock->slock)的owner字段赋给%2,shrl是将(lock->slock)的next字段赋给%0

cmpl就是比较slock的next和owner字段,如果相等,则代表获得了锁;不相等,则进入忙等待(rep;nop),然后通过movzwl %1 %2更新owner字段,因为spin_unlock的操作就是把owner字段加1,然后返回到1重新比较,如果这时owner==next,则获得锁。

这样,各个cpu指定了自己的next字段,然后他们就能按照顺序保证了cpu获取锁的fairness。

Linux内核原子(1) - spinlock的实现的更多相关文章

  1. linux内核自锁旋spinlock常用宏解释

    转自:http://blog.sina.com.cn/s/blog_6929134b0100tdn8.html 自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持, ...

  2. linux内核原子变量与原子位操作API

    原子变量: arch/arm/include/asm/atomic.h 定义并初始化 atomic_t v = ATOMIC_INIT(0); 写 void atomic_set(atomic_t * ...

  3. linux 内核与用户空间通信之netlink使用方法

    转自:http://blog.csdn.net/haomcu/article/details/7371835 Linux中的进程间通信机制源自于Unix平台上的进程通信机制.Unix的两大分支AT&a ...

  4. Linux 同步方法剖析--内核原子,自旋锁和相互排斥锁

    在学习 Linux® 的过程中,您或许接触过并发(concurrency).临界段(critical section)和锁定,可是怎样在内核中使用这些概念呢?本文讨论了 2.6 版内核中可用的锁定机制 ...

  5. Linux内核中进程上下文、中断上下文、原子上下文、用户上下文的理解【转】

    转自:http://blog.csdn.net/laoliu_lcl/article/details/39972459 进程上下文和中断上下文是操作系统中很重要的两个概念,这两个概念在操作系统课程中不 ...

  6. Linux内核同步机制--转发自蜗窝科技

    Linux内核同步机制之(一):原子操作 http://www.wowotech.net/linux_kenrel/atomic.html 一.源由 我们的程序逻辑经常遇到这样的操作序列: 1.读一个 ...

  7. linux内核数据结构学习总结

    目录 . 进程相关数据结构 ) struct task_struct ) struct cred ) struct pid_link ) struct pid ) struct signal_stru ...

  8. Linux内核调试方法总结【转】

    转自:http://my.oschina.net/fgq611/blog/113249 内核开发比用户空间开发更难的一个因素就是内核调试艰难.内核错误往往会导致系统宕机,很难保留出错时的现场.调试内核 ...

  9. Linux内核同步机制

    http://blog.csdn.net/bullbat/article/details/7376424 Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环 ...

随机推荐

  1. FastJson转换自定义枚举类

    在项目中有些状态需要采用枚举类型,在数据库中保存的是name(英文),而前台需要显示的是text(中文). 所以这就需要自己去实现序列. 例如对象: import java.util.Date; im ...

  2. Python—操作redis

    Python操作redis 连接方式:点击 1.String 操作 redis中的String在在内存中按照一个name对应一个value来存储 set() #在Redis中设置值,默认不存在则创建, ...

  3. Python企业级开发之一:基础

    Python企业级开发相关内容.这里涉及到Python开发过程中的问题以及解决办法.还提供新的开发思路. 脚本开发的一些共同的问题.如:1.对OO的支持不完善,2.问题定位方式给出的信息过于晦涩,3. ...

  4. [Dynamic Language] 用Sphinx自动生成python代码注释文档

    用Sphinx自动生成python代码注释文档 pip install -U sphinx 安装好了之后,对Python代码的文档,一般使用sphinx-apidoc来自动生成:查看帮助mac-abe ...

  5. laravel框架总结(十三) -- redis使用

    一切的前提都是已经安装好了redis服务器,并且能启动(我只总结了mac的安装方法:传送门) 我自己使用的是mac系统,有个教程可以参考下,传送门: 1.安装PHP PRedis 1>PRedi ...

  6. sublime text3 C语言环境配置

    { "cmd": ["gcc", "-W", "-Wall", "-Werror","${ ...

  7. 在ubuntu 14.04上安装2.6的内核

    1.到http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.32.tar.bz2这里下载最新的稳定版内核: 2.根据各自系统,安装如下软件:l b ...

  8. [转]表结构设计器EZDML介绍说明(包含修改配置文件,修改文本字段属性)

    超轻量级的表结构设计工具,这是一个数据库建表的小软件,可快速的进行数据库表结构设计,建立数据模型.类似大家常用的数据库建模工具如PowerDesigner.ERWIN.ER-Studio和Ration ...

  9. jquery.validate 使用--验证表单隐藏域

    jQuery validate很不错的一个jQuery表单验证插件.升级到了1.9版的后,发现隐藏表单域验证全部失效,特别是在jquery.ui.tabs.min.js构造的Tabs里的验证. 是因为 ...

  10. python非递归全排列

    刚刚开始学习python,按照廖雪峰的网站看的,当前看到了函数这一节.结合数组操作,写了个非递归的全排列生成.原理是插入法,也就是在一个有n个元素的已有排列中,后加入的元素,依次在前,中,后的每一个位 ...