Spin Lock

昨天写了一下interrupt和exception的不同,在弄清以后,可以来研究linux kernel中的各种同步机制,各种锁了。 先写一下Spin Lock,其余的有时间再写。 Spin Locks 这个是很常见的,好多地方翻译成“自旋锁”,在临界区只允许一个进程进入(且该进程不允许休眠,例如中断处理程序)时经常使用。 spin lock使用spinlock_t结构体来实现,其定义在/include/linux/spinlock_types.h文件中
typedef struct spinlock {
union {
struct raw_spinlock rlock;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct  raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
如果不处于调试时,spinlock_t就是一个raw_spinlock结构体,raw_spinlock的定义也在这个文件中,如下
typedef struct raw_spinlock {
arch_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;
#endif
} raw_spinlock_t;
如果去除调试信息的话,只剩arch_spinlock_t和break_lock了,break_lock只在preemption enable状态下被编译,再来看arch_spinlock_t的定义,这个结构体的定义根据不同的体系结构而不同,在i386上如下
typedef struct arch_spinlock {
unsigned int slock;
} arch_spinlock_t;
可见,其实它就是一个整数。 再来看它的使用。 下面有的文字来自于ULK。 当without kernel preemption时,加锁的代码如下
1: lock; decb slp->slock
jns 3f
2: pause
cmpb $0,slp->slock
jle 2b
jmp 1b
3:
也就是说,将slock值减1,然后判断是否小于零,如果小于零,则继续尝试,直到其大于零,然后再返回1尝试进行加锁。如是减之后不小于零,则证明加锁成功,继续执行下面代码。 当with kernel preemption时,加锁就有些不同了,代码我没仔细看,只把ULK中的描述记下来: 1.先关掉kernel preemption 2.调用_raw_spin_trylock(),这个函数大体如下
movb $0, %al
xchgb %al, slp->slock
也就是将slock置零,然后通过判断al的值来判断加锁是否成功。如果成功应该返回1,否则返回零(这个代码如此高效) 3.如果经判断,加锁已经成功,进程得以继续执行。 如果经判断,加锁失败,那么,会调用preempt_enable(),这时,有可能当前进程被调度出去。 4.将break_lock值设为1,这样,说明有别的进程在等待获得锁,如果占有锁的进程发现有进程在等待而且自己已经占有了较长时间,这可能会自愿将该锁让给别的进程。 5.执行如下代码
while (spin_is_locked(slp) && slp->break_lock)
cpu_relax();
cpu_relax()是从Pentium 4以后新增的指令,专为spin_lock而优化。相比rep;nop;来说,它执行的更快,更省电。 再来看解锁的代码
movb $1,slp->slock
直接将slock置1 毛老师书上说,使用movb而不用inc的原因是movb能节省指令周期,而且,ULK上讲,这个操作是原子的,而inc或者dec不是,所以,在加锁的代码中,dec指令前面加了lock,锁住总线。
原文地址:https://www.cnblogs.com/yangce/p/2910086.html