06、互斥锁的使用 *
互斥锁可以看作是值只能为0或1的特殊信号量,但既然有了信号量,为什么还需要单独设计互斥锁呢?其实两者虽然功能有重叠,但用途和设计侧重点不同:
- 功能专注性 互斥锁专门用于「保护共享资源不被多个线程同时访问」,就像一把只能开一扇门的钥匙,简单直接。而信号量能管理「多个资源的使用数量」,比如同时允许3个线程访问数据库,更像是一个计数器。
- 使用更安全 互斥锁通常自带「所有权」机制,确保锁是谁申请的就必须由谁释放,避免误操作。而普通信号量无法识别调用者,可能因误操作导致逻辑错误。
- 性能更高效 由于功能单一,互斥锁的实现往往更轻量、更快。频繁的锁竞争场景下,专用互斥锁比通用信号量更省资源。
- 编程习惯区分 明确使用互斥锁能直接告诉其他开发者「这段代码在做资源互斥」,而信号量则暗示「在处理资源计数或同步问题」,这样的命名差异让代码更易读。
总结:信号量是万能工具,能解决更复杂的问题;互斥锁是专用工具,专为「互斥访问」设计,简单安全且高效。就像厨房里既有瑞士军刀,也需要专门的削皮刀——虽然功能有重叠,但各有所长。
一、互斥锁是什么
在之前学的章节里,把信号量数值设为1也能实现互斥效果,和本章要讲的互斥锁作用一样。虽然两者都能保证同一时间只有一个线程操作数据,但互斥锁的实现方式更高效、更简单。所以如果发现用信号量数值为1的地方,最好直接换成互斥锁。
当多个线程要同时修改同一个共享数据时,必须做好同步控制。 线程同步的作用就是让多个线程安全地访问共享资源,最简单的办法就是用互斥锁。
互斥锁就像一把锁,为资源标记两种状态:锁上或没锁。 当某个线程要修改共享数据时,必须先"上锁"。上锁后其他线程就不能操作这个数据了,直到这个线程完成操作后"开锁"。只有开锁后,其他线程才能重新尝试上锁并操作数据。 这样就能保证每次只有一个线程在写数据,避免多个线程同时修改导致的数据错误。
二、理解互斥锁:生活案例
比如公司里,当一个人正在用打印机时,如果另一个人也想用,不处理的话纸张会混在一起。解决方法是:打印机所在的房间有一把锁,默认是开着的。
当A要打印时:
先看门是否锁着。没锁的话,进去并锁上门开始打印。
这时如果B也想用打印机:
- B来看锁,发现门已锁,就在外面等。
等A打印完:
- A会开门出来,B就能进去并锁门开始用了。
这就是互斥锁的工作方式:每次只允许一个人使用资源,其他人必须等待。
三、API
DEFINE_MUTEX(name)
直接定义并初始化一个互斥体变量,例如:
DEFINE_MUTEX(my_mutex); // 创建名为my_mutex的互斥体
void mutex_init(struct mutex *lock)
手动初始化一个互斥体变量(需先定义结构体)。
struct mutex my_mutex;
mutex_init(&my_mutex); // 初始化my_mutex
2
void mutex_lock(struct mutex *lock)
尝试获取互斥体(即上锁)。- 如果锁未被占用,立即成功获取。
- 如果已被占用,当前进程会暂停执行,等待锁被释放。
void mutex_unlock(struct mutex *lock)
释放已持有的互斥体(即解锁)。
- 释放后,等待队列中的第一个进程会被唤醒继续执行。
int mutex_is_locked(struct mutex *lock)
检查互斥体是否被锁定:
- 若已被其他线程占用,返回
1
; - 若未被占用,返回
0
。
关键特性
- 互斥性:同一时间只能有一个线程持有互斥体。
- 阻塞等待:
mutex_lock
无法立即获取锁时,进程会主动挂起,直到锁可用。 - 自动唤醒:解锁时会唤醒等待队列中的一个进程。
三、互斥锁使用规则
互斥锁的使用规则:
- 锁必须由持有者自己释放 只有当前占用锁的线程才能解锁,其他线程无法操作。
- 不能重复释放锁 释放锁后若再次释放同一锁,会导致程序崩溃或不可预测错误。
- 同一线程不能重复加锁 若一个线程已持有锁,再次尝试加锁会导致程序卡死(死锁)。
- 必须用专用初始化函数 初始化锁时只能用系统提供的初始化接口(如
pthread_mutex_init
),不能用memset
等函数手动设置。 - 不能重复初始化锁 已初始化的锁不能再重新初始化,否则会出错。
- 线程退出前必须释放所有锁 线程结束前必须解开所有自己持有的锁,否则其他线程可能无法继续运行。
补充说明:
- 为什么不能在中断中使用互斥锁? 因为互斥锁会阻塞线程,而中断处理不能被阻塞,可能导致系统崩溃。
- 递归加锁为何禁止? 同一线程多次加锁同一锁会导致自己无法解锁,最终死锁。
四、使用案例
初始化:
加锁、放锁
五、实验
驱动代码:
#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>
struct mutex mutex_test;//定义mutex类型的互斥锁结构体变量mutex_test
static int open_test(struct inode *inode,struct file *file)
{
printk("\nthis is open_test \n");
mutex_lock(&mutex_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)
{
mutex_unlock(&mutex_test);//互斥锁解锁
printk("\nthis is release_test \n");
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)
{
mutex_init(&mutex_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");
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
应用代码:
#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;
}
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