浅谈对自旋锁的理解

浅谈对自旋锁的理解

概念理解

何谓自旋锁(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 有兴趣的可以参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值