spin_lock
代码位置:/kernel/include/linux/spinlock.h
static __always_inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
可见,spin_lock的实现实际上是通过raw_spin_lock实现的。
看一下spinlock和raw_spinlock的结构体定义:/kernel/include/linux/spinlock_types.h
typedef struct spinlock {
union {
struct raw_spinlock rlock;//raw_spinlock是成员
#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;
......
typedef struct raw_spinlock {
arch_spinlock_t raw_lock;
#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;
可见,spinlock里包含raw_spinlock的成员,这才使spinlock调用raw_spinlock的函数变得可实现起来。
raw_spin_lock系列函数
在/kernel/include/linux/spinlock.h
中,
#define raw_spin_lock(lock) _raw_spin_lock(lock)
在/kernel/include/linux/spinlock_api_smp.h
中,
#define _raw_spin_lock(lock) __raw_spin_lock(lock)
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
其中,preempt_disable是用于关闭抢占的。(与之配套的函数是preempt_enable)
之所以要在真正获得锁之前关闭抢占,是为了防止系统进入死锁。
例如有线程A经过系统调用进入内核态,且需要访问临界区,这时它会获得互斥锁,随后进入临界区里。
若在A尚未离开临界区的过程中,又发生了一个中断,而此时内核是可抢占的,这说明此时需要按调度算法来判断线程的优先级决定谁拥有访问权。若恰巧,队列中有一个线程B的优先级在这一时刻高于进程A,则B将拥有处理器,A将被换出。若B也需要进入内核态来访问同一块临界区,则此时出现死锁。因A已被换出,故而无法释放锁,这导致B永远无法获得锁。
上述情况在单CPU和多CPU场景下均有可能出现。有可能是单cpu的多个线程同时访问同一个资源,也可能是来自多个cpu的多个线程同时访问同一个资源。
故而为了避免此种现象,需要在真正获得锁之前关闭掉系统抢占。在真正释放锁之后再打开系统抢占。
接着分析代码。在/kernel/include/linux/lockdep.h
里可以看到spin_acquire和LOCK_CONTENDED的定义。
#define spin_acquire(l, s, t, i) lock_acquire_exclusive(l, s, t, NULL, i)//由函数名称很清楚的看出来这是用于占有排他性锁的函数。
...... ......
#ifdef CONFIG_LOCK_STAT
extern void lock_contended(struct lockdep_map *lock, unsigned long ip);
extern void lock_acquired(struct lockdep_map *lock, unsigned long ip);
//这种情况下,对应__raw_spin_lock传进来的参数,其实是调用了do_raw_spin_try_lock(lock)
#define LOCK_CONTENDED(_lock, try, lock) \
do { \
if (!try(_lock)) { \
lock_contended(&(_lock)->dep_map, _RET_IP_); \
lock(_lock); \
} \
lock_acquired(&(_lock)->dep_map, _RET_IP_); \
} while (0)
...... ......
#else /* CONFIG_LOCK_STAT */
#define lock_contended(lockdep_map, ip) do {} while (0)
#define lock_acquired(lockdep_map, ip) do {} while (0)
//这种情况下,对应__raw_spin_lock传进来的参数,其实是调用了do_raw_spin_lock(lock)
#define LOCK_CONTENDED(_lock, try, lock) \
lock(_lock)
...... ......
#endif /* CONFIG_LOCK_STAT */
上述一个新属性是CONFIG_LOCK_STAT,关于它的详细介绍可以在/kernel/Documentation/locking/lockstat.txt
中找到。以下只贴一部分,有兴趣自行查看:
LOCK STATISTICS
- WHAT
As the name suggests, it provides statistics on locks.
- WHY
Because things like lock contention can severely impact performance.
- HOW
Lockdep already has hooks in the lock functions and maps lock instances to
lock classes. We build on that (see Documentation/locking/lockdep-design.txt).
The graph below shows the relation between the lock functions and the various
hooks therein.
//这是lock的主要功能流程图
__acquire
|
lock _____
| \
| __contended//锁占有权的竞争阶段
| |
| <wait>//等待
| _______/
|/
|
__acquired//竞争成功,获得锁
|
.
<hold>//保持
.
|
__release//释放锁
|
unlock
lock, unlock - the regular lock functions
__* - the hooks
<> - states
...... ......
- CONFIGURATION
Lock statistics are enabled via CONFIG_LOCK_STAT.
总结翻译过来就是,如果CONFIG_LOCK_STAT置1,则会开启记录locks详细属性的功能。设计目的和使用方法上面文档都写清楚了。
由LOCK_CONTENDED的定义,知道根据CONFIG的值,有两条调用路径。
do_raw_spin_try_lock
位置在/kernel/include/linux/spinlock.h
static inline int do_raw_spin_trylock(raw_spinlock_t *lock)
{
return arch_spin_trylock(&(lock)->raw_lock);//调用了arch的函数
}
arch位置:/kernel/arch/arm/include/asm/spinlock.h
static inline int arch_spin_trylock(arch_spinlock_t *lock)
{
unsigned long contended, res;
u32 slock;
prefetchw(&lock->slock);
do {
__asm__ __volatile__(
" ldrex %0, [%3]\n"
" mov %2, #0\n"
" subs %1, %0, %0, ror #16\n"
" addeq %0, %0, %4\n"
" strexeq %2, %0, [%3]"
: "=&r" (slock), "=&r" (contended), "=&r" (res)
: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
: "cc");
} while (res);
if (!contended) {
smp_mb();
return 1;
} else {
return 0;
}
}
do_raw_spin_lock
位置在/kernel/include/linux/spinlock.h
#ifdef CONFIG_DEBUG_SPINLOCK //如果定义了该属性,则do_raw_spin_lock的定义在别的文件里,此处不贴代码了
extern void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock);
......
extern int do_raw_spin_trylock(raw_spinlock_t *lock);
......
#else
static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
{
__acquire(lock);
arch_spin_lock(&lock->raw_lock);
}
......
#endif
arch位置:/kernel/arch/arm/include/asm/spinlock.h
/*
* ARMv6 ticket-based spin-locking.
*
* A memory barrier is required after we get a lock, and before we
* release it, because V6 CPUs are assumed to have weakly ordered
* memory.
*/
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
unsigned long tmp;
u32 newval;
arch_spinlock_t lockval;
prefetchw(&lock->slock);
__asm__ __volatile__(
"1: ldrex %0, [%3]\n"
" add %1, %0, %4\n"
" strex %2, %1, [%3]\n"
" teq %2, #0\n"
" bne 1b"
: "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
: "cc");
while (lockval.tickets.next != lockval.tickets.owner) {
wfe();
lockval.tickets.owner = READ_ONCE(lock->tickets.owner);
}
smp_mb();
}
总结:
由上述源码,可知其实最终一步调用的函数是当前系统架构里的处理函数。
PS:本部分略微高深,本人菜鸡,若有哪里写错了跪求大佬指导