rtthread - 信号和信号量学习笔记
目录
概述
信号和信号量,没有任何关系。
信号的本质是软中断,是线程层面对中断机制的一种模拟:
- 线程平时执行自己的函数
- 别的线程或者中断服务程序给线程发信号
- 线程当前的执行被打断,线程转而去执行信号处理函数,执行完信号处理
函数后再继续运行之前的代码
如果想要使用信号,需要在
rtconfig.h
中定义
#define RT_USING_SIGNALS
而信号量就像队列、邮箱一样,是用来传递信息的:
- 队列:传递各类大小的数据
- 邮箱:传递多个 32 位的数据
- 信号量:传递 1 个数值
消息队列、邮箱用于传输多个数据,但是有时候我们只需要传递状态,这个状态值需
要用一个数值表示,比如:
- 卖家:做好了 1 个包子,包子数量加 1
- 买家:买了 1 个包子,包子数量减 1
- 这个停车位我占了,停车位减 1
- 我开车走了,停车位加 1
在这种情况下我们只需要维护一个数值,使用信号量效率更高、更节省内存
信号
工作机制
信号的发送者、接收者都是线程:线程发信号给线程,尚不支持中断发信号给线程。
线程对收到的信号,有三类处理方式:
- 第一类:类似中断处理程序,对于需要处理的信号,线程可以指定处理函
- 数,由该函数处理
- 第二类:忽略某个信号,对该信号不处理
- 第三类:对该信号的处理,使用系统的默认方式
- 怎么使用这些处理方式?就是给信号安装处理函数:
rt_signal_install(SIGUSR1, my_signal_handler); /* 给信号 SIGUSR1 安装我们自己的处理函数 */
rt_signal_install(SIGUSR1, SIG_IGN); /* 不处理信号 SIGUSR1 */
rt_signal_install(SIGUSR1, SIG_DFL); /* 给信号 SIGUSR1 安装默认的处理函数,只是打印 */
假设有两个线程,线程
1
接收信号,线程
2
发送信号,用法如下:
- 线程 1 先安装信号并设置对信号的处理方式,然后解除阻塞
/* 安装信号,自定义处理函数 */
rt_signal_install(SIGUSR1, my_signal_handler);
/* 解除阻塞 */
rt_signal_unmask(SIGUSR1);
- 线程 2 发送信号,触发线程 1 进行处理
rt_thread_kill(thread1, SIGUSR1); //向线程 1 发送信号 SIGUSR1
- 线程 1 如果在挂起状态收到信号
- 线程 1 被唤醒
- 线程 1 先调用信号处理函数
- 再继续运行之前的代码
-
线程 1 如果在就绪状态收到信号
- 线程 1 再次运行时,先调用信号处理函数
- 再继续运行之前的代码
-
线程 2 给自己发信号: rt_thread_kill(thread2, SIGUSR1)
- 在 rt_thread_kill()函数内部直接调用信号处理函数
信号函数
安装
如果线程需要处理某一个信号,就需要现在线程中安装该信号。
安装信号的函数原型如下:
/* 安装一个信号,返回安装结果。
* 此函数有两个参数,分别为信号值和信号值的处理方法
* signo:信号值(SIGUSR1 或 SIGUSR2)
* handler:处理方式(SIG_IGN、SIG_DFL、自定义处理函数)
* 返回值: 成功返回 handler 值,错误返回 SIG_ERR
*/
rt_sighandler_t rt_signal_install(int signo, rt_sighandler_t handler);
第
2
个参数可以传入我们提供的函数,也可以:
- 传入 SIG_DFL,即使用系统的默认方式,系统会调用默认的处理函数 _signal_default_handler(),它只是打印
- 传入 SIG_IGN,就是忽略:接收到信号后不做任何处理
屏蔽/使能
如果屏蔽该信号,就该信号不会传达给安装该信号的线程。
屏蔽信号的函数原型如下:
void rt_signal_mask(int signo);
线程中可以安装好几个信号,根据需求选择使能部分信号,则这部分信号才能传达给该线程
接触屏蔽,即使能信号的函数原型如下:
void rt_signal_unmask(int signo);
发送信号
当需要某线程进行异常处理时,如果该线程安装了某信号,则使用
rt_thread_kill()
发送信号
发送信号的函数原型如下:
/* 发送信号。
* tid:接收信号的线程
* sig:信号值
* 返回值: 成功返回 RT_EOK,错误返回-RT_EINVAL
*/
int rt_thread_kill(rt_thread_t tid, int sig);
等待信号
一个线程可以等待别的线程给它发信号,函数原型如下:
/*等待信号
* set : 输入参数:想等待哪个信号,注意它是一个指针,*set 等于信号的编号
* si :输出参数:用来保存等到的信号的信息
* timeout :指定的等待时间
* 返回 : RT_EOK 等到信号
* RT_ETIMEOUT 超时
* RT_EINVAL 参数错误
*/
int rt_signal_wait(const rt_sigset_t *set, rt_siginfo_t *si, rt_int32_t timeout);
信号量
工作机制
信号量这个名字很恰当:
- 信号:起通知作用
- 量:用来表示资源的数量
- 支持的动作:"give"给出资源,计数值加 1;"take"获得资源,计数值减 1
信号量的典型场景是:
- 计数:生产者"give"信号量,让计数值加 1;消费者先"take"信号量,就是获得信号量,让计数值减 1。
- 资源管理:要想访问资源需要先"take"信号量,让计数值减 1;用完资源后"give"信号量,让计数值加 1。
信号量的
"give"
、
"take"
双方并不需要相同,可以用于生产者
-
消费者场合:
- 生产者为线程 A、B,消费者为线程 C、D
- 一开始信号量的计数值为 0,如果线程 C、D 想获得信号量,会有两种结果:
- 阻塞:买不到东西咱就等等吧,可以定个闹钟(超时时间)
- 即刻返回失败:不等
- 线程 A、B 可以生产资源,就是让信号量的计数值增加 1,并且把等待这个
- 唤醒谁?有两种方法。创建信号量时,可以指定一个参数 flag:
- RT_IPC_FLAG_PRIO:表示唤醒优先级最高的等待线程
- RT_IPC_FLAG_FIFO:表示唤醒等待时间最长的等待线程
信号量函数
在 RT-Thread 中,信号量控制块是操作系统用于管理信号量的一个数据结构,由结构体 struct rt_semaphore 表示。另外一种 C 表达方式 rt_sem_t,表示的是信号量的句柄,在 C 语言中的实现是指向信号量控制块的指针。信号量控制块结构的详细定义如下:
struct rt_semaphore
{
struct rt_ipc_object parent; /**< inherit from ipc_object */
rt_uint16_t value; /**< value of semaphore. */
rt_uint16_t reserved; /**< reserved field */
};
里面成员
rt_uint16_t value
表示信号量的值,最大为
65535
。
使用信号量时,先创建
/
初始化、然后去获取资源、释放资源。使用句柄
rt_sem_t
来表示一个信号量。

创建/初始化
使用信号量之前,要先创建,得到一个句柄;使用信号量时,要使用句柄来表明使用哪个信号量。
信号量的创建有两种方法:动态分配内存、静态分配内存,
- 动态分配内存:rt_sem_create(),从对象管理器中分配一个 semaphore 对象,并初始化这个对象
- 静态分配内存:rt_sem_init(),信号量结构体要事先分配好
rt_sem_create()
函数原型如下:
rt_sem_t rt_sem_create(const char *name,rt_uint32_t value,rt_uint8_t flag);
参数
|
说明
|
name
|
信号量名称
|
value
|
信号量初始值
|
flag
|
信号量标志,可以取:
RT_IPC_FLAG_FIFO
或
RT_IPC_FLAG_PRIO
|
返回值
|
信号量句柄:成功,返回句柄,以后使用句柄来操作信号量
RT_NULL
:失败
|
rt_sem_init()函数原型如下:
rt_err_t rt_sem_init(rt_sem_t sem,const char *name,rt_uint32_t value,rt_uint8_t flag)
参数
|
说明
|
sem
|
信号量对象的句柄
|
name
|
信号量的名字
|
value
|
信号量的初始值
|
flag
|
信号量标志:
RT_IPC_FLAG_FIFO
或
RT_IPC_FLAG_PRIO
|
返回值
|
RT_EOK
:成功
|
删除/脱离
不再使用一个信号量时:
- 删除它:rt_sem_delete(),只能删除使用 rt_sem_create()创建的信号量
- 脱离它:rt_sem_detach(),只能脱离使用 rt_sem_init()初始化的信号量
删除消息队列的函数为
rt_sem_delete()
,它会释放内存。原型如下:
rt_err_t rt_sem_delete(rt_sem_t sem);
删除信号量时,如果有线程正在等待该信号量,则内核会先唤醒这些线程(线程返回值是 RT_ERROR
),然后再释放信号量使用的内存,最后删除信号量对象。
脱离信号量,就是将信号量对象被从内核对象管理器中脱离。原型如下:
rt_err_t rt_sem_detach(rt_sem_t sem);
脱离信号量时,如果有线程在等待该信号量,则内核会先唤醒这些线程(线程返回值
是
- RT_ERROR)
。
获取/释放
RT-Thread
有两个获取信号量的函数
,
一个释放信号量函数:
- rt_sem_take() 获取信号量
- rt_sem_trytake() 无等待、尝试获取信号量
- rt_sem_release() 释放信号量
使用
rt_sem_take()
获取信号量时,信号量的值大于
0
,线程将获得信号量,信号量值减 1
;当信号量的值等于
0
时,线程会根据
timeout
参数等待,超时后才返回错误
( - RT_ETIMEOUT)。
使用
rt_sem_trytake()
获取信号量时,信号量的值大于
0
,线程将获得信号量,信号量值减 1
;当信号量的值等于
0
时,线程会直接返回,和
rt_sem_take(sem, RT_WAITING_NO)
作用相同,即不会等待。
使用
rt_sem_release()
将释放信号量:
- 如果有线程在等待这个信号量,此函数不会累加信号量的值,而是直接唤醒等待的线程:
- 有多个线程在等待同一个信号量时,谁被唤醒?
- 创建信号量时指定参数为 RT_IPC_FLAG_PRIO:表示唤醒优先级最高的等待线程
- 创建信号量时指定参数为 RT_IPC_FLAG_FIFO:表示唤醒等待时间最长的等待线程
- 如果没有线程在等待这个信号量,此函数会累加信号量的值
获取信号量的函数原型如下:
rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time);
参数 |
说明
|
sem
|
信号量对象的句柄
|
time
|
超时时间,单位为系统时钟节拍(
OS Tick
)
|
返回值
|
RT_EOK
:获取信号量成功
RT_ETIMEOUT
:获取信号量超时
RT_ERROR
:获取信号量错误
|
无等待获取信号量的函数原型如下:
rt_err_t rt_sem_trytake(rt_sem_t sem);
参数
|
说明
|
sem
|
信号量的句柄
|
返回值
|
RT_EOK
:获取信号量成功
RT_ETIMEOUT
:获取信号量超时
|
释放信号量的函数原型如下:
rt_err_t rt_sem_release(rt_sem_t sem);
参数
|
说明
|
sem
|
信号量的句柄
|
返回值
|
RT_TRUE
:释放信号量成功,并唤醒了一个等待的线程
RT_EOK
:释放信号量成功
RT_EFULL
:信号量的值已经到达极限 0xffff
|