03、经典自旋锁的使用 *
一、什么是自旋锁
自旋锁是一种用来保护共享资源的锁机制。它的特点是:当线程要获取锁时,如果锁正被其他线程占用,该线程不会停下来等待,而是持续用CPU反复尝试获取锁。这种不停"转圈等待"的方式,就是"自旋"。
在什么情况下用自旋锁呢?比如当共享资源的锁定时间非常短(比如几微秒),这时让线程暂停再恢复的开销反而更大。如果有多个CPU核心,等待的线程可以继续占用CPU自旋,同时其他核心还能处理其他任务,这样反而更高效。
举个例子:银行ATM防护舱同一时间只能一人使用。当有人进去后,防护舱锁上,其他人只能在外面等待。但自旋锁不同,等待的人不会真的排队,而是不停看门是否开了——如果舱内的人两秒内出来开门,等待的人立刻就能进去,省去了排队的时间。
在程序中使用自旋锁时,每个任务获取锁后,其他想要获取锁的任务只能不断循环等待。虽然这种方式会一直占用CPU,但只要临界区(需要锁保护的代码段)足够短,总效率反而更高。
需要注意的是,如果自旋锁用得不对(比如锁住时间太长或多个线程互相等待),可能导致死锁——这种情况会在后面详细讲解。
内核中以 spinlock_t 结构体来表示自旋锁, 定义在“内核源码/include/linux/spinlock_types.h”文件中, 如下所示:
C
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
二、基础API介绍
自旋锁相关 API 函数定义在“内核源码/include/linux/spinlock.h”文件中
函数 | 描述 |
---|---|
DEFINE_SPINLOCK(spinlock_t lock) | 定义并初始化自旋锁。 |
int spin_lock_init(spinlock_t *lock) | 初始化自旋锁。 |
void spin_lock(spinlock_t *lock) | 获取指定的自旋锁, 也叫做加锁。 |
void spin_unlock(spinlock_t *lock) | 释放指定的自旋锁。 |
int spin_trylock(spinlock_t *lock) | 尝试获取指定的自旋锁, 如果没有获取到就返回 0 |
int spin_is_locked(spinlock_t *lock) | 检查指定的自旋锁是否被获取, 如果没有被获取就返回非 0, 否则返回 0。 |
三、加锁的其他API
- spin_lock ---- 不安全的API(会发生死锁,不建议使用)
- spin_lock_irq ---- 全局中断的关闭(会发生死锁)(禁止硬中断)
spin_lock_irqsave
---- 记录中断的关闭状态(为线程安全的方案)推荐
四、内核使用案例
自旋锁的使用步骤:
- 在访问临界资源的时候先申请自旋锁
- 获取到自旋锁之后就进入临界区, 获取不到自旋锁就“原地等待”。
- 退出临界区的时候要释放自旋锁。
五、实现
忙等待 (while(1) ) ---- 不会让出CPU的资源
C
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
unsigned long tmp;
u32 newval;
arch_spinlock_t lockval;
prefetchw(&lock->slock);
__asm__ __volatile__(
"1: ldrex %0, [%3]\n"
" add %1, %0, %4\n"
" strex %2, %1, [%3]\n"
" teq %2, #0\n"
" bne 1b"
: "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
: "cc");
while (lockval.tickets.next != lockval.tickets.owner) {
wfe();
lockval.tickets.owner = READ_ONCE(lock->tickets.owner);
}
smp_mb();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
六、实验
驱动代码:
C
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/spinlock.h>
static spinlock_t spinlock_test;//定义spinlock_t类型的自旋锁变量spinlock_test
static int flag = 1;//定义flag标准为,flag等于1表示设备没有被打开,等于0则证明设备已经被打开了
static int open_test(struct inode *inode,struct file *file)
{
//printk("\nthis is open_test \n");
spin_lock(&spinlock_test);//自旋锁加锁
if(flag != 1){//判断标志位flag的值是否等于1
spin_unlock(&spinlock_test);//自旋锁解锁
return -EBUSY;
}
flag = 0;//将标志位的值设置为0
spin_unlock(&spinlock_test);//自旋锁解锁
return 0;
}
static ssize_t read_test(struct file *file,char __user *ubuf,size_t len,loff_t *off)
{
int ret;
char kbuf[10] = "linux";//定义char类型字符串变量kbuf
printk("\nthis is read_test \n");
ret = copy_to_user(ubuf,kbuf,strlen(kbuf));//使用copy_to_user接收用户空间传递的数据
if (ret != 0){
printk("copy_to_user is error \n");
}
printk("copy_to_user is ok \n");
return 0;
}
static char kbuf[10] = {0};//定义char类型字符串全局变量kbuf
static ssize_t write_test(struct file *file,const char __user *ubuf,size_t len,loff_t *off)
{
int ret;
ret = copy_from_user(kbuf,ubuf,len);//使用copy_from_user接收用户空间传递的数据
if (ret != 0){
printk("copy_from_user is error\n");
}
if(strcmp(kbuf,"linux") == 0 ){//如果传递的kbuf是linux就睡眠四秒钟
ssleep(4);
}
else if(strcmp(kbuf,"kernel") == 0){//如果传递的kbuf是kernel就睡眠两秒钟
ssleep(2);
}
printk("copy_from_user buf is %s \n",kbuf);
return 0;
}
static int release_test(struct inode *inode,struct file *file)
{
printk("\nthis is release_test \n");
spin_lock(&spinlock_test);//自旋锁加锁
flag = 1;
spin_unlock(&spinlock_test);//自旋锁解锁
return 0;
}
struct chrdev_test {
dev_t dev_num;//定义dev_t类型变量dev_num来表示设备号
int major,minor;//定义int类型的主设备号major和次设备号minor
struct cdev cdev_test;//定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备
struct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类
};
struct chrdev_test dev1;//创建chrdev_test类型的
struct file_operations fops_test = {
.owner = THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
.open = open_test,//将open字段指向open_test(...)函数
.read = read_test,//将read字段指向read_test(...)函数
.write = write_test,//将write字段指向write_test(...)函数
.release = release_test,//将release字段指向release_test(...)函数
};
static int __init atomic_init(void)
{
spin_lock_init(&spinlock_test);
if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0 ){//自动获取设备号,设备名chrdev_name
printk("alloc_chrdev_region is error \n");
}
printk("alloc_chrdev_region is ok \n");
dev1.major = MAJOR(dev1.dev_num);//使用MAJOR()函数获取主设备号
dev1.minor = MINOR(dev1.dev_num);//使用MINOR()函数获取次设备号
printk("major is %d,minor is %d\n",dev1.major,dev1.minor);
cdev_init(&dev1.cdev_test,&fops_test);//使用cdev_init()函数初始化cdev_test结构体,并链接到fops_test结构体
dev1.cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用cdev_add()函数进行字符设备的添加
dev1.class_test = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_test
device_create(dev1.class_test,0,dev1.dev_num,0,"device_test");//使用device_create进行设备的创建,设备名称为device_test
return 0;
}
static void __exit atomic_exit(void)
{
device_destroy(dev1.class_test,dev1.dev_num);//删除创建的设备
class_destroy(dev1.class_test);//删除创建的类
cdev_del(&dev1.cdev_test);//删除添加的字符设备cdev_test
unregister_chrdev_region(dev1.dev_num,1);//释放字符设备所申请的设备号
printk("module exit \n");
}
module_init(atomic_init);
module_exit(atomic_exit)
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Linux");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
应用测试代码:
C
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;//定义int类型的文件描述符
char str1[10] = {0};//定义读取缓冲区str1
fd = open(argv[1],O_RDWR);//调用open函数,打开输入的第一个参数文件,权限为可读可写
if(fd < 0 ){
printf("file open failed \n");
return -1;
}
/*如果第二个参数为Linux,条件成立,调用write函数,写入Linux*/
if (strcmp(argv[2],"Linux") == 0 ){
write(fd,"Linux",10);
}
/*如果第二个参数为kernel,条件成立,调用write函数,写入kernel*/
else if (strcmp(argv[2],"kernel") == 0 ){
write(fd,"kernel",10);
}
close(fd);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26