浅谈对自旋锁的理解
概念理解
何谓自旋锁(spin lock)?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻, 最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁, “自旋”一词就是因此而得名。
自旋锁的缺点
自旋锁必须基于CPU的数据总线锁定,它通过读取一个内存单元(spinlock_t)来判断这个自旋锁是否已经被别的CPU锁住。如果否,它写进一个特定值,表示锁定了总线,然后返回。如果是,它会重复以上操作直到成功,或者spin次数超过一个设定值。记住上面提及到的:锁定数据总线的指令只能保证一个指令操作期间CPU独占数据总线。(自旋锁在锁定的时侯,不会睡眠而是会持续地尝试)。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。虽然自旋锁的效率比互斥锁高,但是它也有些不足之处:
1.自旋锁一直占用CPU,它在未获得锁的情况下,一直运行——自旋,所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。
2.在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数(如copy_to_user()、copy_from_user()、kmalloc()等)也可能造成死锁。
因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况。
dpdk 自旋锁的实现
类型定义:
typedef struct {
volatile int locked; /**< lock status 0 = unlocked, 1 = locked */
} rte_spinlock_t;
相关api
static inline void rte_spinlock_lock(rte_spinlock_t *sl)
static inline int rte_spinlock_trylock (rte_spinlock_t *sl)
static inline void rte_spinlock_unlock (rte_spinlock_t *sl)
其实现原理主要包含以下两点:
1.使用了cpu的locked修饰指令,来保证指令运行的原子性。
2.cpu处于一个忙等待的过程,使用cmpl、pause、jnz等指令,
让cpu一直处于一个忙等的过程.
static inline void
rte_spinlock_lock(rte_spinlock_t *sl)
{
int lock_val = 1;
/*
* spin lock加锁主要有三部分组成:
* 1、判断sl->locked是否为0,如果不是,则跳转到3,否则继续标号2的处理;
* xchg从内存读取sl->locked,
* xchg语句使用了locked修饰,表示执行这条指令时将总线锁住,
* 不让其他处理器处理,以此来保证这条指令执行的原子性。
* 如果sl->locked=0,这跳转到标号3(jz 3f)
*
* 2、循环判断sl->locked是否为0,如果不为零,一直在标号2循环(jnz 2b),
* 如果加锁者释放锁,则sl->locked = 0,这个时候跳转到标号1(jmp 1b),
* 进行加锁操作
*
* 3、该阶段处理是加锁了[locked],同时把sl->locked设置为1,表示已经加锁
*/
asm volatile (
"1:\n"
"xchg %[locked], %[lv]\n"
"test %[lv], %[lv]\n"
"jz 3f\n"
"2:\n"
"pause\n"
"cmpl $0, %[locked]\n"
"jnz 2b\n"
"jmp 1b\n"
"3:\n"
: [locked] "=m" (sl->locked), [lv] "=q" (lock_val)
: "[lv]" (lock_val)
: "memory");
}
static inline void
rte_spinlock_unlock (rte_spinlock_t *sl)
{
int unlock_val = 0;
/*
* spin unlock比较简单
* xchg从内存读取sl->locked,并把该值设置为0即可,该语句使用
* [locked],锁住总线,防止其他cpu处理,保证了原子性
*/
asm volatile (
"xchg %[locked], %[ulv]\n"
: [locked] "=m" (sl->locked), [ulv] "=q" (unlock_val)
: "[ulv]" (unlock_val)
: "memory");
}
static inline int
rte_spinlock_trylock (rte_spinlock_t *sl)
{
int lockval = 1;
/* try lock
* 和rte_spinlock_lock相比,少了第一和第二阶段
* 直接进行加锁操作,不管成功还是失败,都是直接返回
*/
asm volatile (
"xchg %[locked], %[lockval]"
: [locked] "=m" (sl->locked), [lockval] "=q" (lockval)
: "[lockval]" (lockval)
: "memory");
return lockval == 0;
}
本人从dpdk移植了锁实现到自己的github里面 https://github.com/air5005/usg/tree/master/libs/liblock 有兴趣的可以参考