自旋锁(spin lock)是一种对临界资源进行互斥手访问的典型手段,其名称来源于它的工作方式。为了获得一个自旋锁,在某 CPU 上运行的代码需先执行一个原子操作,该操作测试并设置(test-and-set)某个内存变量,由于它是原子操作,所以在该操作完成之前其他执行单元不可能访问这个内存变量。
如果测试结果表明锁已经空闲,则程序获得这个自旋锁并继续执行;如果测试结果表明锁仍被占用,程序将在一个小的循环内重复这个“测试并设置”操作,即进行所谓的“自旋” ,通俗地说就是“在原地打转” 。当自旋锁的持有者通过重置该变量释放这个自旋锁后,某个等待的“测试并设置”操作向其调用者报告锁已释放。
理解自旋锁最简单的方法是把它作为一个变量看待,该变量把一个临界区或者标记为“我当前在运行,请稍等一会”或者标记为“我当前不在运行,可以被使用” 。如果 A 执行单元首先进入例程,它将持有自旋锁;当 B 执行单元试图进入同一个例程时,将获知自旋锁已被持有,需等到 A 执行单元释放后才能进入。Linux 系统中与自旋锁相关的操作主要有如下 4 种。
1 定义自旋锁spinlock_t spin;
2 初始化自旋锁该宏用于动态初始化自旋锁 lock
3 获得自旋锁该宏尝试获得自旋锁 lock,如果能立即获得锁,它获得锁并返回真,否则立即返回假,实际上不再“在原地打转” ;
4 释放自旋锁
spin_unlock(lock)
该宏释放自旋锁 lock,它与 spin_trylock 或 spin_lock 配对使用。
自旋锁一般这样被使用,如下所示:
//定义一个自旋锁 spinlock_t lock; spin_lock_init(&lock); spin_lock (&lock) ; //获取自旋锁,保护临界区 . . . //临界区 spin_unlock (&lock) ; //解锁
自旋锁主要针对 SMP 或单 CPU 但内核可抢占的情况,对于单 CPU 和内核不支持抢占的系统,自旋锁退化为空操作。在单 CPU 和内核可抢占的系统中,自旋锁持有期间内核的抢占将被禁止。由于内核可抢占的单 CPU 系统的行为实际很类似于 SMP系统,因此,在这样的单 CPU 系统中使用自旋锁仍十分必要。
尽管用了自旋锁可以保证临界区不受别的 CPU 和本 CPU 内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候还可能受到中断和底半部(BH)的影响。
为了防止这种影响,就需要用到自旋锁的衍生。spin_lock()/spin_unlock()是自旋锁机制的基础,它们和关中断 local_irq_ disable()/开中断 local_irq_enable() 、关底半部local_bh_disable()/开底半部 local_bh_enable()、关中断并保存状态字 local_irq_save()/开中断并恢复状态 local_irq_restore()结合就形成了整套自旋锁机制,关系如下所示:
spin_lock_irq() = spin_lock() + local_irq_disable()
spin_unlock_irq() = spin_unlock() + local_irq_enable()
spin_lock_irqsave() = spin_unlock() + local_irq_save()
spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()
spin_lock_bh() = spin_lock() + local_bh_disable()
spin_unlock_bh() = spin_unlock() + local_bh_enable()
驱动工程师应谨慎使用自旋锁,而且在使用中还要特别注意如下几个问题。
l 自旋锁实际上是忙等锁,当锁不可用时,CPU 一直循环执行“测试并设置”该锁直到可用而取得该锁, CPU 在等待自旋锁时不做任何有用的工作, 仅仅是等待。 因此, 只有在占用锁的时间极短的情况下, 使用自旋锁才是合理的。当临界区很大或有共享设备的时候,需要较长时间占用锁,使用自旋锁会降低系统的性能。
l 自旋锁可能导致系统死锁。 引发这个问题最常见的情况是递归使用一个自旋锁,即如果一个已经拥有某个自旋锁的 CPU 想第二次获得这个自旋锁,则该 CPU 将死锁。此外,如果进程获得自旋锁之后再阻塞,也有可能导致死锁的发生。copy_from_user()、copy_to_user()和 kmalloc()等函数都有可能引起阻塞,因此在自旋锁的占用期间不能调用这些函数。
代码清单 7.2 给出了自旋锁的使用实例,它被用于实现使得设备只能被最多一个进程打开。
代码清单 7.2 使用自旋锁使设备只能被一个进程打开
int xxx_count = 0;/*定义文件打开次数计数*/ static int xxx_open(struct inode *inode,struct file *filp) { ... spinlock(&xxx_lock); if (xxx_count)/*已经打开*/ { spin_unlock(&xxx_lock); return - EBUSY; } xxx_count++;/*增加使用计数*/ spin_unlock(&xxx_lock); ... return 0; /* 成功 */ } static int xxx_release(struct inode *inode, struct file *filp) { ... spinlock(&xxx_lock); xxx_count--; /*减少使用计数*/ spin_unlock(&xxx_lock); return 0; }
7.4.2 读写自旋锁
自旋锁不关心锁定的临界区究竟进行怎样的操作,不管是读还是写,它都一视同仁。即便多个执行单元同时读取临界资源也会被锁住。实际上,对共享资源并发访问时, 多个执行单元同时读取它是不会有问题的, 自旋锁的衍生锁读写自旋锁 (rwlock)可允许读的并发。读写自旋锁涉及的操作如下所示。
1 定义和初始化读写自旋锁
rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* 静态初始化 */
rwlock_t my_rwlock;
rwlock_init(&my_rwlock); /* 动态初始化 */
2 读锁定
void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
void read_lock_irq(rwlock_t *lock);
void read_lock_bh(rwlock_t *lock);
3 读解锁
void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);
在对共享资源进行读取之前,应该先调用读锁定函数,完成之后应调用读解锁函数。
read_lock_irqsave() 、read_lock_irq() 和 read_lock_bh() 分别是 read_lock() 分别与local_irq_save() 、 local_irq_disable() 和 local_bh_disable() 的 组 合, 读 解锁 函数read_unlock_irqrestore()、read_unlock_ irq()、read_unlock_bh()的情况与此类似。
4 写锁定
void write_lock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
int write_trylock(rwlock_t *lock);
5 写解锁
void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);
write_lock_irqsave() 、 write_lock_irq() 、 write_lock_bh() 分别是 write_lock() 与local_irq_save() 、 local_irq_disable() 和 local_bh_disable() 的 组 合, 写 解锁 函数write_unlock_irqrestore()、write_unlock_irq()、write_unlock_bh()的情况与此类似。
在对共享资源进行读取之前,应该先调用写锁定函数,完成之后应调用写解锁函数。和 spin_trylock()一样, write_trylock()也只是尝试获取读写自旋锁,不管成功失败,都会立即返回。
读写自旋锁一般这样被使用,如下所示:
rwlock_t lock; //定义 rwlock rwlock_init(&lock); //初始化 rwlock //读时获取锁 read_lock(&lock); ... //临界资源 read_unlock(&lock); //写时获取锁 write_lock_irqsave(&lock, flags); ... //临界资源 write_unlock_irqrestore(&lock, flags);