您的位置:首页 > 运维架构 > Linux

内核同步方法之自旋锁

2008-12-19 11:52 218 查看
 
       linux内核中最常见的锁是自旋锁(spin lock)。自旋锁最多只能被一个可执行线程持有。如果一个执行线程试图获得一个被争用的自旋锁,那么该线程就会一直进行忙循环等待锁重新可用。要是锁未被争用,请求锁的执行线程便能立刻得到它,继续执行。在任意时间,自旋锁都可以防止多于一个的执行线程同时进入临界区。

      一个被正用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理器时间),这种行为是自旋锁的特点。自旋锁不应该被长时间持有。持有自旋锁的时间最好小于完成两次上下文切换的的时间。

     自旋锁的实现和体系结构密切相关,代码往往使用汇编实现。

     因为自旋锁在同一时刻至多被一个执行线程持有,所以一个时刻只能有一个线程位于临界区内,这就为多处理器提供了防止并发访问所需要的包含机制。在单处理器机器上,编译的时候并不会加入自旋锁。

     自旋锁不能递归!

自旋锁可以使用在中断处理程序中(此处不能使用信号量,因为它会导致睡眠)。在中断处理程序中使用自旋锁时,一定要在获取锁之前,首先禁本地中断(在当前处理器上的中断请求),否则,中断处理程序就会打断正持有锁的内核代码,有可能会试图去争用这个已经被持有的自旋锁。注意,需要关闭的只是当前处理器上的中断,因为如果中断发生在不同的处理器上,即使中断处理程序在同一锁上的自旋,也不会妨碍锁的持有者最终释放锁。

  下面是相关文件如下:

/*

 * include/linux/spinlock.h - generic spinlock/rwlock declarations

 *

 * here's the role of the various spinlock/rwlock related include files:

 *

 * on SMP builds:

 *

 *  asm/spinlock_types.h: contains the raw_spinlock_t/raw_rwlock_t and the

 *                        initializers

 *

 *  linux/spinlock_types.h:

 *                        defines the generic type and initializers

 *

 *  asm/spinlock.h:       contains the __raw_spin_*()/etc. lowlevel

 *                        implementations, mostly inline assembly code

 *

 *   (also included on UP-debug builds:)

 *

 *  linux/spinlock_api_smp.h:

 *                        contains the prototypes for the _spin_*() APIs.

 *

 *  linux/spinlock.h:     builds the final spin_*() APIs.

 *

 * on UP builds:

 *

 *  linux/spinlock_type_up.h:

 *                        contains the generic, simplified UP spinlock type.

 *                        (which is an empty structure on non-debug builds)

 *

 *  linux/spinlock_types.h:

 *                        defines the generic type and initializers

 *

 *  linux/spinlock_up.h:

 *                        contains the __raw_spin_*()/etc. version of UP

 *                        builds. (which are NOPs on non-debug, non-preempt

 *                        builds)

 *

 *   (included on UP-non-debug builds:)

 *

 *  linux/spinlock_api_up.h:

 *                        builds the _spin_*() APIs.

 *

 *  linux/spinlock.h:     builds the final spin_*() APIs.

 */

  内核提供的禁止中断同时请求锁的接口:

在<Spinlock.h(include/linux)>中

#if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)

#define spin_lock_irqsave(lock, flags)  flags = _spin_lock_irqsave(lock)

#define read_lock_irqsave(lock, flags)  flags = _read_lock_irqsave(lock)

#define write_lock_irqsave(lock, flags) flags = _write_lock_irqsave(lock)

#ifdef CONFIG_DEBUG_LOCK_ALLOC

#define spin_lock_irqsave_nested(lock, flags, subclass) /

    flags = _spin_lock_irqsave_nested(lock, subclass)

#else

#define spin_lock_irqsave_nested(lock, flags, subclass) /

    flags = _spin_lock_irqsave(lock)

#endif

#else

#define spin_lock_irqsave(lock, flags)  _spin_lock_irqsave(spinlock_t * lock)(lock, flags)

#define read_lock_irqsave(lock, flags)  _read_lock_irqsave(lock, flags)

#define write_lock_irqsave(lock, flags) _write_lock_irqsave(lock, flags)

#define spin_lock_irqsave_nested(lock, flags, subclass) /

    spin_lock_irqsave(lock, flags)

#endif

#define spin_unlock_irqrestore(lock, flags) /

                    _spin_unlock_irqrestore(lock, flags)

   函数spin_lock_irqsave()保存中断的当然状态,并禁止本地中断,然后再去获取指定的锁。spin_lock_irqsave()对指定的锁解锁,然后让中断恢复到加锁前的状态。所以即使中断最初是被禁止的,你的代码也不会错误地激活它们,相反,会继续让它们禁止。flag变量应该由数值传递,因为锁函数有些是通过宏的方式实现的。

   加锁和解锁分别可以禁止和允许内核抢占。

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

#define spin_lock_irq(lock)     _spin_lock_irq(lock)

#if defined(CONFIG_DEBUG_SPINLOCK) || defined(CONFIG_PREEMPT) || /

    !defined(CONFIG_SMP)

# define read_unlock_irq(lock)      _read_unlock_irq(lock)

#else

# define spin_unlock_irq(lock)          /

do {                        /

    __raw_spin_unlock(&(lock)->raw_lock);   /

    __release(lock);            /

    local_irq_enable();         /

} while (0)

    由于内核庞大而复杂,所以在内核的执行路线上,很难搞清楚中断在当前调用点上是否处于激活状态,故一般不提倡使用spin_lock_irq()方法。

 

    针对自旋锁的操作:

spin_lock_init()用来初始化动态创建的自旋锁。

#ifdef CONFIG_DEBUG_SPINLOCK

  extern void __spin_lock_init(spinlock_t *lock, const char *name,

                   struct lock_class_key *key);

# define spin_lock_init(lock)                   /

do {                                /

    static struct lock_class_key __key;         /

                                /

    __spin_lock_init((lock), #lock, &__key);        /

} while (0)

#else

# define spin_lock_init(lock)                   /

    do { *(lock) = SPIN_LOCK_UNLOCKED; } while (0)

#endif

#ifdef CONFIG_DEBUG_SPINLOCK

  extern void __rwlock_init(rwlock_t *lock, const char *name,

                struct lock_class_key *key);

# define rwlock_init(lock)                  /

do {                                /

    static struct lock_class_key __key;         /

                                /

    __rwlock_init((lock), #lock, &__key);           /

} while (0)

#else

# define rwlock_init(lock)                  /

    do { *(lock) = RW_LOCK_UNLOCKED; } while (0)

#endif

#define spin_is_locked(lock)    __raw_spin_is_locked(&(lock)->raw_lock)

#define spin_trylock(lock)      __cond_lock(lock, _spin_trylock(lock))

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

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

#define spin_lock(lock)         _spin_lock(lock)

/*

 * We inline the unlock functions in the nondebug case:

 */

#if defined(CONFIG_DEBUG_SPINLOCK) || defined(CONFIG_PREEMPT) || /

    !defined(CONFIG_SMP)

# define spin_unlock(lock)      _spin_unlock(lock)

#else

# define spin_unlock(lock) /

    do {__raw_spin_unlock(&(lock)->raw_lock); __release(lock); } while (0)

自旋锁和下半部

函数spin_lock_bh()用于获取指定锁,同时它会禁止所有下半部的执行。spin_unlock_bh()函数执行相反的操作。

#define spin_lock_bh(lock)      _spin_lock_bh(lock)

#define spin_unlock_bh(lock)        _spin_unlock_bh(lock)

void __lockfunc _spin_lock_bh(spinlock_t *lock)

{

    local_bh_disable();

    preempt_disable();

    spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);

    _raw_spin_lock(lock);

}

void __lockfunc _spin_unlock_bh(spinlock_t *lock)

{

    spin_release(&lock->dep_map, 1, _RET_IP_);

    _raw_spin_unlock(lock);

    preempt_enable_no_resched();

    local_bh_enable_ip((unsigned long)__builtin_return_address(0));

}

  由于下半部可以抢占进程上下文中的代码,所以当下半部和进程上下文共享数据时,必须对进程上下文中的共享数据进行包含,所以需要加锁的同时还要禁止下半部执行。同样,由于中断处理程序可以抢占下半部,所以如果中断处理程序和下半部共享数据,那么就必须在获取恰当的锁的同时还有禁止中断。
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息