一、信号量
信号量是一个计数器,用来控制同时访问共享资源的线程数量:
- 初始化数值决定允许的线程数:比如设为 2,就允许 2 个线程同时用资源;设为 1,就只能 1 个线程用(类似"排队独占")。
- 数值不能小于 0:当数值为 0 时,后续线程必须等待,直到有线程释放资源。
1、使用步骤
- 线程要使用资源时:先尝试把计数器减 1(称为"P 操作")。
- 如果此时数值 ≥ 1,操作成功,线程可以继续使用资源。
- 如果数值已经是 0,线程会被暂停(暂时挂起),进入等待队列。
- 使用完资源后:线程必须执行"加 1"操作(称为"V 操作")。
- 这会唤醒等待队列中的线程,让它们重新尝试获取资源。
2、与自旋锁的区别
- 信号量:线程等待时会暂停(比如"睡着"),适合处理耗时任务(如读写文件、网络请求)。
- 自旋锁:线程不停"空转"等待(像原地踏步),适合资源占用时间极短的情况(如快速读写数据)。
关键限制
- 信号量不能在中断处理中使用(因为中断无法暂停线程)。
- 如果需要同时用信号量和自旋锁,必须先用信号量再用自旋锁,否则可能死锁。
信号量就像资源的"计数器",通过加减操作控制谁可以使用资源。当资源被占满时,线程会暂停等待,用完后主动释放并唤醒其他人。它适合处理耗时任务,但不适合频繁短时间操作或中断场景。
二、信号量结构体
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_SEMAPHORE(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 |
四、实验程序
源代码下载
bash
git clone git@gitee.com:yangxuesong314/linux-driver.git1
- 若之前已 git 拉取代码可以忽略
- 代码位于:
linux-driver/03.内核互斥锁技术/03.信号量的使用
要避免程序同时访问共享资源时产生冲突,可以这样做:
- 先把信号量的值设为 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;
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";
printk("\nthis is read_test \n");
ret = copy_to_user(ubuf, kbuf, strlen(kbuf));
if (ret != 0) {
printk("copy_to_user is error \n");
}
printk("copy_to_user is ok \n");
return 0;
}
static char kbuf[10] = {0};
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);
if (ret != 0) {
printk("copy_from_user is error\n");
}
if (strcmp(kbuf, "linux") == 0) {
ssleep(4);
} else if (strcmp(kbuf, "kernel") == 0) {
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;
int major, minor;
struct cdev cdev_test;
struct class *class_test;
};
struct chrdev_test dev1;
struct file_operations fops_test = {
.owner = THIS_MODULE,
.open = open_test,
.read = read_test,
.write = write_test,
.release = release_test,
};
static int __init atomic_init(void)
{
sema_init(&semaphore_test, 1); // 初始化信号量,并设置信号量的数量为 1
if (alloc_chrdev_region(&dev1.dev_num, 0, 1, "chrdev_name") < 0) {
printk("alloc_chrdev_region is error \n");
}
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, 0, dev1.dev_num, 0, "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");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
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
Makefile:
makefile
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
clean:
make -C $(KDIR) M=$(PWD) clean1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
驱动加载:insmod semaphore.ko

应用代码(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;
char str1[10] = {0};
fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("file open failed \n");
return -1;
}
if (strcmp(argv[2], "Linux") == 0) {
write(fd, "Linux", 10);
} 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24