时间轴

2025-11-12

init


并发与竞争概念

下面把并发与并行统称并发。

在并发执行的环境中,多个程序可能同时访问同一个共享资源。当多个任务试图同时操作这类资源时,可能会出现执行异常或数据错误,这类问题被称为竞争。

竞争产生的常见原因包括:

  1. 多线程访问。由于 Linux 是一个多任务操作系统,多个线程可能同时访问同一共享资源,这是竞争产生的基本原因。
  2. 中断访问。当进程正在访问某一共享资源,而中断打断了正在执行的进程,而发出中断的进程与被打断的进程之间也可能发生竞争。
  3. 抢占访问。linux2.6 及更高版本引入了抢占式内核,高优先级的任务可以打断低优先级的任务。如果正在访问共享资源的任务被打断,另一个任务接着访问相同资源,就可能引发竞争。
  4. 多核并发访问(SMP)。在多核处理器系统中,不同 CPU 核心之间可能并发访问同一共享资源,形成核间竞争。

Linux 内核提供了多种机制来应对这一问题,常用的方法包括原子操作自旋锁互斥锁信号量等。

原子操作

原子操作会将对整形变量的一次读写操作视为一个整体,确保其不可分割,从而避免竞争。

原子操作可以进一步细分为“整型原子操作”和“位原子操作”,这里首先对整型原子操作进行讲解。

整形原子操作

在 Linux 内核中使用 atomic_tatomic64_t 结构体分别来定义 32 位系统和 64 位系统下的原子变量

include/linux/types.h

1
2
3
4
5
6
7
8
9
10
11
typedef struct {
int counter;
} atomic_t;

#define ATOMIC_INIT(i) { (i) }

#ifdef CONFIG_64BIT
typedef struct {
s64 counter;
} atomic64_t;
#endif

32 位的 atomic_t

include/linux/atomic.h

函数原型 功能解释 参数 返回值
#define ATOMIC_INIT(i) 初始化 atomic_t 变量为 i i:初始整数值
int atomic_read(const atomic_t *v) 读取原子变量的值 v:atomic_t 指针 返回 v 的当前值
void atomic_set(atomic_t *v, int i) 设置原子变量为 i v:变量指针;i:要写入的值
void atomic_add(int i, atomic_t *v) 原子地将 v += i i:增加值;v:变量指针
void atomic_sub(int i, atomic_t *v) 原子地将 v -= i i:减少值;v:变量指针
void atomic_inc(atomic_t *v) 原子地将 v++ v:变量指针
void atomic_dec(atomic_t *v) 原子地将 v-- v:变量指针
int atomic_inc_return(atomic_t *v) 原子 v++ 并返回新值(先加后返回) v:变量指针 自增后的值
int atomic_dec_return(atomic_t *v) 原子 v-- 并返回新值(先减后返回) v:变量指针 自减后的值
int atomic_sub_and_test(int i, atomic_t *v) 原子 v -= i,若结果为 0 返回 true i:减的值;v:变量指针 为 0 → 返回 1,不为 0 → 返回 0
int atomic_dec_and_test(atomic_t *v) 原子 v--,若结果为 0 返回 true v:变量指针 为 0 → 返回 1,不为 0 → 返回 0
int atomic_inc_and_test(atomic_t *v) 原子 v++,若结果为 0 返回 true v:变量指针 为 0 → 返回 1,否则 0
int atomic_add_negative(int i, atomic_t *v) 原子 v += i,若结果 < 0 返回 true i:增加值;v:变量指针 结果 < 0 → 返回 1,否则 0

64 位的 atomic64_t

函数原型 功能解释 参数 返回值
#define ATOMIC64_INIT(i) 初始化 atomic64_t 为 i i:初始 64 位整数
long long atomic64_read(const atomic64_t *v) 读取 64 位原子变量的值 v:变量指针 返回当前 64 位值
void atomic64_set(atomic64_t *v, long long i) 设置原子变量为 i v:变量指针;i:写入的 64 位值
void atomic64_add(long long i, atomic64_t *v) 原子执行 v += i i:增加值;v:变量指针
void atomic64_sub(long long i, atomic64_t *v) 原子执行 v -= i i:减少值;v:变量指针
void atomic64_inc(atomic64_t *v) 原子执行 v++ v:变量指针
void atomic64_dec(atomic64_t *v) 原子执行 v-- v:变量指针
long long atomic64_add_return(long long i, atomic64_t *v) 原子执行 v += i 并返回新值(先加后返回) iv 返回加后的值
long long atomic64_sub_return(long long i, atomic64_t *v) 原子执行 v -= i 并返回新值(先减后返回) iv 返回减后的值
long long atomic64_inc_return(atomic64_t *v) 原子 v++ 并返回新值 v 返回自增后的值
long long atomic64_dec_return(atomic64_t *v) 原子 v-- 并返回新值 v 返回自减后的值
int atomic64_add_negative(long long i, atomic64_t *v) 原子 v += i,若结果 < 0 返回 true iv 结果 < 0 → 1,否则 0
int atomic64_add_and_test(long long i, atomic64_t *v) 原子 v += i,若结果 == 0 返回 true iv 结果 == 0 → 1,否则 0
int atomic64_sub_and_test(long long i, atomic64_t *v) 原子 v -= i,若结果 == 0 返回 true iv 结果 == 0 → 1,否则 0
int atomic64_inc_and_test(atomic64_t *v) 原子 v++,若结果 == 0 返回 true v 为 0 → 1,否则 0
int atomic64_dec_and_test(atomic64_t *v) 原子 v--,若结果 == 0 返回 true v 为 0 → 1,否则 0
long long atomic64_xchg(atomic64_t *v, long long i) 原子交换:将 v 设置成 i 并返回旧值 vi 返回交换前的旧值
long long atomic64_cmpxchg(atomic64_t *v, long long old, long long new) 原子比较交换:若 *v == old,则写入 new voldnew 返回交换前的旧值(可用于判断是否成功)

错误用法

下面的使用方法是错误的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static atomic_t atomic_key = ATOMIC_INIT(1);

int atomic_t_test_open(struct inode *inode, struct file *file)
{
file->private_data = test_drv_data;
if(atomic_read(&atomic_key) == 0){
return -EBUSY;
}
atomic_dec(&atomic_key);

return 0;
}

int atomic_t_test_release(struct inode *inode, struct file *file)
{
atomic_inc(&atomic_key);
return 0;
}

原因是atomic_read(&atomic_key)atomic_dec(&atomic_key)这两个操作是分开的,两个进程可以同时atomic_read(&atomic_key)然后atomic_dec(&atomic_key)成功。可以把这两个操作合到一起

1
2
if (!atomic_dec_and_test(&atomic_key))
return -EBUSY;

例子:

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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/atomic.h>

struct test_drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
};

static struct test_drv_data *test_drv_data;

static atomic_t atomic_key = ATOMIC_INIT(1);

int atomic_t_test_open(struct inode *inode, struct file *file)
{
file->private_data = test_drv_data;
if (atomic_dec_and_test(&atomic_key) == 0){
pr_err("this device is opened by another process\n");
return -EBUSY;
}
pr_info("atomic_t_test_open is called\n");
return 0;
}

int atomic_t_test_release(struct inode *inode, struct file *file)
{
atomic_inc(&atomic_key);
return 0;
}

ssize_t atomic_t_test_read (struct file *file, char __user *buf, size_t size, loff_t *offset){
pr_info("atomic_t_test_read is called\n");
return 0;
}
ssize_t atomic_t_test_write (struct file *file, const char __user *buf, size_t size, loff_t *offset){
pr_info("atomic_t_test_write is called\n");
return 0;
}

struct file_operations fops = {
.owner = THIS_MODULE,
.open = atomic_t_test_open,
.release = atomic_t_test_release,
};

static int __init atomic_t_test_init(void)
{
int err;
test_drv_data = (struct test_drv_data *)kzalloc(sizeof(struct test_drv_data), GFP_KERNEL);
if (test_drv_data == NULL) {
err = -ENOMEM;
goto kzalloc_fail;
}
err = alloc_chrdev_region(&test_drv_data->dev_num, 0, 1, "atomic_t test chrdev region");
if (err < 0)
goto alloc_chrdev_region_fail;
cdev_init(&test_drv_data->cdev, &fops);
err = cdev_add(&test_drv_data->cdev, test_drv_data->dev_num, 1);
if (err < 0)
goto cdev_add_fail;
test_drv_data->class = class_create(THIS_MODULE, "atomic_t_test");
if (IS_ERR(test_drv_data->class)) {
err = PTR_ERR(test_drv_data->class);
goto class_create_fail;
}
test_drv_data->dev = device_create(test_drv_data->class, NULL, test_drv_data->dev_num, NULL,
"atomic_t_test%d", 0);
if (IS_ERR(test_drv_data->dev)) {
err = PTR_ERR(test_drv_data->dev);
goto device_create_fail;
}

return 0;
device_create_fail:
class_destroy(test_drv_data->class);
class_create_fail:
cdev_del(&test_drv_data->cdev);
cdev_add_fail:
unregister_chrdev_region(test_drv_data->dev_num, 1);
alloc_chrdev_region_fail:
kfree(test_drv_data);
kzalloc_fail:
return err;
}

static void __exit atomic_t_test_exit(void)
{
device_destroy(test_drv_data->class, test_drv_data->dev_num);
class_destroy(test_drv_data->class);
cdev_del(&test_drv_data->cdev);
unregister_chrdev_region(test_drv_data->dev_num, 1);
kfree(test_drv_data);
}

module_init(atomic_t_test_init);
module_exit(atomic_t_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@163.com>");
MODULE_DESCRIPTION("This is a test sample for atomic_t");

测试代码:

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
pid_t pid;
int fd;

pid = fork();

if (pid < 0) {
goto fail;
} else if (pid == 0) { // child process
fd = open("/dev/atomic_t_test0", O_RDWR);
if (fd < 0)
goto fail;

printf("child open device success\n");
// do something
sleep(3);
close(fd);

} else { // parent process
fd = open("/dev/atomic_t_test0", O_RDWR);
if(fd < 0){
goto fail;
}
printf("parent open device success\n");
// do something
sleep(3);
close(fd);
}

return 0;
fail:
fprintf(stderr, "Error:%s[errno:%d]\n", strerror(errno), errno);
exit(EXIT_FAILURE);
}

测试:

1
2
3
4
5
6
7
$ insmod atomic_t.ko
[ 13.631548] atomic_t: loading out-of-tree module taints kernel.
$ ./test_atomic_t.o
[ 20.232024] atomic_t_test_open is called
[ 20.233129] this device is opened by another process
parent open device success
Error:Device or resource busy[errno:16]

atomic_cmpxchg

这个函数比较难理解,作用是:

  • 如果原子变量的当前值等于 old
    • 把它改成 new
    • 返回“修改前的值”(因为v修改前的值等于old,所以返回的值等于old)
  • 如果当前值 ≠ old
    • 不会修改原子变量
    • 直接返回“当前值”
1
2
3
#include <linux/atomic.h>

long long atomic64_cmpxchg(atomic64_t *v, long long old, long long new);

参数

参数 说明
v 指向要操作的 atomic64_t 变量的指针
old 期望的旧值(expected value)
new 如果原子变量的值等于 old,就用 new 替换它

返回值

  • 返回 原子变量操作前的原始值
  • 如果原始值等于 old,把原始值替换位 new,返回值 = old。
  • 如果原始值不等于 old,替换失败,返回值 ≠ old。

例子

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/atomic.h>

struct device_test {
// 设备号
dev_t dev_id;
// 所属类
struct class *class;
// 类下的device 设备节点
struct device *device;
// cdev,字符类设备
struct cdev cdev_test;
};

static struct device_test device1;

static atomic64_t cnt = ATOMIC_INIT(1);

static int major_num;
static int minor_num;

module_param(major_num, int, S_IRUGO);
module_param(minor_num, int, S_IRUGO);

int cdev_test_open(struct inode *inode, struct file *file)
{
if (atomic64_cmpxchg(&cnt, 1, 0) != 1) {
return -EBUSY;
}
file->private_data = &device1;
pr_info("cdev_test_open was called");
return 0;
}

ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size,
loff_t *off)
{
//struct device_test *dev1 = (struct device_test *)file->private_data;

pr_info("cdev_test_read was called");
return 0;
}

ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size,
loff_t *off)
{
//struct device_test *dev1 = (struct device_test *)file->private_data;

return 0;
}
int cdev_test_release(struct inode *inode, struct file *file)
{
atomic64_set(&cnt, 1);
pr_info("cdev_test_release was called");
return 0;
}

static struct file_operations cdev_test_ops = { .owner = THIS_MODULE,
.open = cdev_test_open,
.read = cdev_test_read,
.write = cdev_test_write,
.release = cdev_test_release };

static int __init my_driver_init(void)
{
int ret;

// 申请设备号
if (major_num) { //驱动传参,静态申请设备号
device1.dev_id = MKDEV(major_num, minor_num);

pr_info("major from module_param: %d", MAJOR(device1.dev_id));
pr_info("minor from module_param: %d", MINOR(device1.dev_id));

ret = register_chrdev_region(device1.dev_id, 1,
"my_driver device");

if (ret < 0) {
pr_err("register_chrdev_region error\n");
goto get_chrdev_region_err;
}
pr_info("register_chrdev_region ok\n");

} else { //动态申请设备号
ret = alloc_chrdev_region(&device1.dev_id, 0, 1,
"my_driver device");
if (ret < 0) {
pr_err("alloc_chrdev_region error\n");
goto get_chrdev_region_err;
}
pr_info("alloc_chrdev_region ok\n");

pr_info("major allocated: %d", MAJOR(device1.dev_id));
pr_info("minor allocated: %d", MINOR(device1.dev_id));
}
// cdev初始化,注册字符类设备
cdev_init(&device1.cdev_test, &cdev_test_ops);

//将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
device1.cdev_test.owner = THIS_MODULE;
// 添加一个字符设备
ret = cdev_add(&device1.cdev_test, device1.dev_id, 1);

if (ret < 0) {
pr_err("cdev_add error\n");
goto cdev_add_err;
}
pr_info("cdev_add ok\n");

// 创建一个class
device1.class = class_create(THIS_MODULE, "test");
if (IS_ERR(device1.class)) {
ret = PTR_ERR(device1.class);
pr_err("class create error\n");
goto class_create_err;
}
// 在class下创建一个设备结点, /dev/my_driver
device1.device = device_create(device1.class, NULL, device1.dev_id,
NULL, "my_driver");
if (IS_ERR(device1.device)) {
ret = PTR_ERR(device1.device);
pr_err("device create error\n");
goto device_create_err;
}
pr_info("my_driver: Module loaded\n");

return 0;

device_create_err:
class_destroy(device1.class);
class_create_err:
cdev_del(&device1.cdev_test);
cdev_add_err:
unregister_chrdev_region(device1.dev_id, 1);
get_chrdev_region_err:
return ret;
}

static void __exit my_driver_exit(void)
{
// 删除设备结点
device_destroy(device1.class, device1.dev_id);
// 删除该设备结点的class
class_destroy(device1.class);
// 删除cdev
cdev_del(&device1.cdev_test);
// 释放设备号
unregister_chrdev_region(device1.dev_id, 1);

pr_info("unregister_chrdev_region ok\n");
pr_info("my_driver: Module unloaded\n");
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zhao Hang");
MODULE_DESCRIPTION("my_driver Kernel Module");

位原子操作

函数原型 功能解释 参数说明 返回值
void set_bit(int nr, void *p) p 地址的第 nr置为 1 nr:位编号(0 表最低位)p:起始地址
void clear_bit(int nr, void *p) p 地址的第 nr清零 同上
void change_bit(int nr, void *p) nr取反(0→1 或 1→0) 同上
int test_bit(int nr, void *p) 读取 nr 位的值 同上 返回该位的值:0 或 1
int test_and_set_bit(int nr, void *p) nr 位设置为 1,并返回原值 同上 返回操作前该位的值(0 或 1)
int test_and_clear_bit(int nr, void *p) nr 位清零,并返回原值 同上 返回操作前该位的值
int test_and_change_bit(int nr, void *p) nr 位翻转(1↔0),并返回原值 同上 返回操作前该位的值

位编号是从 p 指向内存的起始地址算起:

  • nr = 0 → 第一字节的最低位(bit0)
  • nr = 7 → 第一字节的最高位
  • nr = 8 → 第二字节的 bit0
  • 依此类推

位编号为绝对 bit index,而不是字节内偏移。

自旋锁

自旋锁是为了保护共享资源提出的一种锁机制。当一个线程尝试获取自旋锁而发现该锁已被其他线程持有时,它不会进入睡眠状态等待,而是会持续循环地尝试获取锁,直到成功为止。这个过程称为“自旋”,因此得名“自旋锁”。

定义

include/linux/spinlock_types.h

1
2
3
4
5
6
7
8
9
10
11
12
13
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;

offsetof 定义在include/linux/stddef.h。作用是计算结构体到某一个字段的偏移。

1
2
3
4
5
#ifdef __compiler_offsetof
#define offsetof(TYPE, MEMBER) __compiler_offsetof(TYPE, MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#endif

上述 spinlock 字段通过 offsetof 计算设置填充字段,保证两个 union 成员的 dep_map 地址完全一致,这样保证不管是否启用 CONFIG_DEBUG_LOCK_ALLOCspinlock_t 的大小和对齐方式 一致

还有另外一个作用:

spinlock_t通用锁类型

  • 不想暴露 raw_spinlock 的内部结构
  • lockdep 需要 dep_map

这样设计让内核代码可以这样写:

1
lockdep_init_map(&lock->dep_map, ...);

而非:

1
lock->rlock.dep_map   // ❌ 不希望这样

自旋锁相关 API 函数定义在内核源码include/linux/spinlock.h 文件中(spinlock.h 头文件包含 spinlock_types.h 等,所以只需加入 spinlock.h 头文件即可)

API

API / 宏 返回值类型 功能描述 是否阻塞 中断状态 返回值 / 备注
DEFINE_SPINLOCK(name) void 静态定义并初始化自旋锁 - -
spin_lock_init(spinlock_t *lock) void 动态初始化自旋锁(只初始化,并不会分配内存) - -
spin_lock(spinlock_t *lock) void 获取自旋锁 阻塞(自旋) 不改变
spin_lock_bh(spinlock_t *lock) void 获取自旋锁并禁止 softirq 阻塞 禁用 softirq
spin_lock_irq(spinlock_t *lock) void 获取自旋锁并禁止本地 CPU 中断 阻塞 禁止本地中断
spin_lock_irqsave(spinlock_t *lock, unsigned long flags) void 获取自旋锁并保存本地中断状态 阻塞 禁止中断,保存 flags
spin_trylock(spinlock_t *lock) int 尝试获取自旋锁 非阻塞 不改变 成功返回 1,失败返回 0
spin_trylock_bh(spinlock_t *lock) int 尝试获取自旋锁并禁止 softirq 非阻塞 禁用 softirq 成功返回 1,失败返回 0
spin_trylock_irq(spinlock_t *lock) int 尝试获取自旋锁并禁止中断 非阻塞 禁用本地中断 成功返回 1,失败返回 0
spin_trylock_irqsave(spinlock_t *lock, unsigned long flags) int 尝试获取自旋锁并保存中断状态 非阻塞 禁用中断,保存 flags 成功返回 1,失败返回 0
spin_unlock(spinlock_t *lock) void 释放自旋锁 - -
spin_unlock_bh(spinlock_t *lock) void 释放自旋锁并恢复 softirq 状态 - 恢复 softirq
spin_unlock_irq(spinlock_t *lock) void 释放自旋锁并恢复中断 - 恢复中断
spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) void 释放自旋锁并恢复中断状态 - 恢复中断状态 flags
spin_is_locked(spinlock_t *lock) int 检查锁是否被持有 非阻塞 - 被锁返回 1,否则 0
spin_is_contended(spinlock_t *lock) int 检查锁是否存在竞争 非阻塞 - 有竞争返回 1,否则 0

使用自旋锁分为以下 3 步:

  1. 在访问临界资源的时候先申请自旋锁
  2. 获取到自旋锁之后就进入临界区,获取不到自旋锁就原地等待。
  3. 退出临界区的时候要释放自旋锁。

例子

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
108
109
110
111
112
113
114
115
116
117
118
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/sched.h>

struct test_drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
};

static struct test_drv_data *test_drv_dat;
static bool status = 1;
static spinlock_t lock;

int spinlock_test_open(struct inode *inode, struct file *file)
{
file->private_data = test_drv_dat;
spin_lock(&lock);
if(status == 0){
spin_unlock(&lock);
return -EBUSY;
}
status = 0;
spin_unlock(&lock);
pr_info("spinlock_test_open is called by [pid: %d]\n",task_pid_nr(current));

return 0;
}
ssize_t spinlock_test_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
pr_info("spinlock_test_read is called\n");
return 0;
}
ssize_t spinlock_test_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
pr_info("spinlock_test_write is called\n");
return 0;
}
int spinlock_test_release(struct inode *inode, struct file *file)
{
spin_lock(&lock);
status = 1;
spin_unlock(&lock);
pr_info("spinlock_test_release is called by [pid: %d]\n", task_pid_nr(current));
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = spinlock_test_open,
.read = spinlock_test_read,
.write = spinlock_test_write,
.release = spinlock_test_release,
};

static int __init spinlock_test_init(void)
{
int err;
test_drv_dat = kzalloc(sizeof(struct test_drv_data), GFP_KERNEL);
if (test_drv_dat == NULL) {
err = -ENOMEM;
goto kzalloc_fail;
}
err = alloc_chrdev_region(&test_drv_dat->dev_num, 0, 1, "spinlock test chrdev region\n");

if (err < 0)
goto alloc_chrdev_region_fail;
cdev_init(&test_drv_dat->cdev, &fops);
test_drv_dat->cdev.owner = THIS_MODULE;
err = cdev_add(&test_drv_dat->cdev, test_drv_dat->dev_num, 1);
if (err < 0)
goto cdev_add_fail;
test_drv_dat->class = class_create(THIS_MODULE, "chrdev");
if (IS_ERR(test_drv_dat->class)) {
err = PTR_ERR(test_drv_dat->class);
goto class_create_fail;
}
test_drv_dat->dev = device_create(test_drv_dat->class, NULL, test_drv_dat->dev_num, NULL,
"spinlock_test%d", 0);
if (IS_ERR(test_drv_dat->dev)) {
err = PTR_ERR(test_drv_dat->dev);
goto device_create_fail;
}
// 初始化自旋锁
spin_lock_init(&lock);

return 0;

device_create_fail:
class_destroy(test_drv_dat->class);
class_create_fail:
cdev_del(&test_drv_dat->cdev);
cdev_add_fail:
unregister_chrdev_region(test_drv_dat->dev_num, 1);
alloc_chrdev_region_fail:
kfree(test_drv_dat);
kzalloc_fail:
return err;
}

static void __exit spinlock_test_exit(void)
{
device_destroy(test_drv_dat->class, test_drv_dat->dev_num);
class_destroy(test_drv_dat->class);
cdev_del(&test_drv_dat->cdev);
unregister_chrdev_region(test_drv_dat->dev_num, 1);
kfree(test_drv_dat);
}

module_init(spinlock_test_init);
module_exit(spinlock_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@163.com>");
MODULE_DESCRIPTION("This is a test sample for spinlock");

测试:

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
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char **argv)
{
pid_t pid;
int fd = -1;

pid = fork();
if (pid < 0) {
perror("fork error");
fprintf(stderr, "Error: %s[errno: %d]\n", strerror(errno), errno);
exit(EXIT_FAILURE);
} else if (pid == 0) { // child process
printf("[child: <pid: %d>] try to open /dev/spinlock_test0\n",getpid());
fd = open("/dev/spinlock_test0", O_RDWR);
if (fd < 0) {
perror("[child] open error");
fprintf(stderr, "[child] Error: %s[errno: %d]\n", strerror(errno), errno);
exit(EXIT_FAILURE);
}
printf("[child: <pid: %d>] open /dev/spinlock_test0 success\n", getpid());
sleep(2);
close(fd);

} else { // parent process
printf("[parent: <pid: %d>] try to open /dev/spinlock_test0\n", getpid());
fd = open("/dev/spinlock_test0", O_RDWR);
if (fd < 0) {
perror("[parent] open error");
fprintf(stderr, "[parent] Error: %s[errno: %d]\n", strerror(errno), errno);
exit(EXIT_FAILURE);
}
sleep(2);
printf("[parent: <pid: %d>] open /dev/spinlock_test0 success\n", getpid());

close(fd);
}

return 0;
}

测试用例:

1
2
3
4
5
6
7
8
9
10
$ insmod spinlock_test.ko
[ 10.948417] spinlock_test: loading out-of-tree module taints kernel.
$ ./test_spinlock.o
[child: <pid: 102>] try to open /dev/spinlock_test0
[parent: <pid: 101>] try to open /dev/spinlock_test0
[ 16.331206] spinlock_test_open is called by [pid: 102]
[child: <pid: 102>] open /dev/spinlock_test0 success
[parent] open error: Device or resource busy
[parent] Error: Device or resource busy[errno: 16]
$ [ 18.335915] spinlock_test_release is called by [pid: 102]

注意事项

  1. 由于自旋锁会”原地等待”,因为原地等待会继续占用 CPU,会消耗 CPU 资源,所以锁的时间不能太长。也就是临界区的代码不能太多
  2. 在自旋锁保护的临界区里面不能调用可能会导致线程休眠的函数,否则可能会发生死锁
  3. 自旋锁一般是用在多核 SOC 上

自旋锁死锁

自旋锁死锁是指多个任务或进程因互相等待彼此释放资源,导致谁也无法继续往下执行的现象。

举个例子,假设有进程 A 和进程 B 俩个进程,进程 A 对资源 1 持有自旋锁,同时进程 A 也想获取资源 2。而进程 B 对资源 2 持有自旋锁,同时进程 B 也想获取资源 1。在这种情况下进程 A 和进程 B 都在等待彼此释放资源,从而造成了死锁。

又如进程 A 在拥有自旋锁期间发生了中断,CPU 转头去执行中断处理函数,而此时的中断处理函数也需要获取同一把自旋锁。这时由于锁已被 A 占用,中断处理函数只能等待自旋,从而导致系统进入死锁状态。

  • 在中断处理函数中也要获得自旋锁时,驱动程序则需要在拥有自旋锁时禁止中断(spin_lock_irqsave,spin_lock_irqrestore),在释放自旋锁时使能中断

  • 同时尽量短时间拥有自旋锁,长时间拥有自旋锁可能会导致系统资源耗尽从而造成死锁。

  • 最后也要避免某个获得自旋锁的函数调用其他同样试图获取这个锁的函数,否则也会造成死锁。

  • 临界区中不能调用任何能够引起休眠或阻塞的函数

信号量

自旋锁是通过“原地等待”的方式来处理并发与竞争的,所以被保护的临界区不能太长,以免造成对 CPU 资源的浪费。但是有些情况下我们必不可免的要长时间对一些资源进行保护。这时候可以使用信号量。

信号量会引起调用者睡眠,所以信号量也叫睡眠锁。

信号量具有 P 操作V 操作。P 操作会使信号量的值减一,如果此时信号量的值小于等于 0,说明资源都被占用,如果还有其他进程需要使用,则该进程会被加入等待队列,如果大于 0,表示可用资源为 n

定义

include/linux/semaphore

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
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2008 Intel Corporation
* Author: Matthew Wilcox <willy@linux.intel.com>
*
* Please see kernel/locking/semaphore.c for documentation of these functions
*/
#ifndef __LINUX_SEMAPHORE_H
#define __LINUX_SEMAPHORE_H

#include <linux/list.h>
#include <linux/spinlock.h>

/* Please don't access any members of this structure directly */
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};

#define __SEMAPHORE_INITIALIZER(name, n) \
{ \
.lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock), \
.count = n, \
.wait_list = LIST_HEAD_INIT((name).wait_list), \
}

#define DEFINE_SEMAPHORE(name) \
struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

static inline void sema_init(struct semaphore *sem, int val)
{
static struct lock_class_key __key;
*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}

extern void down(struct semaphore *sem);
extern int __must_check down_interruptible(struct semaphore *sem);
extern int __must_check down_killable(struct semaphore *sem);
extern int __must_check down_trylock(struct semaphore *sem);
extern int __must_check down_timeout(struct semaphore *sem, long jiffies);
extern void up(struct semaphore *sem);

#endif /* __LINUX_SEMAPHORE_H */

API

API / 宏 返回值类型 功能说明
DEFINE_SEMAPHORE(name) N/A 定义并初始化一个信号量,初值为 1(互斥信号量)。
sema_init(struct semaphore *sem, int val) void 初始化一个信号量 sem,初值为 val
down(struct semaphore *sem) void 获取信号量。如果计数 > 0,减 1 并立即返回;如果计数为 0,阻塞等待直到可获取。
down_interruptible(struct semaphore *sem) int 获取信号量。如果被中断信号打断,返回非 0;否则阻塞直到获取成功并返回 0。
down_killable(struct semaphore *sem) int 获取信号量,如果进程接收到致命信号则返回非 0;否则阻塞直到获取成功并返回 0。
down_trylock(struct semaphore *sem) int 尝试立即获取信号量。如果获取成功返回非 0,否则立即返回 0,不阻塞。
down_timeout(struct semaphore *sem, long jiffies) int 尝试获取信号量,最多阻塞 jiffies 个时钟周期。返回值 >0 表示成功,0 表示超时,负值表示错误或信号打断。
up(struct semaphore *sem) void 释放信号量(计数 +1),并唤醒等待队列中的任务。

down 和 down_interruptible 的区别:

特性 down(struct semaphore *sem) down_interruptible(struct semaphore *sem)
阻塞行为 如果信号量 count 为 0,调用进程会无限阻塞,直到信号量可用 如果信号量 count 为 0,调用进程会阻塞,但可被中断信号打断
信号打断 不响应信号,中断不会让 down() 返回 响应信号,如果阻塞期间收到信号,则立即返回非 0
返回值 void(永远成功,除非内核 BUG) int,0 表示获取成功,非 0 表示被信号打断,获取失败
使用场景 不需要响应中断,确保获取信号量 需要可中断阻塞,例如用户进程可以响应 Ctrl+C 或其他信号

示例

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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/semaphore.h>
#include <linux/sched.h>

struct test_drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
};

static struct test_drv_data *drv_dat;
static struct semaphore sema;

int semaphore_test_open(struct inode *inode, struct file *file)
{
int ret;
file->private_data = drv_dat;
ret = down_interruptible(&sema);
if (ret != 0) {
pr_info("semaphore_test_open is called by [pid: %d] but is interrupted\n",
current->pid);
return -EINTR;
}
pr_info("semaphore_test_open is called by [pid: %d]\n", current->pid);
return 0;
}

int semaphore_test_release(struct inode *inode, struct file *file)
{
up(&sema);
pr_info("semaphore_test_release is called by [pid: %d]\n", current->pid);
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = semaphore_test_open,
.release = semaphore_test_release,
};

static int __init semaphore_test_init(void)
{
int err;

drv_dat = (struct test_drv_data *)kzalloc(sizeof(struct test_drv_data), GFP_KERNEL);
if (drv_dat == NULL) {
err = -ENOMEM;
goto kzalloc_fail;
}

err = alloc_chrdev_region(&drv_dat->dev_num, 0, 1, "semaphore_test_chrdev_region");
if (err < 0)
goto alloc_chrdev_region_fail;
cdev_init(&drv_dat->cdev, &fops);
drv_dat->cdev.owner = THIS_MODULE;
err = cdev_add(&drv_dat->cdev, drv_dat->dev_num, 1);
if (err < 0)
goto cdev_add_fail;
drv_dat->class = class_create(THIS_MODULE, "chrdev_test");
if (IS_ERR(drv_dat->class)) {
err = PTR_ERR(drv_dat->class);
goto class_create_fail;
}

drv_dat->dev =
device_create(drv_dat->class, NULL, drv_dat->dev_num, NULL, "semaphore_test%d", 0);
if (IS_ERR(drv_dat->dev)) {
err = PTR_ERR(drv_dat->dev);
goto device_create_fail;
}
// 初始化信号量
sema_init(&sema, 1);

return 0;
device_create_fail:
class_destroy(drv_dat->class);
class_create_fail:
cdev_del(&drv_dat->cdev);
cdev_add_fail:
unregister_chrdev_region(drv_dat->dev_num, 1);
alloc_chrdev_region_fail:
kfree(drv_dat);
kzalloc_fail:
return err;
}

static void __exit semaphore_test_exit(void)
{
device_destroy(drv_dat->class, drv_dat->dev_num);
class_destroy(drv_dat->class);
cdev_del(&drv_dat->cdev);
unregister_chrdev_region(drv_dat->dev_num, 1);
kfree(drv_dat);
}

module_init(semaphore_test_init);
module_exit(semaphore_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@163.com>");
MODULE_DESCRIPTION("This is test sample for semaphore");

测试用例:

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
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
pid_t pid;
int fd;

pid = fork();
if (pid < 0) {
perror("fork error");
exit(EXIT_FAILURE);
} else if (pid == 0) { // child
fd = open("/dev/semaphore_test0", O_RDWR);
if (fd < 0) {
perror("child open /dev/semaphore_test0 error");
exit(EXIT_FAILURE);
}
printf("[child: pid<%d>] open /dev/semaphore_test0 success\n", getpid());
sleep(2);
close(fd);
} else { // parent
fd = open("/dev/semaphore_test0", O_RDWR);
if (fd < 0) {
perror("parent open /dev/semaphore_test0 error");
exit(EXIT_FAILURE);
}
printf("[parent: pid<%d>] open /dev/semaphore_test0 success\n", getpid());
sleep(2);
close(fd);
}

return 0;
}
1
2
3
4
5
6
7
8
9
$ insmod semaphore_test.ko
[ 15.981179] semaphore_test: loading out-of-tree module taints kernel.
$ ./test_semaphore.o
[ 20.535287] semaphore_test_open is called by [pid: 101]
[parent: pid<101>] open /dev/semaphore_test0 success
[ 22.544668] semaphore_test_release is called by [pid: 101]
[ 22.545409] semaphore_test_open is called by [pid: 102]
[child: pid<102>] open /dev/semaphore_test0 success
[ 24.550326] semaphore_test_release is called by [pid: 102]

注意

  1. 信号量的值不能小于 0
  2. 访问共享资源时,信号量执行”减一”操作,访问完成后再执行”加一”操作
  3. 当信号量的值为 0,想访问共享资源的线程必须等待,当信号量大于 0 时,等待的线程才可以访问
  4. 因为信号量会引起休眠,所以中断里面不能使用信号量
  5. 如果共享资源持有时间比较长,一般用信号量而不用自旋锁
  6. 在同时使用信号量和自旋锁的时候,要先获取信号量,再使用自旋锁。因为信号量会导致睡眠。

互斥锁

同一个资源同一个时间只有一个访问者在进行访问,其他的访问者结束以后才可以访问这个资源。这就是互斥。

互斥锁和信号量为 1 的情况很类似,但是互斥锁更简洁高效。不过要注意的事项更多。

定义

include/linux/mutex.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct mutex {
atomic_long_t owner;
spinlock_t wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
struct list_head wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};

API

函数/宏 返回值类型 功能说明
DEFINE_MUTEX(name) struct mutex 静态定义并初始化一个互斥锁,初始状态为 unlocked
mutex_init(struct mutex *lock) void 初始化互斥锁为 unlocked 状态
mutex_destroy(struct mutex *lock) void 销毁互斥锁(仅在 DEBUG_MUTEXES 下实际工作)
mutex_lock(struct mutex *lock) void 获取互斥锁,如果锁被占用则阻塞直到锁可用
mutex_lock_interruptible(struct mutex *lock) int 获取互斥锁,如果锁被占用则阻塞,可被信号中断,成功返回 0,被信号打断返回 -EINTR
mutex_lock_killable(struct mutex *lock) int 获取互斥锁,如果锁被占用则阻塞,可被致死信号打断,返回值同上
mutex_lock_io(struct mutex *lock) void 获取互斥锁,允许在 I/O 上下文使用(不常用)
mutex_lock_nested(struct mutex *lock, unsigned int subclass) void 嵌套锁获取,用于 Lockdep 分析,实际等效于 mutex_lock()
mutex_lock_interruptible_nested(struct mutex *lock, unsigned int subclass) int 可中断的嵌套锁获取,用于 Lockdep 分析
mutex_lock_killable_nested(struct mutex *lock, unsigned int subclass) int 可致死信号打断的嵌套锁获取,用于 Lockdep 分析
mutex_lock_nest_lock(struct mutex *lock, struct lockdep_map *nest_lock) void 嵌套锁获取,用于 Lockdep 分析
mutex_trylock(struct mutex *lock) int 尝试获取互斥锁,不阻塞,成功返回 1,失败返回 0
mutex_trylock_recursive(struct mutex *lock) enum mutex_trylock_recursive_enum 尝试获取互斥锁,允许递归,返回 0/1/递归标志
mutex_unlock(struct mutex *lock) void 释放互斥锁,必须由持有锁的线程调用
atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock) int 将原子计数器减 1,如果减到 0 则获取 mutex;否则返回 0
mutex_is_locked(struct mutex *lock) bool 查询互斥锁是否被占用,true 表示锁被持有,false 表示未被持有

示例

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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/mutex.h>
#include <linux/sched.h>

struct test_drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
};

static struct test_drv_data *drv_dat;
static struct mutex drv_mutex;

int mutex_test_open(struct inode *inode, struct file *file)
{
int err;
file->private_data = drv_dat;
err = mutex_lock_interruptible(&drv_mutex);
if (err != 0) {
pr_err("pid: %d is interrupted", current->pid);
return err;
}
pr_info("mutex_test_open is called by pid: %d\n", current->pid);
return 0;
}

int mutex_test_release(struct inode *inode, struct file *file)
{
mutex_unlock(&drv_mutex);
pr_info("mutex_test_release is called by pid: %d\n", current->pid);
return 0;
}

static struct file_operations fops = {
.owner = THIS_MODULE,
.open = mutex_test_open,
.release = mutex_test_release,
};

static int __init mutex_test_init(void)
{
int err;
drv_dat = (struct test_drv_data *)kzalloc(sizeof(struct test_drv_data), GFP_KERNEL);

if (drv_dat == NULL) {
err = -ENOMEM;
goto kzalloc_fail;
}

err = alloc_chrdev_region(&drv_dat->dev_num, 0, 1, "mutex_test_chrdev_region");
if (err < 0)
goto alloc_chrdev_region_fail;

cdev_init(&drv_dat->cdev, &fops);
drv_dat->cdev.owner = THIS_MODULE;
err = cdev_add(&drv_dat->cdev, drv_dat->dev_num, 1);
if (err < 0)
goto cdev_add_fail;

drv_dat->class = class_create(THIS_MODULE, "chrdev_test");
if (IS_ERR(drv_dat->class)) {
err = PTR_ERR(drv_dat->class);
goto class_create_fail;
}

drv_dat->dev =
device_create(drv_dat->class, NULL, drv_dat->dev_num, NULL, "mutex_test%d", 0);
if (IS_ERR(drv_dat->dev)) {
err = PTR_ERR(drv_dat->dev);
goto device_create_fail;
}
// 初始化互斥锁
mutex_init(&drv_mutex);
return 0;
device_create_fail:
class_destroy(drv_dat->class);
class_create_fail:
cdev_del(&drv_dat->cdev);
cdev_add_fail:
unregister_chrdev_region(drv_dat->dev_num, 1);
alloc_chrdev_region_fail:
kfree(drv_dat);
kzalloc_fail:
return err;
}

static void __exit mutex_test_exit(void)
{
kfree(drv_dat);
}

module_init(mutex_test_init);
module_exit(mutex_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@163.com>");
MODULE_DESCRIPTION("This is a test sample for mutex");

测试用例:

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
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
pid_t pid;
int fd;
char prefix[16];

pid = fork();
if (pid < 0) {
perror("fork error");
exit(EXIT_FAILURE);
} else if (pid == 0) {
snprintf(prefix, sizeof(prefix), "[pid: %d]", getpid());

fd = open("/dev/mutex_test0", O_RDWR);
if (fd < 0) {
perror(prefix);
exit(EXIT_FAILURE);
}
printf("%s child open success\n", prefix);
sleep(2);
close(fd);

} else {
snprintf(prefix, sizeof(prefix), "[pid: %d]", getpid());

fd = open("/dev/mutex_test0", O_RDWR);
if (fd < 0) {
perror(prefix);
exit(EXIT_FAILURE);
}
printf("%s parent open success\n", prefix);
sleep(2);
close(fd);
}

return 0;
}
1
2
3
4
5
6
7
8
9
$ insmod mutex_test.ko
[ 13.589343] mutex_test: loading out-of-tree module taints kernel.
$ ./test_mutex.o
[ 17.141253] mutex_test_open is called by pid: 101
[pid: 101] parent open success
[ 19.150144] mutex_test_release is called by pid: 101
[ 19.151379] mutex_test_open is called by pid: 102
[pid: 102] child open success
[ 21.156199] mutex_test_release is called by pid: 102

注意事项

  1. 互斥锁会导致休眠,因此在中断中不能使用互斥锁
  2. 同一时刻只能有一个线程持有互斥锁,并且只有持有者可以解锁
  3. 不允许递归上锁和解锁