Linux自旋锁使用场景分析

Linux自旋锁使用场景分析

  • 自旋锁
  • 场景分析
    • 仅在进程上下文中使用自旋锁
    • 进程上下文和中断中使用自旋锁
    • 进程上下文和中断下半部中使用自旋锁
    • 中断上下文之间的竞争
  • 总结

自旋锁

  spinlock同一时刻只能被一个内核代码路径持有,如果有另外一个内核代码试图获取一个已经被持有的spinlock,那么该内核代码路径需要一直自旋忙等到,直到锁持有者释放了该锁。如果该锁没有被别人持有,那么可以立即获得该锁。特性如下:

  • 忙等待的锁机制。操作系统总锁的机制分为两类,一类是忙等待,另一类是睡眠等待。spinlock属于前者,当无法获取spinlock锁时会不断尝试,直到获取锁为止。

  • 同一时刻只能有一个内核代码路径可以获得该锁。

  • 要求spinlock锁持有者尽快完成临界区的执行任务。如果临界区执行时间过程,在锁外面忙等待的CPU比较浪费,特别是spinlock临界区里不能睡眠。

  • spinlock锁可以在中断上下文中使用。

场景分析

仅在进程上下文中使用自旋锁

  • A进程在某段代码中访问共享资源S
  • B进程在某段代码中也需要访问共享资源S

  假设在A访问共享资源S的过程中发生了中断,中断唤醒了沉睡中的,优先级更高的B,在中断返回现场的时候,发生了进程切换。B程序开始执行,并且同样需要访问共享资源S。如果这时候没有锁保护,那么就会出现两个线程进行竞争资源的情况,导致程序出错。
  加上spinlock后,A在进入临界区之前获取了spinlock,同时,A在访问共享资源S的时候发生了中断,中断中还是唤醒了优先级更高的进程B,那么B在访问临界区之前仍然会获取spinlock,这时由于A占有了spinlock,所以B进程会永久的等待spinlock,即发生了死锁。
  结合上面的场景分析,自旋锁在同一cpu上必须要做的操作就是,关闭本地CPU上的进程抢占。不管是在单核还是多核场景下,关闭本地cpu进程抢占都是必不可少的。spinlock关闭本地抢占后,上述中断场景下,发生中断后,由于不允许发生进程抢占,所以中断结束后还是会回到进程A执行。

  如果A和B运行在不同cpu上,那么场景就简单很多了,虽然A获取了spinlock导致B在自旋获取spinlock消耗资源。但A操作完共享资源S后会立刻释放资源,B进程就能获取到spinlock了。

进程上下文和中断中使用自旋锁

  • A进程在某段代码中访问共享资源S
  • 外设X的中断处理函数也会访问共享资源S

  在这样的场景下,还可以用spinlock保护共享资源S吗?
  假设A在进入临界区之前获取了spinlock,随后外设X的中断来临,且在当前cpu上处理,即打断了spinlock临界区的操作。这时候在X中断中去获取spinlock就会一直自旋,因为spinlock已经被A获取了,所以这时候就会死锁。
  结合上述分析,在A进入临界区获取spinlock时,需要关闭本地cpu中断。即使用spin_lock_irq/spin_unlock_irq或者spin_lock_irqsave/spin_unlock_irqrestore接口。

  如果A和外设X中断服务函数在不同cpu上,那么首先,A使用spin_lock_irqsave不会影响X中断中获取自旋锁的行为。但是多核情况下,中断可能可以保证每次都在一个cpu上执行,但是进程会在不同cpu间切换运行。所以在设计驱动时还是要使用spin_lock_irqsave接口来防止出现死锁情况。

进程上下文和中断下半部中使用自旋锁

  大多数时候,驱动代码在实现的时候,中断上半部仅仅清除了硬件中断状态,随机进行下半部处理。这里说的下半部是指softirqtasklet
  其实场景和中断情况类似,当中断处理完退出(irq_exit)时检查软中断并执行时,如果发生了spinlock竞争情况,也会出现死锁情况。
  但是仅仅因为下半部中spinlock的使用,就把本地中断禁止,就未免得不偿失了。所以Linux又设计了禁止下半部的spinlock接口。即spin_lock_bh/spin_unlock_bh

  补充一点软中断的说明,spin_lock_irq是硬件/架构相关的操作。因为软中断其实就是软件依托中断上下文进行处理的行为,所以spin_lock_bh仅仅就是增加了当前taskthread_info数据结构的preemt_count中软中断的计数。
  preempt_count变量中包含了抢占计数,软中断计数,硬中断计数。其中软中断计数占了8bit。但是Linux其实是不支持软中断嵌套的。所以软中断计数其实只有0和1,即只占了1Bit。这里Linux做了点手脚,禁止软中断时,在软中断计数的Bit1开始增加。用于区分是在软中断处理过程中还是禁止软中断调度(disable_bh)。

中断上下文之间的竞争

  同一种中断handler之间在单核和smp多核上都不会并行执行,这是linux kernel的特性。
  如果不同中断handler需要使用spinlock保护共享资源,对于新的内核,所有handler都是关闭中断的,因此使用spinlock不需要关闭中断的配合。

  bottom half又分成softirqtasklet,同一种softirq会在不同的CPU上并发执行,因此如果某个驱动中的softirqhandler中会访问某个全局变量,对该全局变量是需要使用spinlock保护的,不用配合disable CPU中断或者bottom half

  tasklet更简单,因为同一种tasklet不会多个CPU上并发。

  如果是中断上半部和下半部共用一个spinlock来保护资源。那么下半部使用spinlock时,还是需要spin_lock_irq()。因为软中断环境下,中断又重新使能了,这时候软中断还是有可能会被中断打断,并出现死锁情况的。更何况,软中断负载特别大的情况下,软中断会被延后到`ksoftirqd线程中进行,即有概率在进程上下文中被执行。

总结

spin_lock() 的时候,禁止内核抢占

如果涉及到中断上下文的访问,spinlock需要和禁止本 CPU 上的中断联合使用:spin_lock_irq/spin_unlock_irqspin_lock_irqsave/spin_unlock_irqrestore

涉及 half bottom 使用:spin_lock_bh / spin_unlock_bh

你可能感兴趣的:(Linux,服务器,运维,linux,spinlock)