linux内核编程之四:同步方法自旋锁(spin lock)

自旋锁


    Linux的的内核最常见的锁是自旋锁。自旋锁最多只能被一个可执行线程持有。如果一个执行线程试图获得一个被已经持有(争用)的自旋锁,那么该线程就会一直进行忙循环-旋转-等待锁重新可用要是锁未被争用,请求锁的执行线程就可以立即得到它,继续执行。

    在任意时间,自旋锁都可以防止多于一个的执行线程同时进入临界区。

同一个锁可以用在多个位置,例如,对于给定数据的所有访问都可以得到保护和同步。

-------------------------------------------------- ----------------

  在Linux的2.6.11.12内核版本中,自旋锁的实现接口定义在包含\ linux的\ <spinlock.h>中,与体系结构相关的代码定义包含在\ ASM \ <spinlock.h>中。

基本结构
自旋锁的结构体是spinlock_t,

typedef struct {
    /**
     * 该字段表示自旋锁的状态,值为1表示未加锁,任何负数和0都表示加锁
     */
    volatile unsigned int slock;
#ifdef CONFIG_DEBUG_SPINLOCK
    unsigned magic;
#endif
#ifdef CONFIG_PREEMPT
    /**
     * 表示进程正在忙等待自旋锁。
     * 只有内核支持SMP和内核抢占时才使用本标志。
     */
    unsigned int break_lock;
#endif
} spinlock_t;
spin_lock()
/**
 * 当内核不可抢占时,spin_lock的实现过程。
 */
#define _spin_lock(lock)    \
do { \
    /**
     * 调用preempt_disable禁用抢占。
     */
    preempt_disable(); \
    /**
     * _raw_spin_lock对自旋锁的slock字段执行原子性的测试和设置操作。
     */
    _raw_spin_lock(lock); \
    __acquire(lock); \
} while(0)
     函数_raw_spin_lock()对自旋锁的SLOCK字段执行原子性的测试和设置操作。

#define _raw_spin_lock(x)        \
    do { \
         CHECK_LOCK(x); \
        if ((x)->lock&&(x)->babble) { \
            (x)->babble--; \
            printk("%s:%d: spin_lock(%s:%p) already locked by %s/%d\n", \
                    __FILE__,__LINE__, (x)->module, \
                    (x), (x)->owner, (x)->oline); \
        } \
        (x)->lock = 1; \
        (x)->owner = __FILE__; \
        (x)->oline = __LINE__; \
    } while (0)


spin_unlock()
#define _spin_unlock(lock) \
do { \
    _raw_spin_unlock(lock); \
    preempt_enable(); \
    __release(lock); \
} while (0)
static inline void _raw_spin_unlock(spinlock_t *lock)
{
#ifdef CONFIG_DEBUG_SPINLOCK
    BUG_ON(lock->magic != SPINLOCK_MAGIC);
    BUG_ON(!spin_is_locked(lock));
#endif
    __asm__ __volatile__(
        spin_unlock_string
    );
}
宏函数spin_unlock_string
   在spin_unlock_string中,%0即为 锁 - > s 锁,movb指令将锁 - > s 锁定为1,movb指令本身就是原子操作,所以不需要锁总线。
#define spin_unlock_string \
    "movb $1,%0" \
        :"=m" (lock->slock) : : "memory"

    自旋锁在同一时刻至多被一个执行线程持有,所以一个时刻只有一个线程位于临界区内,这就为多处理器机器提供了防止并发访问所需的保护机制。

    在单处理机器上,编译的时候不会加入自旋锁,仅会被当作一个设置内核抢占机制是否被启用的开关。如果禁止内核抢占,那么在编译时自旋锁就会被剔除出内核。


内核提供的禁止中断同时请求锁的接口
(1)_spin_lock_irqsave()
    保存中断的当前状态,并禁止本地中断,然后再去获取指定的锁。

unsigned long __lockfunc _spin_lock_irqsave(spinlock_t *lock)
{
    unsigned long flags;
 
    local_irq_save(flags);
    preempt_disable();
    _raw_spin_lock_flags(lock, flags);
    return flags;
}
(2)_spin_unlock_irqrestore()
         对指定的锁解锁,然后让中断恢复到加锁前的状态

void __lockfunc _write_unlock_irqrestore(rwlock_t *lock, unsigned long flags)
{
    _raw_write_unlock(lock);
    local_irq_restore(flags);
    preempt_enable();
}


    如果能确定中断在加锁前是激活的,那就不需要在解锁后恢复中断以前的状态。也就可以无条件地在解锁时激活中断。这时可以使用spin_lock_irq()和spin_unlock_irq()。

_spin_lock_irq()
         禁止本地中断并获取指定的锁 。
void __lockfunc _read_lock_irq(rwlock_t *lock)
{
    local_irq_disable();
    preempt_disable();
    _raw_read_lock(lock);
}
_spin_unlock_irq()
        释放指定的锁,并激活本地中断。

void __lockfunc _spin_unlock_irq(spinlock_t *lock)
{
    _raw_spin_unlock(lock);
    local_irq_enable();
    preempt_enable();
}
   在使用spin_lock_irq()方法时,需要确定中断原来是否处于激活状态。一般不建议使用。


spin_lock_init()
     动态初始化指定的spinlock_t,(此时只有一个指向spinlock_t类型地指针,没有它的实体)

#define spin_lock_init(lock)    do { (void)(lock); } while(0)

spin_try_lock()
    试图获的某个特定的自旋锁。如果该锁已经被争用,那么该函数立即返回一个非0值,而不会自旋等待锁被释放;                

如果成功地获得了这个自旋锁,该函数返回0。

int __lockfunc _spin_trylock(spinlock_t *lock)
{
    preempt_disable();    //使抢占计数加1
    if (_raw_spin_trylock(lock))
        return 1;
    
    preempt_enable();    // 使抢占计数减1,并在thread_info描述符的TIF_NEED_RESCHED标志被置为1的情况下,调用preempt_schedule()
    return 0;
}

spin_is_locked()
    用于检查特定的锁当前是否已被占用,如果已被占用,返回非0值;否则返回0。

#define spin_is_locked(x) \
    ({ \
         CHECK_LOCK(x); \
        if ((x)->lock&&(x)->babble) { \
            (x)->babble--; \
            printk("%s:%d: spin_is_locked(%s:%p) already locked by %s/%d\n", \
                    __FILE__,__LINE__, (x)->module, \
                    (x), (x)->owner, (x)->oline); \
        } \
        0; \
    })


-------------------------------------------------- ----------------

总结

(1) 一个被争用的自旋锁使得请求它的线程在等待锁重新可用时自旋,会特别浪费处理器时间。所以自旋锁不应该被长时间持有。因此,自旋锁应该使用在:短时间内进行轻量级加锁。

(2)还可以采取另外的方式来处理对锁的争用:让请求线程睡眠,直到锁重新可用时再唤醒它这样处理器不必循环等待,可以执行其他任务。

    但是让请求线程睡眠的处理也会带来一定开销:会有两次上下文切换,被阻塞的线程要换出和换入所以,自旋持有锁的时间最好小于完成两次上下文e月刊的耗时,也就是让持有自旋锁的时间尽可能短。(在抢占式内核中,的锁持有等价于系统-的调度等待时间),信号量可以在发生争用时,等待的线程能投入睡眠,而不是旋转。

(3)在单处理机器上,自旋锁是无意义的。因为在编译时不会加入自旋锁,仅仅被当作一个设置内核抢占机制是否被启用的开关。如果禁止内核抢占,那么在编译时自旋锁会被完全剔除出内核。

(4)Linux内核中,自旋锁是不可递归的。如果试图得到一个你正在持有的锁,你必须去自旋,等待你自己释放这个锁。但这时你处于自旋忙等待中,所以永远不会释放锁,就会造成死锁现象。

(5)在中断处理程序中,获取锁之前一定要先禁止本地中断(当前处理器的中断),否则,中断程序就会打断正持有锁的内核代码,有可能会试图去争用这个已经被持有的自旋锁。这样就会造成双重请求死锁(中断处理程序会自旋,等待该锁重新可用,但锁的持有者在这个处理程序执行完之前是不可能运行的)

(6)锁真正保护的是数据(共享数据),而不是代码。对于BLK(大内核锁)保护的是代码。




补充:
BLK:大内核锁
    BLK是一个全局自旋锁,主要目的是使Linux的最初的SMP过渡到细粒度加锁机制。

特性如下:

·持有BLK的任务可以睡眠的,是安全的。因为当任务无法被调度时,所加锁会自动被丢弃;当任务被调度时,锁会被重新获得。

·BLK是一种递归锁。一个进程可以多次请求一个锁,而不会像自旋锁那样造成死锁现象。

·BLK只可以用在进程上下文中。不同于自旋锁可在中断上下文中加锁。

·BLK锁保护的是代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

隨意的風

如果你觉得有帮助,期待你的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值