【RT-Thread】信号和信号量

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 被唤醒
  2. 线程 1 先调用信号处理函数
  3. 再继续运行之前的代码
  • 线程 1 如果在就绪状态收到信号
  1. 线程 1 再次运行时,先调用信号处理函数
  2. 再继续运行之前的代码
  • 线程 2 给自己发信号: rt_thread_kill(thread2, SIGUSR1)
  1. rt_thread_kill()函数内部直接调用信号处理函数

信号函数

【RT-Thread】信号和信号量_第1张图片

安装

如果线程需要处理某一个信号,就需要现在线程中安装该信号。
安装信号的函数原型如下:
/* 安装一个信号,返回安装结果。
* 此函数有两个参数,分别为信号值和信号值的处理方法
* 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" 双方并不需要相同,可以用于生产者 - 消费者场合:
  • 生产者为线程 AB,消费者为线程 CD
  • 一开始信号量的计数值为 0,如果线程 CD 想获得信号量,会有两种结果:
  1. 阻塞:买不到东西咱就等等吧,可以定个闹钟(超时时间)
  2. 即刻返回失败:不等
  • 线程 AB 可以生产资源,就是让信号量的计数值增加 1,并且把等待这个
  • 唤醒谁?有两种方法。创建信号量时,可以指定一个参数 flag:
  1. RT_IPC_FLAG_PRIO:表示唤醒优先级最高的等待线程
  2. RT_IPC_FLAG_FIFO:表示唤醒等待时间最长的等待线程

【RT-Thread】信号和信号量_第2张图片

 信号量函数

    在 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-Thread】信号和信号量_第3张图片

创建/初始化

 使用信号量之前,要先创建,得到一个句柄;使用信号量时,要使用句柄来表明使用哪个信号量。

信号量的创建有两种方法:动态分配内存、静态分配内存,

  • 动态分配内存: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() 将释放信号量:
  • 如果有线程在等待这个信号量,此函数不会累加信号量的值,而是直接唤醒等待的线程:
  1. 有多个线程在等待同一个信号量时,谁被唤醒?
  2. 创建信号量时指定参数为 RT_IPC_FLAG_PRIO:表示唤醒优先级最高的等待线
  3. 创建信号量时指定参数为 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

你可能感兴趣的:(rtos,stm32,c语言,单片机,mcu)