内核同步机制——自旋锁

瘦欲@ 提交于 2020-12-10 06:35:33

        由于关键代码区可以跨越了多个函数或数据结构,需要有更通用的同步方法:锁。

内核中最常见的一种锁就是自旋锁。相同的锁可用于多处。

自旋锁可用在不可睡眠的场景,如中断处理函数。自旋锁是一种互斥设备,只有两个值:锁定非锁定。它通常实现为一个整数值的某个比特位。想获取某个锁的代码首先测试相关的位,如果锁可得,则该位的锁定位被置位,代码继续执行,反之,代码将进入一个紧凑的循环,不停地检测锁定位直至自旋锁变得可得。该循环是自旋锁的旋转部分。自旋锁主要用于多处理器的情况下。

1.通用自旋锁

相关操作:

DEFINE_SPINLOCK(mr_lock)

spinlock_tmy_lock = SPIN_LOCK_UNLOCKED;//静态初始化

voidspin_lock_init(spinlock_t *lock);//动态初始化

获取自旋锁

voidspin_lock(spinlock_t *lock);//不可中断的

释放自旋锁

voidspin_unlock(spinlock_t *lock);

使用自旋锁时要禁止中断,禁止睡眠,并且应当尽可能减少占用自旋锁的时间。

其他函数

voidspin_lock(spinlock_t *lock);


//在获取自旋锁之前,禁止中断

voidspin_lock_irqsave(spinlock_t *lock, unsigned long flags);


voidspin_lock_irq(spinlock_t *lock);


//禁止软件中断,但允许硬件中断

voidspin_lock_bh(spinlock_t *lock)


对应的解锁函数如下:

voidspin_unlock(spinlock_t *lock);

voidspin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);

voidspin_unlock_irq(spinlock_t *lock);

voidspin_unlock_bh(spinlock_t *lock);


非阻塞自旋锁操作(成功返回非0,允许中断)

intspin_trylock(spinlock_t *lock);

intspin_trylock_bh(spinlock_t *lock);


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

如果被保护的共享资源只在进程上下文和tasklettimer上下文访问,那么应该使用与上面情况相同的获得和释放锁的宏,因为tasklettimer是用软中断实现的。

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

如果被保护的共享资源只在两个或多个tasklettimer上下文访问,那么对共享资源的访问仅需要用spin_lockspin_unlock来保护,不必使用_bh版本,因为当tasklettimer运行时,不可能有其他tasklettimer在当前CPU上运行。如果被保护的共享资源只在一个软中断(tasklettimer除外)上下文访问,那么这个共享资源需要用spin_lockspin_unlock来保护,因为同样的软中断可以同时在不同的CPU上运行。

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

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

在使用spin_lock_irqspin_unlock_irq的情况下,完全可以用spin_lock_irqsavespin_unlock_irqrestore取代,那具体应该使用哪一个也需要依情况而定,如果可以确信在对共享资源访问前中断是使能的,那么使用spin_lock_irq更好一些,因为它比spin_lock_irqsave要快一些,但是如果你不能确定是否中断使能,那么使用spin_lock_irqsavespin_unlock_irqrestore更好,因为它将恢复访问共享资源前的中断标志而不是直接使能中断。当然,有些情况下需要在访问共享资源时必须中断失效,而访问完后必须中断使能,这样的情形使用spin_lock_irqspin_unlock_irq最好。

2./写自旋锁rwlock_t

头文件:<linux/spinlock.h>

说明:读写自旋锁是一种比自旋锁粒度更小的锁机制,它保留了自旋的概念,但是在写操作方面,只能最多有一个写进程,在读操作方面,同时可以有多个读执行单元。当然,读写操作不能同时进行。

初始化:

rwlock_tmy_rwlock = RW_LOCK_UNLOCKED; /* Static way */

rwlock_tmy_rwlock;

rwlock_init(&my_rwlock); /* Dynamic way */

读:

voidread_lock(rwlock_t *lock);

voidread_lock_irqsave(rwlock_t *lock, unsigned long flags);

voidread_lock_irq(rwlock_t *lock);

voidread_lock_bh(rwlock_t *lock);

voidread_unlock(rwlock_t *lock);

voidread_unlock_irqrestore(rwlock_t *lock, unsigned long flags);

voidread_unlock_irq(rwlock_t *lock);

voidread_unlock_bh(rwlock_t *lock);

voidwrite_lock(rwlock_t *lock);

voidwrite_lock_irqsave(rwlock_t *lock, unsigned long flags);

voidwrite_lock_irq(rwlock_t *lock);

voidwrite_lock_bh(rwlock_t *lock);

intwrite_trylock(rwlock_t *lock);

voidwrite_unlock(rwlock_t *lock);

voidwrite_unlock_irqrestore(rwlock_t *lock, unsigned long flags);

voidwrite_unlock_irq(rwlock_t *lock);

voidwrite_unlock_bh(rwlock_t *lock);

3.顺序锁seqlocks

对读写锁的一种优化。使用顺序锁,读执行单元绝不会被写执行单元阻塞,也就是说,读执行单元可以在写执行单元对被顺序锁保护的共享资源进行写操作时仍然可以继续读,而不必等待写执行单元完成操作,写操作也不需要等待所有读执行单元完成读操作才去进行写操作。用于受保护的资源很小,简单且经常访问,适用于写操作很少但必须很快的场景。

不能保护有指针成员变量的数据结构。

头文件:<linux/seqlock.h>

seqlock_tlock1 = SEQLOCK_UNLOCKED;


seqlock_tlock2;


seqlock_init(&lock2);


unsignedint seq;


do{


seq= read_seqbegin(&the_lock);


/*Do what you need to do */


}while (read_seqretry(&the_lock, seq));

在中断处理函数中使用seqlock,则应当使用IRQ安全的版本:

unsignedint read_seqbegin_irqsave(seqlock_t *lock, unsigned long flags);


intread_seqretry_irqrestore(seqlock_t *lock, unsigned int seq, unsignedlong flags);


获取一个写保护:

voidwrite_seqlock(seqlock_t *lock);

释放:

voidwrite_sequnlock(seqlock_t *lock);

类似函数

voidwrite_seqlock_irqsave(seqlock_t *lock, unsigned long flags);


voidwrite_seqlock_irq(seqlock_t *lock);


voidwrite_seqlock_bh(seqlock_t *lock);


voidwrite_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);


voidwrite_sequnlock_irq(seqlock_t *lock);


voidwrite_sequnlock_bh(seqlock_t *lock);




易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!