自旋锁

自旋锁主要是为了解决对临界资源的互斥访问 也就是适合在smp或者单cpu可抢占的系统中

比如在SMP 多核处理器中的情况 现在的处理器大多是这种类型的 可以使用自旋锁来解决对临界资源互斥访问 当一个CPU在访问自旋锁保护的临界区时 临界区锁被锁上 也就是spin_lock(lock) 将自旋锁置0 这时其他访问此临界区的CPU 或者本地CPU的其他进程想要访问此临界区时会忙等待 直到第一个访问该临界区的进程释放锁后方可访问 但是这个忙等待不是睡眠 而信号量是让等待线程睡眠阻塞。所以就有了一个问题了 如果CPU一直忙等待 浪费了处理器的时间 处理器的性能会下降 虽然等待的时间在1毫秒以下 但是我觉得这还是会影响系统的性能的 所以在临界区比较大的情况还是推荐使用信号量

使用方法如下

定义自旋锁
spinlock_t *lock;

初始化自旋锁 ,将自旋锁设置为1,表示有一个资源可用。
spin_lock_init(lock);

获取自旋锁 也就是将自旋锁设置为0
spin_lock(lock);

获取自旋锁 但获取不到会立刻返回 如果原来锁的值为1(可以获取到锁),返回1,否则返回0。
spin_trylock(lock);

释放自旋锁
spin_unlock(lock);

一般的话使用spin_lock和spin_trylock 配合着spin_unlock使用就可以了 但是还是要解决另外一个问题就是即使用了自旋锁可以保证临界区不受别的CPU和本CPU内的抢占进程打扰 但是得到锁的代码路径在执行临界区的时候 还可能收到中断和底半部机制的影响 所以在拥有自旋锁的时候要禁止中断

就比如我们的驱动程序在运行 并且获得了这个锁 但是这时设备产生了一个中断 调用了中断处理函数 这个中断处理函数也要获取这个锁 这个中断例程又在最初拥有锁所在的处理器上执行 那就会造成死锁的情况 所以linux里又有以下API

循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。关中断,将状态寄存器值存入flags。
spin_lock_irqsave(lock, flags)

将自旋锁解锁(置为1)。开中断,将状态寄存器值从flags存入状态寄存器。
spin_unlock_irqrestore(lock, flags)

循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。关中断。
spin_lock_irq(lock)

将自旋锁解锁(置为1)。开中断。
spin_unlock_irq(lock)

将自旋锁解锁(置为1)。开启底半部的执行
spin_unlock_bh(lock)

循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。阻止软中断的底半部的执行。
spin_lock_bh(lock)

关于驱动中使用自旋锁需要注意的问题
1.占用锁的时间应该尽可能短 否则性能下降 毕竟一直在忙等待
2.进程上下文使用spin_lock_irqsave和spin_unlock_irqrestore 中断上下文使用spin_lock和spin_unlock
3.不要死锁 比如递归的使用自旋锁
4.自旋锁锁定期间不能调用引起进程调度的函数 比如kmalloc mseep copy_from_user

和信号量相似 自旋锁也有 读取者锁和写入者锁 性质相似 读取者锁允许任意数量的同时进入临界区 而写入者必须互斥访问
结构体是
typedef struct {
arch_rwlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
} rwlock_t;

typedef struct {
u32 lock;
} arch_rwlock_t;
在结构raw_rwlock_t中,读/写自旋锁状态变量lock为32位,它分为2个部分,0~23位是一个24位计数器,表示对临界数据区进行并发读操作的线程数,线程数以补码形式存入计数器;第24位为表示”未锁”的状态位,在没有线程读或写临界区时,设置为1,否则,设置为0。
如果自旋锁设置了”未锁”状态且无读者,那么lock值为0x01000000;如果写者已获得自旋锁且无读者,则未锁状态位清0,lock值为0x00000000。如果有一个、2个或多个线程获取锁对临界数据区进行读操作,则lock值为0x00ffffff、0x00fffffe等(第24位清0表示未锁,第0~23位为读者个数的补码)。
API如下

初始化自旋锁值为0x01000000(未锁)。
rwlock_init(lock)

加读者锁,即将读者计数加1
read_lock(lock)

加读者锁,即将读者计数加1。并且关中断,存储状态标识到flags中。
read_lock_irqsave(lock, flags)

加读者锁,即将读者计数加1。并且关中断。
read_lock_irq(lock)

解读者锁,即将读者计数减1。
read_unlock(lock)

解读者锁,即将读者计数减1。并且开中断,将状态标识从flags读到状态寄存器中
read_unlock_irqrestore(lock, flags)

解读者锁,即将读者计数减1。并且开中断。
read_unlock_irq(lock)

加写者锁,即将写者锁置0。
write_lock(lock)

加写者锁,即将写者锁置0。并且关中断,存储状态标识到flags中
write_lock_irqsave(lock, flags)

加写者锁,即将写者锁置0。并且关中断。
write_lock_irq(lock)

解写者锁,即将写者锁置1。
write_unlock(lock)

解写者锁,即将写者锁置1。并且开中断,将状态标识从flags读到状态寄存器中
write_unlock_irqrestore(lock, flags)

解写者锁,即将写者锁置1。并且开中断
write_unlock_irq(lock)

一般的使用方法是
定义读写锁
rwlock_t lock;

初始化读写锁
rwlock_init(&lock) ;

读取获取锁
read_lock(&lock);
xxx;/临界区
read_unlock(lock);

写入获取锁
write_lock_irqsave(&lock, flags);
xxx;//临界区
write_unlock_irqrestore(lock, flags)

顺序锁
顺序锁就是对读写锁的优化 也就是读执行操作不会被写执行操作阻塞 也就是读执行单元在写执行单元对顺序锁保护的共享资源进行写操作时仍然可以读 而不必等待写执行单元完成写操作 写执行单元也不需要等待所有的读执行单元完成读操作才去进行写操作 但是写操作依然是互斥的 不可并发

尽管读写不互斥 但是读操作还是需要在写操作后重新读取数据 才能读取到有效的数据

相关的API
seqlock_init(x); //动态初始化
DEFINE_SEQLOCK(x); //静态初始化

void write_seqlock(seqlock_t* sl); //写加锁
int write_tryseqlock(seqlock_t* sl); //尝试写加锁
write_seqlock_irqsave(lock, flags); //local_irq_save() + write_seqlock()
write_seqlock_irq(lock); //local_irq_disable() + write_seqlock()
write_seqlock_bh(lock); //local_bh_disable() + write_seqlock()

void write_sequnlock(seqlock_t* sl); //写解锁
write_sequnlock_irqrestore(lock, flags); //write_sequnlock() + local_irq_restore()
write_sequnlock_irq(lock); //write_sequnlock() + local_irq_enable()
write_sequnlock_bh(lock); //write_sequnlock() + local_bh_enable()

读操作:
unsigned int read_seqbegin(const seqlock_t* sl);
read_seqbegin_irqsave(lock, flags); //local_irq_save() + read_seqbegin()
读执行单元在访问共享资源时要调用顺序锁的读函数,返回顺序锁s1的顺序号;该函数没有任何获得锁和释放锁的开销,只是简单地返回顺序锁当前的序号;
B.重读操作:
int read_seqretry(const seqlock_t* sl, unsigned start);
read_seqretry_irqrestore(lock, iv, flags);
在顺序锁的一次读操作结束之后,调用顺序锁的重读函数,用于检查是否有写执行单元对共享资源进行过写操作;如果有,则需要重新读取共享资源;iv为顺序锁的顺序号;

用例:

write_seqlock(&lock);
…… //写操作代码
write_sequnlock(&lock);

unsigned int seq_num = 0;
do
{
seq_num = read_seqbegin(&seqlock);
//读操作代码
……
} while(read_seqretry(&seqlock, seq_num));

注意:要求共享资源中不能含有指针。

注意:
1.顺序锁:允许读和写操作之间的并发,也允许读与读操作之间的并发,但写与写操作之间只能是互斥的、串行的。
2.读写自旋锁:只允许读操作与读操作之间的并发,而读与写操作,写与写操作之间只能是互斥的、串行的。
3.自旋锁:不允许任何操作之间并发。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值