为什么需要内核自旋锁?
现在很多CPU都是几核几核的了,如果有一个变量A,CPU-X正在访问,突然CPU-Y也过来访问他,这时候就可能出现问题,因为这个A非常重要,可能导致系统崩溃,中断异常等。
我们来看之前说的TP驱动里面的代码
void gtp_irq_enable(struct goodix_ts_data *ts)
{
unsigned long irqflags = 0;
GTP_DEBUG_FUNC();
spin_lock_irqsave(&ts->irq_lock, irqflags);
if (ts->irq_is_disable)
{
enable_irq(ts->client->irq);
ts->irq_is_disable = 0;
}
spin_unlock_irqrestore(&ts->irq_lock, irqflags);
}
在进行中断操作的时候,用到了自旋锁,就是担心正在操作的时候又被调用,听起来有点拗口,但是就是那么一回事。
自旋锁(spinlock)是用在多个CPU系统中的锁机制,当一个CPU正访问自旋锁保护的临界区时,临界区将被锁上,其他需要访问此临界区的CPU只能忙等待,直到前面的CPU已访问完临界区,将临界区开锁。自旋锁上锁后让等待线程进行忙等待而不是睡眠阻塞,而信号量是让等待线程睡眠阻塞。自旋锁的忙等待浪费了处理器的时间,但时间通常很短,在1毫秒以下。
自旋锁用于多个CPU系统中,在单处理器系统中,自旋锁不起锁的作用,只是禁止或启用内核抢占。在自旋锁忙等待期间,内核抢占机制还是有效的,等待自旋锁释放的线程可能被更高优先级的线程抢占CPU。
自旋锁基于共享变量。一个线程通过给共享变量设置一个值来获取锁,其他等待线程查询共享变量是否为0来确定锁现是否可用,然后在忙等待的循环中"自旋"直到锁可用为止。
通用自旋锁解析
自旋锁的状态值为1表示解锁状态,说明有1个资源可用;0或负值表示加锁状态,0说明可用资源数为0。Linux内核为通用自旋锁提供了API函数初始化、测试和设置自旋锁。API函数功能说明如下表。
通用自旋锁API函数功能说明
函数定义 功能说明
spin_lock_init(lock) 初始化自旋锁,将自旋锁设置为1,表示有一个资源可用。
spin_is_locked(lock) 如果自旋锁被置为1(未锁),返回0,否则返回1。
spin_unlock_wait(lock) 等待直到自旋锁解锁(为1),返回0;否则返回1。
spin_trylock(lock) 尝试锁上自旋锁(置0),如果原来锁的值为1,返回1,否则返回0。
spin_lock(lock) 循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。
spin_unlock(lock) 将自旋锁解锁(置为1)。
spin_lock_irqsave(lock, flags) 循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。关中断,将状态寄存器值存入flags。
spin_unlock_irqrestore(lock, flags) 将自旋锁解锁(置为1)。开中断,将状态寄存器值从flags存入状态寄存器。
spin_lock_irq(lock) 循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。关中断。
spin_unlock_irq(lock) 将自旋锁解锁(置为1)。开中断。
spin_unlock_bh(lock) 将自旋锁解锁(置为1)。开启底半部的执行。
spin_lock_bh(lock) 循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。阻止软中断的底半部的执行。
自旋锁小例子
下面用一个使用自旋锁锁住链表的样例,代码列出如下(在gt9xx.c中):
/*初始化变量锁*/
spin_lock_init(&ts->irq_lock); // 2.6.39 内核版本后的初始化使用方式
// ts->irq_lock = SPIN_LOCK_UNLOCKED; // 2.6.39 内核版本之前的初始化使用方式
void gtp_irq_enable(struct goodix_ts_data *ts)
{
unsigned long irqflags = 0;// 中断上下文使用的变量
GTP_DEBUG_FUNC();
spin_lock_irqsave(&ts->irq_lock, irqflags);//加锁
if (ts->irq_is_disable)
{
enable_irq(ts->client->irq);
ts->irq_is_disable = 0;
}
spin_unlock_irqrestore(&ts->irq_lock, irqflags);//解锁
}
自旋锁相关源码
自旋锁用结构spinlock_t描述,在include/linux/spinlock.h中有类型 spinlock_t定义,列出如下:
typedef struct {
raw_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK /*引入另一个自旋锁*/
unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK /*用于调试自旋锁*/
unsigned int magic, owner_cpu;
void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map; /*映射lock实例到lock-class对象
#endif
} spinlock_t;
(1)spin_lock_init
函数spin_lock_init将自旋锁状态值设置为1,表示未锁状态。其列出如下(在include/linux/spinlock.h中):
# define spin_lock_init(lock) /
do { *(lock) = SPIN_LOCK_UNLOCKED; } while (0)
宏__SPIN_LOCK_UNLOCKED列出如下(在include/linux/spinlock_types.h中):
# define __SPIN_LOCK_UNLOCKED(lockname) /
(spinlock_t) { .raw_lock = __RAW_SPIN_LOCK_UNLOCKED, /
SPIN_DEP_MAP_INIT(lockname) }
#define __RAW_SPIN_LOCK_UNLOCKED { 1 }
(2)函数spin_lock_irqsave
函数spin_lock_irqsave等待直到自旋锁解锁,即自旋锁值为1,它还关闭本地处理器上的中断。其列出如下(在include/linux/spinlock.h中):
#define spin_lock_irqsave(lock, flags) flags = _spin_lock_irqsave(lock)
函数spin_lock_irqsave分析如下(在kernel/spinlock.c中):
unsigned long __lockfunc _spin_lock_irqsave(spinlock_t *lock)
{
unsigned long flags;
local_irq_save(flags); //将状态寄存器的值写入flags保存
preempt_disable(); //关闭内核抢占,内核抢占锁加1
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
#ifdef CONFIG_LOCKDEP
LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
#else
_raw_spin_lock_flags(lock, &flags);
#endif
return flags;
}
宏定义local_irq_save保存了状态寄存器的内容到x中,同时关中断。这个宏定义列出如下:
#define local_irq_save(x) __asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x): /* no input */ :"memory")
上述语句中,指令pushfl将当前处理器的状态寄存器的内容压入堆栈保护。指令popl %0 将状态寄存器的内容存入x中,其中%0这里指x。
函数_raw_spin_lock_flags空操作等待直到自旋锁的值为1,表示有资源可用,就跳出循环等待,准备执行本函数后面的操作。其列出如下:
# define _raw_spin_lock_flags(lock, flags) /
__raw_spin_lock_flags(&(lock)->raw_lock, *(flags
函数__raw_spin_lock_flags列出如下(在include/asm-x86/spinlock.h中):
#define __raw_spin_lock_flags(lock, flags) __raw_spin_lock(lock)
static __always_inline void __raw_spin_lock(raw_spinlock_t *lock)
{
int inc = 0x00010000;
int tmp;
/*指令前缀lock用来锁住内存控制器,不让其他处理器访问,保证指令执行的原子性*/
asm volatile("lock ; xaddl %0, %1/n" // lock->slock=lock->slock+inc
"movzwl %w0, %2/n/t" //tmp=inc
"shrl $16, %0/n/t" //inc >> 16 后,inc=1
"1:/t"
"cmpl %0, %2/n/t" //比较inc与lock->slock
"je 2f/n/t" //如果inc与lock->slock相等,跳转到2
"rep ; nop/n/t" //空操作
"movzwl %1, %2/n/t" //tmp=lock->slock
/* 这里不需要读内存屏障指令lfence,因为装载是排序的*/
"jmp 1b/n" //跳转到1
"2:"
: "+Q" (inc), "+m" (lock->slock), "=r" (tmp)
:
: "memory", "cc");
}
(3)函数spin_unlock_irqrestore
宏定义spin_unlock_irqrestore是解锁,开中断,并把flags值存入到状态寄存器中,这个宏定义分析如下:
#define spin_unlock_irqrestore(lock, flags) _spin_unlock_irqrestore(lock, flags)
函数_spin_unlock_irqrestore列出如下(在kernel/spinlock.c中):
void __lockfunc _spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
{
spin_release(&lock->dep_map, 1, _RET_IP_);
_raw_spin_unlock(lock); //解锁
local_irq_restore(flags); //开中断,将flag的值存入状态寄存器
preempt_enable(); //开启内核抢占
}
# define _raw_spin_unlock(lock) __raw_spin_unlock(&(lock)->raw_lock)
函数__raw_spin_unlock将自旋锁状态值加1,表示有1个资源可用,从而释放自旋锁,其列出如下(在include/asm-x86/spinlock.h中):
static __always_inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
asm volatile(UNLOCK_LOCK_PREFIX "incw %0" // lock->slock= lock->slock +1
: "+m" (lock->slock)
:
: "memory", "cc");
}
自旋锁调试
由于自旋锁的性能严重地影响着操作系统的性能,Linux内核提供了Lock-class和Lockdep跟踪自旋锁的使用对象和锁的状态,并可从/proc文件系统查询自旋锁的状态信息。自旋锁的调试通过配置项CONFIG_DEBUG_*项打开。
对于对称多处理器系统(SMP),slock为一个int数据类型,对于单个处理器系统,slock定义为空。SMP的slock定义列出如下(在include/linux/spinlock_types.h):
typedef struct {
volatile unsigned int slock;
} raw_spinlock_t;