spinlock结构
1 | arch_spinlock_t |
api
arch_spin_is_locked
1 | static inline int arch_spin_is_locked(arch_spinlock_t *lock) |
自旋锁的变体
接口 API 的类型 | spinlock 中的定义 | raw_spinlock 的定义 |
---|---|---|
定义 spin lock 并初始化 | DEFINE_SPINLOCK | DEFINE_RAW_SPINLOCK |
动态初始化 spin lock | spin_lock_init | raw_spin_lock_init |
获取指定的 spin lock | spin_lock | raw_spin_lock |
获取指定的 spin lock 同时 disable 本 CPU 中断 | spin_lock_irq | raw_spin_lock_irq |
保存本 CPU 当前的 irq 状态,disable 本 CPU 中断并获取指定的 spin lock | spin_lock_irqsave | raw_spin_lock_irqsave |
获取指定的 spin lock 同时 disable 本 CPU 的 bottom half | spin_lock_bh | raw_spin_lock_bh |
释放指定的 spin lock | spin_unlock | raw_spin_unlock |
释放指定的 spin lock 同时 enable 本 CPU 中断 | spin_unlock_irq | raw_spin_unock_irq |
释放指定的 spin lock 同时恢复本 CPU 的中断状态 | spin_unlock_irqstore | raw_spin_unlock_irqstore |
获取指定的 spin lock 同时 enable 本 CPU 的 bottom half | spin_unlock_bh | raw_spin_unlock_bh |
尝试去获取 spin lock,如果失败,不会 spin,而是返回非零值 | spin_trylock | raw_spin_trylock |
判断 spin lock 是否是 locked,如果其他的 thread 已经获取了该 lock,那么返回非零值,否则返回 0 | spin_is_locked | raw_spin_is_locked |
mips 体系加锁分析
arch_spin_lock
1 | static inline void arch_spin_lock(arch_spinlock_t *lock) |
subsection的作用是分割线, subsection前面的部分为函数本应该在的位置, subsection后面的部分直接拼接到了函数尾部.
比如函数调用结束后, 一般会调用jr ra
返回, 而subsection后面的部分直接拼在了这条jr ra
后面, 不破坏函数的执行, 只有b语句 标签[no]f 的时候才会跳到这里,否则程序一条一条往下运行, 永远不会跑到这里
这个函数做了两件事:
- 每来一个调用, 就给它一个号, 号每次+1.
- 判断serving_now 与 ticket是否相等, 相等的话, 就退出函数, 不相等,就让这个调用方在这等着, 等ticket 与 serving_now 相等.
这里两条原子性操作 ll``sc
保证读取与写入lock(共享内存)时, 读操作和写操作都是原子性的, 函数最后调用了sync, 保证了cache同步.
sync的作用可以参考, 简单概括就是 约束执行CPU上的读写操作的顺序,CPU一定是完成read memory barrier之前的读写操作之后,才开始执行read memory barrier之后的读写操作。
内存屏障(Memory Barrier)究竟是个什么鬼? - 知乎
那什么时候serving_now变化的呢. 就需要看下释放锁的函数
arch_spin_unlock
1 | static inline void arch_spin_unlock(arch_spinlock_t *lock) |
小结
多个函数片段拿锁时, 会陷入到arch_spin_lock的等待中, 每个调用都给分了一个ticket, 在subsection 4
中, 每个调用持有的是my_ticket
, 即调用arch_spin_lock
时分给这个调用过程的ticket号, 这个号属于该次调用.
在这个循环中, 最终延时了一定数目的时钟周期后, 都要判断my_ticket
与内存中的serving_now
是否相等, 相等则退出arch_spin_lock的等待.
而arch_spin_unlock
放锁过程, 会把serving_now++, 存回到共享内存中.
每个因调用arch_spin_lock陷入到循环中的调用体, 等到自己的my_ticket与serving_now相等时, 解除等待状态.
spin_lock的主体是调用过程, 而不是本线程, 或本函数, 不支持递归拿锁或反复拿锁
取号机制可以避免有的调用过程被饿死, 防止多个调用过程之间的无序竞争.
arch_spin_trylock
1 | static inline unsigned int arch_spin_trylock(arch_spinlock_t *lock) |
尝试拿锁, 即判断my_ticket 与 now_serving 是否相等, 相等则返回1, 表示能拿到锁
读写锁
允许多个调用过程同时持同一个读锁, 但同一时刻只能有一个写锁.
arch_read_lock
1 | static inline void arch_read_lock(arch_rwlock_t *rw) |
有写锁时阻塞, 没有写锁, 多个读可以同时调用
arch_read_unlock
1 | static inline void arch_read_unlock(arch_rwlock_t *rw) |
arch_write_lock
1 | static inline void arch_write_lock(arch_rwlock_t *rw) |
为0时才可以持锁(有读锁或写锁时,自旋等待), 持锁后, lock被写入0x80000000
. 锁上期间新的调用过程拿不到锁, 直到该调用过程把锁放掉
arch_write_unlock
1 | static inline void arch_write_unlock(arch_rwlock_t *rw) |