05、信号量的使用 *
一、信号量
信号量是一个计数器,用来控制同时访问共享资源的线程数量:
- 初始化数值决定允许的线程数:比如设为2,就允许2个线程同时用资源;设为1,就只能1个线程用(类似“排队独占”)。
- 数值不能小于0:当数值为0时,后续线程必须等待,直到有线程释放资源。
① 使用步骤
线程要使用资源时:先尝试把计数器减1(称为“P操作”)。
如果此时数值≥1,操作成功,线程可以继续使用资源。
- 如果数值已经是0,线程会被暂停(暂时挂起),进入等待队列。
使用完资源后:线程必须执行“加1”操作(称为“V操作”)。
这会唤醒等待队列中的线程,让它们重新尝试获取资源。
② 与自旋锁的区别
信号量:线程等待时会暂停(比如“睡着”),适合处理耗时任务(如读写文件、网络请求)。
自旋锁:线程不停“空转”等待(像原地踏步),适合资源占用时间极短的情况(如快速读写数据)。
关键限制:
- 信号量不能在中断处理中使用(因为中断无法暂停线程)。
- 如果需要同时用信号量和自旋锁,必须先用信号量再用自旋锁,否则可能死锁。
信号量就像资源的“计数器”,通过加减操作控制谁可以使用资源。当资源被占满时,线程会暂停等待,用完后主动释放并唤醒其他人。它适合处理耗时任务,但不适合频繁短时间操作或中断场景。
二、信号量结构体
Linux内核使用semaphore结构体来管理信号量,这个结构体定义在内核源码的include/linux/semaphore.h文件中。
C
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
1
2
3
4
5
2
3
4
5
三、信号量API
与信号量相关的 API 函数同样定义在 semaphore.h 文件内, 部分常用 API 函数如下 所示:
函数 | 描述 |
---|---|
DEFINE_SEAMPHORE(name) | 定义信号量, 并且设置信号量的值为 1。 |
void sema_init(struct semaphore *sem, int val) | 初始化信号量 sem, 设置信号量值为 val。 |
void down(struct semaphore *sem) | 获取信号量, 不能被中断打断, 如 ctrl+c |
int down_interruptible(struct semaphore *sem) | 获取信号量, 可以被中断打断, 如 ctrl+c |
void up(struct semaphore *sem) | 释放信号量 |
int down_trylock(struct semaphore *sem); | 尝试获取信号量, 如果能获取到信号量就获取, 并且返回 0。如果不能就返回非 0 |
四、实验程序
要避免程序同时访问共享资源时产生冲突,可以这样做:
- 先把信号量的值设为1(表示同一时间只能有一个进程使用)
- 在打开文件的open()函数开头,加入获取信号量的代码
- 在关闭文件的release()函数结尾,加入释放信号量的代码
这样就能确保每次只能有一个进程操作文件,其他进程需要等待当前操作完成才能继续。整个过程就像在门口设置一个"正在使用"的牌子,用完再把牌子收起来。
C++
#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/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 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)
{
up(&semaphore_test);//信号量数量加1
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)
{
sema_init(&semaphore_test,1);//初始化信号量结构体semaphore_test,并设置信号量的数量为1
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");
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
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
Makefile:
C
export ARCH=arm64
export CROSS_COMPILE=/home/book/rk/tspi/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
obj-m +=semaphore.o #此处要和你的驱动源文件同名
KDIR := /home/book/rk/tspi/kernel #这里是你的内核目录
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules #make#操作
clean:
make -C $(KDIR) M=$(PWD) clean #make clean操作
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
app
aarch64-linux-gnu-gcc 编译
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