时间轴

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 返回交换前的旧值(可用于判断是否成功)

atomic_cmpxchg

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;

自旋锁相关 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
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/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/device.h>

#define DEVICE_NAME "device_test"
#define CLASS_NAME "class_test"

static spinlock_t spinlock_test; // 自旋锁保护 flag
static int flag = 1; // 设备状态标志:1=未打开,0=已打开

static char kbuf[10] = {0}; // 内核缓冲区

struct chrdev_test {
dev_t dev_num;
int major, minor;
struct cdev cdev_test;
struct class *class_test;
struct device *device_test;
};

static struct chrdev_test dev1;

/* ---------------- File Operations ---------------- */
static int open_test(struct inode *inode, struct file *file)
{
spin_lock(&spinlock_test);

if (flag != 1) {
spin_unlock(&spinlock_test);
return -EBUSY;
}

flag = 0;
spin_unlock(&spinlock_test);

printk(KERN_INFO "open_test: device opened\n");
return 0;
}

static ssize_t read_test(struct file *file, char __user *ubuf,
size_t len, loff_t *off)
{
int ret;
size_t to_copy = min(len, strlen(kbuf));

printk(KERN_INFO "read_test: reading from device\n");

ret = copy_to_user(ubuf, kbuf, to_copy);
if (ret != 0)
printk(KERN_WARNING "read_test: copy_to_user failed, %d bytes left\n", ret);
else
printk(KERN_INFO "read_test: copy_to_user success\n");

return to_copy;
}

static ssize_t write_test(struct file *file, const char __user *ubuf,
size_t len, loff_t *off)
{
int ret;
size_t to_copy = min(len, sizeof(kbuf) - 1);

ret = copy_from_user(kbuf, ubuf, to_copy);
if (ret != 0)
printk(KERN_WARNING "write_test: copy_from_user failed, %d bytes left\n", ret);

kbuf[to_copy] = '\0'; // 确保字符串结尾

if (strcmp(kbuf, "topeet") == 0)
ssleep(4);
else if (strcmp(kbuf, "itop") == 0)
ssleep(2);

printk(KERN_INFO "write_test: received buf='%s'\n", kbuf);
return len;
}

static int release_test(struct inode *inode, struct file *file)
{
spin_lock(&spinlock_test);
flag = 1;
spin_unlock(&spinlock_test);

printk(KERN_INFO "release_test: device released\n");
return 0;
}

static struct file_operations fops_test = {
.owner = THIS_MODULE,
.open = open_test,
.read = read_test,
.write = write_test,
.release = release_test,
};

/* ---------------- Module Init / Exit ---------------- */
static int __init atomic_init(void)
{
int ret;

spin_lock_init(&spinlock_test);

ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, DEVICE_NAME);
if (ret < 0) {
printk(KERN_ERR "alloc_chrdev_region failed\n");
return ret;
}

dev1.major = MAJOR(dev1.dev_num);
dev1.minor = MINOR(dev1.dev_num);
printk(KERN_INFO "Device major=%d minor=%d\n", dev1.major, dev1.minor);

cdev_init(&dev1.cdev_test, &fops_test);
dev1.cdev_test.owner = THIS_MODULE;

ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
if (ret < 0) {
printk(KERN_ERR "cdev_add failed\n");
unregister_chrdev_region(dev1.dev_num, 1);
return ret;
}

dev1.class_test = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(dev1.class_test)) {
ret = PTR_ERR(dev1.class_test);
printk(KERN_ERR "class_create failed\n");
cdev_del(&dev1.cdev_test);
unregister_chrdev_region(dev1.dev_num, 1);
return ret;
}

dev1.device_test = device_create(dev1.class_test, NULL,
dev1.dev_num, NULL, DEVICE_NAME);
if (IS_ERR(dev1.device_test)) {
ret = PTR_ERR(dev1.device_test);
printk(KERN_ERR "device_create failed\n");
class_destroy(dev1.class_test);
cdev_del(&dev1.cdev_test);
unregister_chrdev_region(dev1.dev_num, 1);
return ret;
}

printk(KERN_INFO "Module loaded successfully\n");
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);
unregister_chrdev_region(dev1.dev_num, 1);

printk(KERN_INFO "Module unloaded\n");
}

module_init(atomic_init);
module_exit(atomic_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");
MODULE_DESCRIPTION("Character device with spinlock example");

注意

  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
#include <linux/semaphore.h>
struct semaphore semaphore_test;//定义一个 semaphore 类型的结构体变量 semaphore_test
static int open_test(struct inode *inode,struct file *file)
{
printk("\nthis is open_test \n");
down(&semaphore_test);//信号量数量减 1
return 0;
}

static int release_test(struct inode *inode,struct file *file)
{
up(&semaphore_test);//信号量数量加 1
printk("\nthis is release_test \n");
return 0;
}

static int __init atomic_init(void)
{
sema_init(&semaphore_test,1);
}

注意

  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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/mutex.h>

static struct mutex mutex_test; // 定义 mutex 类型的互斥锁


static int open_test(struct inode *inode, struct file *file)
{
printk("\nthis is open_test \n");
mutex_lock(&mutex_test); // 获取互斥锁
return 0;
}

static int release_test(struct inode *inode, struct file *file)
{
mutex_unlock(&mutex_test); // 释放互斥锁
printk("\nthis is release_test \n");
return 0;
}

static int __init atomic_init(void)
{
mutex_init(&mutex_test); // 初始化互斥锁

if (alloc_chrdev_region(&dev1.dev_num, 0, 1, "chrdev_name") < 0) {
printk("alloc_chrdev_region is error \n");
return -1;
}
printk("alloc_chrdev_region is ok \n");

dev1.major = MAJOR(dev1.dev_num);
dev1.minor = MINOR(dev1.dev_num);
printk("major is %d, minor is %d\n", dev1.major, dev1.minor);

cdev_init(&dev1.cdev_test, &fops_test);
dev1.cdev_test.owner = THIS_MODULE;
cdev_add(&dev1.cdev_test, dev1.dev_num, 1);

dev1.class_test = class_create(THIS_MODULE, "class_test");
device_create(dev1.class_test, NULL, dev1.dev_num, NULL, "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);
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("topeet");

注意

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