一、自旋锁概念

自旋锁(spin lock)是一种对临界资源进行互斥所访问的典型手段,它是为防止多处理器并发而引入的一种锁,在内核钟大量用于中断处理部分。对于单处理器来说,防止中断钟的并发可简单采用关闭中断的方式,直接打开关闭中断不需要自旋锁。自旋锁在锁定的时候不会休眠,一直等待尝试,因此不合适进行长时间的锁保持。自旋锁要慎用,只有在内核可抢占式或SMP的情况下才会用到,自旋锁适用于锁使用者保持锁的时间比较久的情况下。

二、自旋锁的使用

Linux内核同步方法–自旋锁
Linux内核–内核自旋锁的理解

2.1 简单自旋锁

  • 定义自旋锁

    spinlock_t spin;
    
  • 初始化自旋锁

    spin_lock_init(lock);  // 动态
    DEFINE_SPINLOCK(lock); // 静态
    
  • 获得自旋锁,若立即获得锁则返回,否则将自旋到锁的保持者释放

    spin_lock(lock);
    
  • 尝试获得自旋锁,若立即获得则返回,否则返回假。

    spin_trylock(lock);
    
  • 释放自旋锁

    spin_unlock(lock);
    

2.2 中断自旋锁

尽管用了自旋锁可以保证临界区不受别的CPU和本CPU内的抢占进程打扰,但是得到的锁代码路径在执行临界区的时候还可能收到中断和底半部的影响。在简单的自旋锁里面,仍然有机会被中断打破原子操作过程,因此需要禁用中断来维持原子状态。

  • 定义自旋锁

    spinlock_t spin;
    
  • 初始化自旋锁

    spin_lock_init(lock);
    
  • 保存中断当前状态,并禁止本地中断,然后获取指定的锁

    spin_lock_irqsave(lock, flags);
    
  • 解锁指定锁,然后让中断恢复到加锁前状态

    spin_unlock_irqrestore(lock, flags);
    
  • 禁止本地中断并获取指定的锁(能确定中断在加锁前是激活的)

    spin_lock_irq(lock);
    
  • 释放指定的锁,并激活本地中断

    spin_unlock_irq(lock);
    

2.3 自旋锁函数使用规则

不会在任何中断例程中操作临界区

spin_lock(lock);
spin_unlock(lock);

在软件中断中操作临界区

spin_lock_bh(lock);
spin_unlock_bh(lock);

在硬件中断中操作临界区

spin_lock_irq(lock);
spin_unlock_irq(lock);

在硬件中端中要保存中断状态

spin_lock_irqsave(lock, flags);
spin_unlock_irqrestore(lock, flags);

如果被保护的共享资源只在进程上下文访问和软中断(包括tasklet、timer)上下文访问,那么当在进程上下文访问共享资源时,可能被软中断打断,从而可能进入软中断上下文来对被保护的共享资源访问,因此对于这种情况,对共享资源的访问必须使用spin_lock_bh和spin_unlock_bh来保护。当然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它们失效了本地硬中断,失效硬中断隐式地也失效了软中断。但是使用spin_lock_bh和spin_unlock_bh是最恰当的,它比其他两个快。

如果被保护的共享资源只在两个或多个tasklet或timer上下文访问,那么对共享资源的访问仅需要用spin_lock和spin_unlock来保护,不必使用_bh版本,因为当tasklet或timer运行时,不可能有其他tasklet或timer在当前CPU上运行。

如果被保护的共享资源只在一个tasklet或timer上下文访问,那么不需要任何自旋锁保护,因为同一个tasklet或timer只能在一个CPU上运行,即使是在SMP环境下也是如此。实际上tasklet在调用tasklet_schedule标记其需要被调度时已经把该tasklet绑定到当前CPU,因此同一个tasklet决不可能同时在其他CPU上运行。timer也是在其被使用add_timer添加到timer队列中时已经被帮定到当前CPU,所以同一个timer绝不可能运行在其他CPU上。当然同一个tasklet有两个实例同时运行在同一个CPU就更不可能了。

如果被保护的共享资源只在一个软中断(tasklet和timer除外)上下文访问,那么这个共享资源需要用spin_lock和spin_unlock来保护,因为同样的软中断可以同时在不同的CPU上运行。

如果被保护的共享资源在两个或多个软中断上下文访问,那么这个共享资源当然更需要用spin_lock和spin_unlock来保护,不同的软中断能够同时在不同的CPU上运行。

如果被保护的共享资源在软中断(包括tasklet和timer)或进程上下文和硬中断上下文访问,那么在软中断或进程上下文访问期间,可能被硬中断打断,从而进入硬中断上下文对共享资源进行访问,因此,在进程或软中断上下文需要使用spin_lock_irq和spin_unlock_irq来保护对共享资源的访问。而在中断处理句柄中使用什么版本,需依情况而定,如果只有一个中断处理句柄访问该共享资源,那么在中断处理句柄中仅需要spin_lock和spin_unlock来保护对共享资源的访问就可以了。因为在执行中断处理句柄期间,不可能被同一CPU上的软中断或进程打断。

以上需要在实战中体会再重新整理

正常来说,如果自旋锁都是再进程上下文中使用,则建议使用spin_lock/spin_unlock;
若中断处理中要使用自旋锁,则所有争用锁的地方使用spin_lock_irqsave/spin_unlock_irqrestore

三、总结

自旋锁的使用基本雷同互斥锁,用的时候注意临界区即可。

附、例程

spinlock_t lock;
spin_lock_init(&lock);
spin_lock_irqsave(&lock, flags);
...
spin_unlock_irqrestore(&lock, flags);
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐