本文kernel代码分析基于以下
1.linux-4.14.159
2.64bit代码处理逻辑
我们在上面学习semaphore的时候知道其不能在有中断的场景下使用,这节我们看下自旋锁spinlock,这个主要用在有中断的场景下,其最大特点就是获取不了锁则会自旋忙等待既不让出cpu,在临界区入口死等,这主要用于SMP(多核)下的同步问题。
先概述下spinlock的特点:
1.spinlock不可递归,具有不可重入性,如果释放锁之前又调用了接口去获取这个锁,肯定获取不到从而引起cpu死锁
2. 是一种cpu忙等待的锁机制,即不会让出cpu调用其它进程,而是一直死等
3. 因为如上这些特性,其使用场景主要针对那些临界区资源执行比较短的场景,否则的话会造成cpu资源严重浪费
spinlock不足及改进:
1.老的的kernel版本的spinlock不能保证资源访问公平及有序性,Linux 2.6.25引入了排队策略(FIFO Ticket Spinlock)的概念,采取先访问者先获取锁即先来先到的原则保证了资源访问的公平及有序性,这在后面再重点介绍这个策略
2.有些共享资源的访问如共享内存时,对于并发读操作的spinlock保护是多余的或者说对于读写需要进一步细分的场景spinlock没有区别对待,spinlock又引入了读写 spinlock,这个后面会详细介绍。
一. spinlock
1.1 首先看下spinlock的数据结构:
//取掉了一些debug相关的code
typedef struct spinlock {
//@1
union {
struct raw_spinlock rlock;
};
} spinlock_t;
typedef struct raw_spinlock {
arch_spinlock_t raw_lock;
} raw_spinlock_t;
typedef struct {
//@2
#ifdef __AARCH64EB__ //@3
u16 next;
u16 owner;
#else
u16 owner;
u16 next;
#endif
} __aligned(4) arch_spinlock_t;
@1. 可以看到spinlock 封装了raw_spinlock,而后者又封装了和体系相关的arch_spinlock_t,
@2. 我们在开头提到了spinlock的排队策略,这里我们重点来解释下
a.我们看到arch_spinlock_t定义两个两个字段next和owner,
next表示下一个进程来获取锁时将给其分配的数值,owner表示存在这个数值的进程才配持有锁
b.开始时都为0,而只有当next=owner的进程才能获取锁;
c.每个进程获取锁时会next++,当释放锁时owner++
举例描述下这个过程:
a.第一个进程获取锁,next=owner=0,可以获取锁,然后(next++)=1
b.第一个释放锁前,无后续进程来申请锁,第一个进程释放锁owner++,此时next=owner=1,过程结束;
如果第一个释放锁前,此时陆续有三个进程来申请锁,goto step c
c.第二个进程申请锁next=1,owner=0;然后 (next++)=2 (先记录lock中next,再把next++写入到lock中,下同)
d.第三个进程申请锁next=2,owner=0;然后 (next++)=3
e.第四个进程申请锁next=3,owner=0;然后 (next++)=4
f.若第一个进程释放锁后(owner++)=1;而此时只有第二个进程获取时记录的next=1,
此时owner=next=1(这里next是申请时记录的next非next++后的),因此只有第二个进程能获取到锁,而其它进程继忙等待
g.这样先到的先给锁后到后给锁,保证了公平性和有序性,但其实这里没有考虑到优先级问题
@3 大小端的区别
1.2 初始化后我们看使用场景
spin_lock(lock);
...临界区资源