什么是自旋锁(Spin Lock)
原子操作虽然也是一种很好的避免出现竞态的方式,而且使用也非常简单。但是却无法处理当有多种共享数据读写操作需要被避免竞态的情况。由此引申出了另外一种避免竞态的手段——自旋锁(Spin lock)。
自旋锁(Spin Lock) 是一种典型的对共享数据进行互斥避免竞态的手段,它的名称正是来源与它的工作方式。自旋锁从本质上来说就是保证代码段(即临界区)的操作是原子的,即保证被锁保护的代码段不能被任务线程或进程打断。当多个任务线程或进程同时并行执行,并不分先后的对某个代码段的共享数据进行访问时,就需要我们使用自旋锁来避免竞态问题的发生。
自旋锁的使用方法
- 定义并初始化自旋锁方法(1)
spinlock_t lock; /* 定义一个自旋锁变量lock */
spin_lock_init(&lock); /* 初始化定义的自旋锁变量lock */
- 定义并初始化自旋锁方法(2)
DEFINE_SPINLOCK(lock); /* 定义并初始化一个自旋锁变量lock */
- 获取自旋锁
spin_lock(&lock); /* 获取自旋锁lock */
如果使用spin_lock() 成功获取自旋锁,spin_lock() 函数会立即放回。如果没有获取到自旋锁,spin_lock() 函数就会被阻塞(即自旋等待资源释放),直到可以获取到自旋锁才返回。
如果想无论是否获取到锁都立刻返回,可以使用spin_trylock(&lock) 函数来实现这个功能,其代码如下:
if(spin_trylock(&lock)) {
printk("spin lock available\n");
} else {
printk("spin lock unavailable\n");
}
如果spin_trylock() 函数成功获取自旋锁,就立刻返回一个非0值,如果没有获取到自旋锁,spin_trylock() 函数会立刻返回0。
- 释放自旋锁
在使用完自旋锁后,应立即使用spin_unlock() 函数来释放自旋锁,否则其他的任务线程或进程将无法获取这个自旋锁导致发生死锁的问题。释放自旋锁的代码如下:
spin_unlock(&lock); /* 释放获取的自旋锁lock */
获取自旋锁时,无论是使用spin_lock() 函数还是spni_trylock() 函数,都可以使用spin_unlock() 函数来释放自旋锁。
自旋锁的一些衍生操作
自旋锁主要是针对SMP(Symmetrical Multi-Processing,对称多处理) 或单CPU 单内核可抢占的情况,对于单CPU但是不支持抢占的系统,自旋锁将是一个空操作。
虽然使用自旋锁可以保证共享数据(临界区) 被使用时不会被别的CPU和本CPU内的抢占进程访问,但是在执行共享数据的代码段时仍然会收到中断和底半部(BH)操作的影响。为了应对这种情况的放生,由此产生了一些自旋锁的衍生操作。
spin_lock()/spin_unlock() 是自旋锁机制的基础,它们和关中断(local_irq_disable)/开中断(local_irq_enable)、关底半部(local_bh_disable)/开底半部(local_bh_enable) 结合组成了整套自旋锁机制。以下就是自旋锁一些衍生操作相关的函数:
自旋锁的衍生操作函数 | 类型 | 描述 |
---|---|---|
void spin_lock_irq(spinlock_t *lock) | 函数 | 获取自旋锁并禁止中断 |
void spin_trylock_irq(spinlock_t *lock) | 函数 | 获取自旋锁并禁止中断。如果成功获取自旋锁,立即返回非0值,否则返回0。 |
void spin_unlock_irq(spinlock_t *lock) | 函数 | 释放自旋锁并允许中断。 |
void spin_lock_bh(spinlock_t *lock) | 函数 | 获取自旋锁并关闭底半部。 |
void spin_trylock_bh(spinlock_t *lock) | 函数 | 获取自旋锁并关闭底半部。如果成功获取自旋锁,立即返回非0值,否则返回0。 |
void spin_unlock_bh(spinlock_t *lock) | 函数 | 释放自旋锁并打开底半部。 |
使用自旋锁应该注意的一些问题
- 自旋锁实际上是一种忙等待锁,当锁被占用后,再尝试获取自旋锁的时候,CPU会一直循环执行尝试获取锁的动作,直到自旋锁被释放,并成功获取到锁位置。在此之前,CPU会一直等待自旋锁被释放而不做任何其他的工作。所以,只有在占用自旋锁的时间非常短的时候,才考虑使用自旋锁。当共享数据很多,要执行很多代码段的情况下会长时间占用锁,此时使用自旋锁无疑会极大的降低整个系统的性能。
- 不合理的使用自旋锁会很有可能会导致系统死锁的问题发生。引发这个问题最常见的情况就是当一个已经获取了自旋锁的CPU再没有释放当前占用的锁时想第二次获取当前锁,就会将该CPU陷入死锁状态。当然,所有任务线程或进程在获取自旋锁后被阻塞都可能导致死锁的问题发生,因此在自旋锁被占用期间,务必不要调用copy_from_user()、copy_to_usr和kmalloc() 等容易引起阻塞的函数。
实例:使用自旋锁保护共享数据代码实现
#include <linuc/module.h>
#include <linuc/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
/* 定义设备文件名 */
#define DEVICE_NAME "spin_lock"
static char *data = "read\n"
static char flag = 1;
static DEFINE_SPINLOCK(lock); /* 定义并初始化自旋锁变量lock */
/* 设备文件的read函数 */
static ssize_t demo_read(struct file *file, char _user *buf, size_t count, loff_t *ppos)
{
int size = strlen(data);
/* 向用户空间复制数据 */
if(copy_to_user(buf, (void*) data, size)) {
return -EINVAL;
}
if(flag) {
flag = 0;
/* 申请自旋锁 */
if(spin_trylock(&lock)) {
/****************************************************
* 延时10s(模拟共享数据时的代码自行情况),在这里延迟了较长
* 的一段时间,只是为了验证,实际应用中不应该执行这么长时间。
****************************************************/
mdelay(10000);
/* 释放自旋锁 */
spin_unlock(&lock);
} else {
return -EBUSY;
}
} else {
flag = 1;
return 0;
}
}
static ssize_t demo_write(struct file *file, const char _user *buf, size_t count, loff_t *ppos)
{
char data[10];
/* 给data数组清零 */
memset(data, 0, 10);
/* 从用户空间获取数据 */
if(copy_from_user(data, buf, count)) {
return -EINVAL;
}
/* 如果写入的数据是lock,使用spin_lock函数获取自旋锁 */
if(strcmp("lock\n", data) == 0) {
spin_lock(&lock);
... /* 表示共享数据代码段 */
spni_unlock(&lock);
}
/* 如果写入的数据是trylock,使用spin_trylock()函数获取自旋锁 */
else if(strcmp("trylock\n", data) == 0) {
if(spin_trylock(&lock)) {
... /* 表示共享数据代码段 */
printk("spin lock available.\n");
spin_unlock(&lock);
} else {
printk("spin lock unavailable.\n");
return -EBUSY;
}
}
return count;
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.read = demo_read,
.write = demo_write
}
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops
};
/* 初始化Linux驱动 */
static int _init demo_init(void)
{
/* 创建设备文件 */
int ret = misc_register(&misc);
printk("demo_init success\n");
return ret;
}
/* 卸载Linux驱动 */
static void _exit demo_exit(void)
{
printk("demo_exit success\n");
misc_deregister(&misc);
}
/* 注册Linux驱动入口函数 */
module_init(demo_init);
/* 注册Linux驱动出口函数 */
module_exit(demo_exit);
MODULE_LICENSE("GPL");