抢占式内核与非抢占式内核中的自旋锁(spinlock)的差别
spin_lock()
在Linux2.6中,spin_lock()宏有两种实现方式,一种是具有内核抢占的spin_lock(),一种是非抢占式内核中的spin_lock(),下面先看下自旋锁的数据结构,在Linux中,每个自旋锁都用spinlock_t结构表示,如下:
typedef struct { /** * 该字段表示自旋锁的状态,值为1表示未加锁,任何负数和0都表示加锁 */ volatile unsigned int slock; #ifdef CONFIG_PREEMPT /** * 表示进程正在忙等待自旋锁。 * 只有内核支持SMP和内核抢占时才使用本标志。 */ unsigned int break_lock; #endif } spinlock_t;
spin_lock()定义如下:
#define spin_lock(lock) _spin_lock(lock)
具有内核抢占的_spin_lock宏
/** * 通过BUILD_LOCK_OPS(spin, spinlock);定义了_spin_lock,进而实现了spin_lock * 这是在具有内核抢占时,spin_lock的实现。 */ #define BUILD_LOCK_OPS(op, locktype) \ void __lockfunc _##op##_lock(locktype##_t *lock) \ { \ /** * preempt_disable禁用内核抢占。 * 必须在测试spinlock的值前,先禁止抢占,原因很简单: * 下面的循环中有可能会成功获得自旋锁,如果获得锁之后被抢占了,将造成死锁 */ preempt_disable(); \ for (;;) { \ /** * 调用_raw_spin_trylock,它对自旋锁的slock字段进行原子性的测试和设置。 * 本质上它执行以下代码: * movb $0,%al * xchgb %al, slp->slock * xchgb原子性的交换al和slp->slock内存单元的内容。如果原值>0,就返回1,否则返回0 * 换句话说,如果原来的锁是开着的,就关掉它,它返回成功标志。如果原来就是锁着的,再次设置锁标志,并返回0。 */ if (likely(_raw_##op##_trylock(lock))) \ /** * 如果旧值是正的,表示锁是打开的,宏结束,已经获得自旋锁了。 * 注意:返回后,本函数的一个负作用就是禁用抢占了。配对使用unlock时再打开抢占。 * 请想一下禁用抢占的必要性。 */ break; \ /** * 否则,无法获得自旋锁,就循环一直到其他CPU释放自旋锁。 * 在循环前,暂时打开preempt_enable。也就是说,在等待自旋锁的中间,进程是可能被抢占的。 */ preempt_enable(); \ /** * break_lock表示有其他进程在等待锁。 * 拥有锁的进程可以判断这个标志,如果进程把持锁的时间太长,可以提前释放锁。 */ if (!(lock)->break_lock) \ (lock)->break_lock = 1; \ /** * 执行等待循环,cpu_relax简化成一条pause指令,对应rep;nop,即空操作 * 为什么要加入cpu_relax,是有原因的,表面上看,可以用一段死循环的汇编来代替这个循环 * 但是实际上是不能那样的的,那样会锁住总线,unlock想设置值都不能了。 * cpu_relax就是要让CPU休息一下,把总线暂时让出来。 */ while (!op##_can_lock(lock) && (lock)->break_lock) \ cpu_relax(); \ /** * 上面的死循环lock的值已经变化了。那么关抢占后,再次调用_raw_spin_trylock * 真正的获得锁还是在_raw_spin_trylock中。 */ preempt_disable(); \ } \ }
_raw_spin_trylock如下:
static inline int _raw_spin_trylock(spinlock_t *lock) { char oldval; __asm__ __volatile__( "xchgb %b0,%1" :"=q" (oldval), "=m" (lock->slock) :"0" (0) : "memory"); return oldval > 0; }
非抢占式内核中的_spin_lock()
void __lockfunc _spin_lock(spinlock_t *lock) { preempt_disable(); //一直关闭可抢占 , _raw_spin_lock(lock); }
_raw_spin_lock(lock)如下:
/** * 对自旋锁的slock字段执行原子性的测试和设置操作。 */ static inline void _raw_spin_lock(spinlock_t *lock) { #ifdef CONFIG_DEBUG_SPINLOCK if (unlikely(lock->magic != SPINLOCK_MAGIC)) { printk("eip: %p\n", __builtin_return_address(0)); BUG(); } #endif __asm__ __volatile__( spin_lock_string :"=m" (lock->slock) : : "memory"); } /* spin_lock_string如下: */ #define spin_lock_string \ "\n1:\t" \ /** * %0对应上面的lock->slock * decb递减自旋锁的值。它有lock前缀,因此是原子的。 */ "lock ; decb %0\n\t" \ /** * 如果结果为0(不是负数),说明锁是打开的,跳到3f处继续执行。 */ "jns 3f\n" \ /** * 否则,结果为负,说明锁是关闭的。就执行死循环,等待它的值变化。 */ "2:\t" \ "rep;nop\n\t" \ /** * 比较lock值,直到它变化,才跳到开头,试图再次获得锁。 * 否则,继续死循环等lock值变化。 ..自选期间不打开可抢占 */ "cmpb $0,%0\n\t" \ "jle 2b\n\t" \ "jmp 1b\n" \ "3:\n\t"
arch_spin_lock
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2021-03-09 Packet fragmentation and segmentation offload in UDP and VXLAN
2021-03-09 vxlan 内核实现
2021-03-09 数据中心网络 BBR 不如 CUBIC ?
2021-03-09 perf 系统调用
2021-03-09 quic NAT
2021-03-09 机器学习在ABR算法中的应用纵览
2021-03-09 清华最新AIOps案例:强化学习,降低网络传输延时