互斥锁

提供比自旋锁更安全的互斥机制,支持睡眠等待,适用于可能阻塞的长临界区场景。

关键特性

  1. 强所有者模型

    • 仅持有者线程可解锁
    • 严格禁止递归锁定
  2. 状态跟踪

    • owner 字段存储持有者的 task_struct 指针 + 状态标志位
    1
    2
    3
    4
    5
    [ 持有者地址 | flags ]
    // flags 位含义:
    // bit0: 锁是否被持有 (MUTEX_FLAG_HOLD)
    // bit1: 是否有等待者 (MUTEX_FLAG_WAITERS)
    // bit2: 是否启用乐观自旋 (MUTEX_FLAG_HANDOFF)
  3. 乐观自旋优化

    • CONFIG_MUTEX_SPIN_ON_OWNER 启用时
    • 若持有者正在运行,新竞争者短暂自旋而非立即睡眠
  4. 等待队列

    • 竞争失败线程加入 wait_list 并进入 TASK_UNINTERRUPTIBLE 状态
  5. 上下文限制严格

    1
    2
    3
    4
    // 禁止在中断上下文使用
    void interrupt_handler() {
    mutex_lock(&lock); // 触发 BUG()
    }
  6. 额外内存开销

    • 每个 mutex 占用 32-48 字节(含调试字段)
  7. 性能折衷

    • 获取/释放锁平均耗时 ≈ 50-100 纳秒(自旋锁 ≈ 20-30 纳秒)
    • 睡眠/唤醒操作成本较高
  8. 功能局限性

    • 不支持读写分离(需用 rw_semaphore
    • 无法用于原子上下文(如网络协议栈 fast path)

核心 API 函数

初始化

1
2
3
4
5
6
// 静态初始化
DEFINE_MUTEX(name);

// 动态初始化
struct mutex my_mutex;
mutex_init(&my_mutex);

加锁操作

API 行为
void mutex_lock(struct mutex *) 阻塞直到获取锁(不可被信号中断)
int mutex_lock_interruptible(struct mutex *) 可被信号中断,返回 -EINTR
mutex_lock_killable(struct mutex *) 仅被致命信号中断(如 SIGKILL
int mutex_trylock(struct mutex *) 非阻塞尝试,成功返回 1,失败返回 0

解锁操作

1
2
mutex_unlock(struct mutex *);
// 严格校验调用者是否为锁持有者

状态检查

1
mutex_is_locked(struct mutex *);  // 返回 1 表示已锁定,0 表示未锁定

工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sequenceDiagram
participant TaskA as 任务A
participant Mutex as mutex
participant TaskB as 任务B

TaskA->>Mutex: mutex_lock()
Note over Mutex: owner = TaskA (FLAG_HOLD)

TaskB->>Mutex: mutex_lock()
alt 乐观自旋可用 && 持有者正在运行
loop 自旋检查
Mutex->>TaskB: 短暂自旋
end
else
Mutex->>Mutex: 添加 TaskB 到 wait_list
Mutex->>TaskB: 设置 TASK_UNINTERRUPTIBLE
TaskB-->>CPU: schedule() 让出 CPU
end

TaskA->>Mutex: mutex_unlock()
Mutex->>Mutex: 唤醒 wait_list 首任务
Mutex->>TaskB: 设置 owner = TaskB
TaskB->>Mutex: 获取锁成功

实验:

我们实验对LED的设备文件进行管理,在有单元对其进行访问的时候,其它所有单元都不能对其进行访问,主要的原理如下:

  1. 本次实验我们采用的是互斥体的原理进行编程的,我们先初始互斥体

    1
    mutex_init(&leddev->lock);/* 初始化信号量 */
  2. 加锁:如果当前进程尝试获取锁时,锁已经被其他进程持有,那么当前进程会进入可中断的睡眠状态,等待锁被释放。在睡眠等待期间,如果该进程收到了致命信号(如 SIGKILL),睡眠会被提前唤醒,函数会返回一个错误码,而不是一直等待下去。返回值为 0:表示成功获取了锁,进程继续执行 if 语句块之后的代码(即加锁成功)。返回值不为 0(通常是 -EINTR):表示在等待锁的过程中,进程被信号中断了,未能成功获取锁

    1
    2
    3
    if(down_interruptible(&leddev->sem)){
    return -ERESTARTSYS;
    }
  3. 释放互斥锁

    1
    mutex_unlock(&leddev->lock);

代码:

驱动代码:

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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/semaphore.h>
#define LEDDRV_CNT 1
#define LEDDRV_NAME "gpioled"
#define LED_OPEN 1
#define LED_CLOSE 0

struct leddev_struct{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
struct gpio_desc *leddes;
struct mutex lock;
};
struct leddev_struct *leddev;

static int ledopen(struct inode *node, struct file *file)
{

file->private_data = leddev;
//printk("led device open\n");
if(mutex_lock_interruptible(&leddev->lock)){
return -ERESTARTSYS;
}

return 0;
}

static int ledrelease (struct inode *node, struct file *file)
{
//printk("led device closed\n");
struct leddev_struct *leddev=(struct leddev_struct *)file->private_data;
mutex_unlock(&leddev->lock);

return 0;
}
static ssize_t ledwrite (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
struct leddev_struct *leddev=(struct leddev_struct *)file->private_data;
int ret=0;
char receive;
if(size!=1)
return -EINVAL;
ret=copy_from_user(&receive,buf,size);
if(ret<0){
return -EFAULT;
}

if(receive==LED_CLOSE){
//printk("led close\n");
gpiod_set_value(leddev->leddes,0);
}
else if(receive==LED_OPEN){
//printk("led open\n");
gpiod_set_value(leddev->leddes,1);
}
return size;
}

static struct file_operations led_fops={
.owner=THIS_MODULE,
.open=ledopen,
.write=ledwrite,
.release=ledrelease,
};

int leddrv_probe(struct platform_device *pdev){
int ret=0;
struct device *dev=&pdev->dev;
printk("matched success!\n");
leddev=devm_kzalloc(dev,sizeof(struct leddev_struct),GFP_KERNEL);
if(!leddev){
printk("kzalloc failed!\n");
return -ENOMEM;
}
if(leddev->major)
{
leddev->devid=MKDEV(leddev->major,0);
ret=register_chrdev_region(leddev->devid,LEDDRV_CNT,LEDDRV_NAME);
}
else{
ret=alloc_chrdev_region(&leddev->devid,0,LEDDRV_CNT,LEDDRV_NAME);
leddev->major=MAJOR(leddev->devid);
}
if(ret<0){
printk("devid register failed!\n");
goto chrdev_fail;
}
printk("major is %d\n",leddev->major);

cdev_init(&leddev->cdev,&led_fops);
ret=cdev_add(&leddev->cdev,leddev->devid,LEDDRV_CNT);
if(ret<0)
{
printk("cdev create error!\n");
goto cdev_fail;
}

leddev->class=class_create(THIS_MODULE,LEDDRV_NAME);
if(IS_ERR(leddev->class))
{
printk("class create error!\n");
ret=PTR_ERR(leddev->class);
goto class_fail;
}

leddev->device=device_create(leddev->class,NULL,leddev->devid,NULL,LEDDRV_NAME);
if(IS_ERR(leddev->device))
{
printk("device create error!\n");
ret=PTR_ERR(leddev->device);
goto device_fail;
}

leddev->leddes=devm_gpiod_get(dev,"led",GPIOD_OUT_LOW);
if(IS_ERR(leddev->leddes))
{
printk("leddes get error!\n");
ret=PTR_ERR(leddev->leddes);
goto leddes_fail;
}
platform_set_drvdata(pdev, leddev);

mutex_init(&leddev->lock);

return 0;
leddes_fail:
device_destroy(leddev->class,leddev->devid);
device_fail:
class_destroy(leddev->class);
class_fail:
cdev_del(&leddev->cdev);
cdev_fail:
unregister_chrdev_region(leddev->devid,LEDDRV_CNT);
chrdev_fail:

return ret;
}
int leddev_resume(struct platform_device *pdev){
struct leddev_struct *leddev = platform_get_drvdata(pdev);

device_destroy(leddev->class,leddev->devid);
class_destroy(leddev->class);
cdev_del(&leddev->cdev);
unregister_chrdev_region(leddev->devid,LEDDRV_CNT);

return 0;
}


const struct of_device_id led_device_id[]={
{.compatible="gpio-led"},
{},
};
struct platform_driver led_dev_driver={
.probe=leddrv_probe,
.driver={
.of_match_table=led_device_id,
.name="gpioled",
},
.remove=leddev_resume,
};

static int __init leddrv_init(void){
int ret=0;
platform_driver_register(&led_dev_driver);
return ret;
}

static void __exit leddrv_exit(void){
platform_driver_unregister(&led_dev_driver);
}


module_init(leddrv_init);
module_exit(leddrv_exit);

MODULE_AUTHOR("KEVIN");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("GPIO LED Platform Driver");


应用代码:

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

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

if (argc < 3) {
printf("Usage: ./%s /dev/<device> module(0/1)\n", argv[0]);
exit(1);
}

// 打开设备文件(确保有写权限)
fd = open(argv[1], O_WRONLY);
if (fd < 0) {
perror("open failed"); // 显示系统错误
exit(1);
}

// 解析并验证模块参数
input_mode = atoi(argv[2]);
if (input_mode != 0 && input_mode != 1) {
printf("Error: module must be 0 or 1\n");
exit(1);
}

// 写入数据并验证写入成功
if (write(fd, &input_mode, sizeof(input_mode)) != sizeof(input_mode)) {
perror("write failed"); // 显示系统错误
printf("Failed to write mode=%d\n", input_mode);
exit(1);
}
while(1);//让代码一直运行,来验证自旋锁的效果

close(fd); // 显式关闭文件描述符(虽然 exit 会自动关闭,但显式更好)
exit(0);
}