linux驱动中的互斥途径三:自旋锁

来源:互联网 发布:云计算怎么开公司啊 编辑:程序博客网 时间:2024/05/23 18:34

1. 自旋锁

1.1 概念:

自旋锁是一种典型的对临界资源进行互斥访问的手段,其名称来源于它的工作方式。为了获得一个自旋锁,在某 CPU 上运行的代码需先执行一个原子操作,该操作测试并设置某个内存变量,如果测试结果表示锁已经空闲,则程序获得这个自旋锁并继续执行,如果测试结果表明锁仍被占用,程序将在一个小的循环内重复这个"测试并设置"操作。当然当锁的持有者通过重置该变量释放这个锁后,某个等待的操作向其使用者报告锁已释放。

理解锁最简单的方法是把它作为一个变量看待。

使用的时候需要注意一下的问题:

  1. 自旋锁是忙等锁。使用时要求短平快
  2. 可能导致系统死锁。如果一个已经拥有某个自旋锁的 CPU 想第二次获得这个自旋锁,该CPU 将锁死。
  3. 锁定期间不能调用可能引起进程调度的函数。如果进程获得自旋锁之后再阻塞(如调用copy_from_user()、copy_to_user()、kmalloc()、msleep等函数)可能导致内核崩溃。

1. 2 函数:

定义
spinlock_t lock;
初始化
spin_lock_init(lock);
这是一个宏,用于动态的初始化一个锁
获得锁
spin_lock(lock);

功能:

获得自旋锁,

获得,返回

不能获得,自旋在那里

spin_trylock(lock);

尝试获得自旋锁,

能立即获得锁,获得锁返回 真

不能获得,返回 假

释放锁
spin_unlock(lock);

自旋锁主要针对 SMP 或单 CPU 单内核支持可抢占的情况,使临界区不受别的 CPU 和本 CPU 内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候,还可能受到中断和底半部的影响。解决办法是:

spin_lock_irq() = spin_lock() + local_irq_disable() /* 关中断加锁 */

spin_unlock_irq() = spin_unlock() + local_irq_enable()

spin_lock_irqsave() = spin_lock() + local_irq_save()

spin_lock_irqrestore() = spin_unlock() + local_irq_restore()

spin_lock_bh() = spin_lock() + local_bh_disable() /* 关底半部加锁 */

spin_unlock_bh() = spin_unlock() + local_bh_enable()


1.3 例子:

使用自旋锁实现一个设备只能被一个进程打开:

int flag = 0;struct hello_device{    char data[128];    spinlock_t lock;    struct cdev cdev;} hello_device;static int hello_open (struct inode *inode, struct file *file){    spin_lock(&hello_device.lock);    if ( flag )    {        spin_unlock(&hello_device.lock);        return -EBUSY;    }    flag++;    spin_unlock(&hello_device.lock);    printk (KERN_INFO "Hey! device opened\n");    return 0;}static int hello_release (struct inode *inode, struct file *file){    spin_lock(&hello_device.lock);    flag--;    spin_unlock(&hello_device.lock);    printk (KERN_INFO "Hmmm... device closed\n");    return 0;}
【1】程序的思路是在驱动中声明一个变量初始化为0,当第一次打开时,加1(此部分用自旋锁保护一下),在open函数中检查flag,如果是1,则返回。在release函数中flag减1(此部分也要加锁保护下)

2. 读写自旋锁:

解决的问题:
对共享资源并发访问时,多个执行单元同时读取是没有问题的,只要保证写的时候只能有一个写进程就行。

2.1定义和初始化

rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* 静态初始化 */

rwlock_t my_rwlock;

rwlock_init(&my_rwlock); /* 动态初始化 */


2.2读锁定

void read_lock(rwlock_t *lock);

void read_lock_irqsave(rwlock_t *lock, unsigned long flags);

void read_lock_irq(rwlock_t *lock);

void read_lock_bh(rwlock_t *lock);


2.3读解锁

void read_unlock(rwlock_t *lock);

void read_unlock_irqresore(rwlock_t *lock, unsigned long flags);

void read_unlock_irq(rwlock_t *lock);

void read_unlock_bh(rwlock_t *lock);


2.4写锁定

void write_lock(rwlock_t *lock);

void write_lock_irqsave(rwlock_t *lock, unsigned long flags);

void write_lock_irq(rwlock_t *lock);

void write_lock_bh(rwlock_t *lock);

void write_trylock(rwlock_t *lock);


2.5写解锁

void write_unlock(rwlock_t *lock);

void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);

void write_unlock_irq(rwlock_t *lock);

void write_unlock_bh(rwlock_t *lock);

在对共享资源进行写之前,应该先调用写锁定函数,完成后应调用写解锁函数


3. 顺序锁: -- 解决的是读写同时进行的小的概率情况

循序锁是对读写锁的一种优化,读执行单元不会被写执行单元阻塞,但是写执行单元与写执行单元之间是互斥的:
  • 如果有写执行单元在进行写操作,其他写执行单元必须自旋在那里,直到写执行单元释放了顺序锁
  • 如果读执行单元在读操作期间,写执行单元已经发生了写操作,那么,读执行单元必须重新读取数据
限制
要求被保护的共享资源不含指针,因为写执行单元可能使得指针失效,但读执行单元如果正在访问该指针,将导致 oops。

3.1 写执行单元:

获取顺序锁:
void write_seqlock(seqlock *sl);
void write_tryseqlock(seqlock *sl);
write_seqlock_irqsave(lock, flags); = local_irq_save() + write_seqlock()
write_seqlock_irq(lock, flags); = local_irq_disable() + write_seqlock()
write_seqlock_bh(lock, flags); = local_bh_disable() + write_seqlock()

释放顺序锁:
void write_sequnlock(seqlock *sl);
write_sequnlock_irqrestore(lock, flags); = write_sequnlock() + local_irq_restore()
write_sequnlock_irq(lock, flags); = write_sequnlock() + local_irq_enable()
write_sequnlock_bh(lock, flags); = write_sequnlock() + local_bh_enable()

3.2 读执行单元:

读开始
unsigned read_seqbegin(const seqlock_t *sl);
read_seqbegin_irqsave(lock, flags); = local_irq_save() + read_seqbegin()
读执行单元在被顺序锁 sl 保护的共享资源进行访问需要调用该函数,该函数仅返回顺序锁 sl 的当前顺序号

重读
int read_seqretry(const seqlock_t *sl, unsigned iv);
read_seqretry_irqrestore(lock, iv, flags); = read_seqretry() + local_irq_restore()
读执行单元在访问完被顺序锁 sl 保护的共享资源后需要调用该函数来检查,在读访问期间是否有写操作。如果有写操作,读执行单元需要重新进行读操作。
使用顺序锁的格式:
do{
seqnum = read_seqbegin(&seqlock_a);
/* 读操作代码块 */
...
}while (read_seqretry(&seqlock_a, seqnum));

4. RCU:

RCU 是 Read-Copy Update 的缩写,读-拷贝-更新,他是基于其原理命名的,对于被 RCU 保护的共享资源,读执行单元不需要获得任何锁就可以访问它,不使用原子执行,而且在除alpha的所有架构上也不需要内存屏障,因此不会导致锁竞争、内存延迟以及流水线停滞。

使用 RCU 的写执行单元在访问他前需要首先拷贝一个副本,然后对副本进行修改,最后使用一个回调机制在适当的时机吧指向原来数据的指针重新指向新的被修改的数据,这个时机就是所有引用该数据 CPU 都退出对共享数据的操作的时候,读执行单元没有任何的同步开销,写执行单元的同步开销则取决于使用的写执行单元之间同步机制。


0 0
原创粉丝点击