LV040-信号量
一、信号量简介
1. 什么是信号量
1.1 一个例子
举个例子,某个停车场有 100 个停车位,这 100 个停车位大家都可以用,对于大家来说这 100 个停车位就是共享资源。假设现在这个停车场正常运行,我们要把车停到这个这个停车场肯定要先看一下现在停了多少车了?还有没有停车位?当前停车数量就是一个信号量,具体的停车数量就是这个信号量值,当这个值到 100 的时候说明停车场满了。停车场满的时我们可以等一会看看有没有其他的车开出停车场,当有车开出停车场的时候停车数量就会减一,也就是说信号量减一,此时我们就可以把车停进去了,我们把车停进去以后停车数量就会加一,也就是信号量加一。这就是一个典型的使用信号量进行共享资源管理的案例,在这个案例中使用的就是计数型信号量。
1.2 信号量
信号量是操作系统中最典型的用于同步和互斥的手段, 本质上是一个全局变量, 信号量的值表示控制访问资源的线程数, 可以根据实际情况来自行设置。
如果在初始化的时候将信号量量值设置为大于 1, 那么这个信号量就是 计数型信号量, 允许多个线程同时访问共享资源,不能用于互斥访问。
如果将信号量量值设置为 1, 那么这个信号量就是 二值信号量, 同一时间内只允许一个线程访问共享资源, 可以用于互斥访问。
注意! 信号量的值不能小于 0。 当信号量的值为 0 时, 想访问共享资源的线程必须等待, 直到信号量大于 0 时, 等待的线程才可以访问。 当访问共享资源时, 信号量执行“减一”操作, 访问完成后再执行“加一” 操作 。
2. 信号量的特点
相比于自旋锁,信号量可以使线程进入休眠状态,比如 A 与 B、 C 合租了一套房子,这个房子只有一个卫生间,一次只能一个人使用。某一天早上 A 去上卫生间了,过了一会 B 也想用卫生间,因为 A 在卫生间里面,所以 B 只能等到 A 用来了才能进去。 B 要么就一直在卫生间门口等着,等 A 出来,这个时候就相当于自旋锁。 B 也可以告诉 A,让 A 出来以后通知他一下,然后 B 继续回房间睡觉,这个时候相当于信号量。
可以看出,使用信号量会提高处理器的使用效率,毕竟不用一直傻乎乎的在那里“自旋”等待。但是,信号量的开销要比自旋锁大,因为信号量使线程进入休眠状态以后会切换线程,切换线程就会有开销。总结一下信号量的特点:
①、因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
②、因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
③、如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。
二、相关数据结构与 API
1. struct semaphore
Linux 内核使用 struct semaphore 结构体表示信号量:
/* Please don't access any members of this structure directly */
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};2. 相关 API
信号量相关的 API 函数声明可以在 semaphore.h - include/linux/semaphore.h 中找到:
| 函数 | 描述 |
|---|---|
| DEFINE_SEAMPHORE(name) | 定义一个信号量,并且设置信号量的值为 1。 |
| void sema_init(struct semaphore *sem, int val) | 初始化信号量 sem,设置信号量值为 val。 |
| void down(struct semaphore *sem) | 获取信号量,因为会导致休眠,因此不能在中断中使用。 |
| int down_trylock(struct semaphore *sem); | 尝试获取信号量,如果能获取到信号量就获取,并且返回 0。如果不能就返回非 0,并且不会进入休眠。 |
| int down_interruptible(struct semaphore *sem) | 获取信号量,和 down 类似,只是使用 down 进入休眠状态的线程不能被信号打断。而使用此函数进入休眠以后是可以被信号打断的。 |
| void up(struct semaphore *sem) | 释放信号量 |
3. 使用示例
struct semaphore sem; /* 定义信号量 */
sema_init(&sem, 1); /* 初始化信号量 */
down(&sem); /* 申请信号量 */
/* 临界区 */
up(&sem); /* 释放信号量 */三、信号量 demo
1. demo 源码
2. 开发板验证
我们将编译得到的 sdriver_demo.ko、app_demo.out 拷贝到开发板。
- (1)加载驱动
insmod sdriver_demo.ko- (2)app_demo.out 会创建子进程运行
./app_demo.out /dev/sdevchr 2
可以看到是一个执行完再执行另一个。没有信号量保护的时候是这样的:

参考资料: