信号
RT-Thread 信号机制详解
信号 (Signal),又称软中断信号,是软件层面对中断机制的一种模拟。从原理上讲,一个线程接收到信号,类似于处理器接收到中断请求。
1. 信号的工作机制
在 RT-Thread 中,信号用于实现异步通信。POSIX 标准定义了 sigset_t
类型来表示信号集。然而,sigset_t
在不同系统上的定义可能不同。在 RT-Thread 中,sigset_t
被定义为 unsigned long
类型,并命名为 rt_sigset_t
。应用程序可以使用的信号包括 SIGUSR1
(10) 和 SIGUSR2
(12)。
信号本质上是软中断,用于通知线程发生了异步事件,常用于线程间的异常通知和应急处理。线程无需任何操作等待信号到达,事实上,线程也无法预知信号何时到达。线程之间可以通过调用 rt_thread_kill()
函数发送软中断信号。
接收到信号的线程可以对不同的信号采取不同的处理方式,主要分为以下三类:
-
自定义处理函数: 类似于中断处理程序,线程可以为需要处理的信号指定一个处理函数,当信号发生时由该函数来处理。 -
忽略信号: 线程可以选择忽略某个信号,对该信号不做任何处理,如同信号从未发生过。 -
默认处理: 线程可以选择使用系统默认的信号处理方式,即调用系统默认的处理函数 _signal_default_handler()
。

如上图所示,假设线程 1 需要处理信号,它首先安装信号并解除对该信号的阻塞,同时设定该信号的异常处理方式。然后,其他线程可以向线程 1 发送信号,触发线程 1 对该信号的处理。
当信号被传递给线程 1 时,如果线程 1 正处于挂起状态,则会将其状态改为就绪状态,并执行信号处理。如果线程 1 正处于运行状态,则会在当前线程栈的基础上创建新的栈帧空间来执行信号处理。需要注意的是,这会使线程栈的使用量相应增加。
2. 信号的管理方式
对信号的操作主要包括:安装信号、阻塞信号、解除信号阻塞、发送信号和等待信号。信号相关的接口如下:

2.1 安装信号
如果线程需要处理某个信号,则必须先安装该信号。安装信号主要用于确定信号值与线程针对该信号值所采取的操作之间的映射关系,即线程将要处理哪个信号,以及当该信号被传递给线程时,将执行什么操作。可以使用 rt_signal_install()
函数安装信号:
rt_sighandler_t rt_signal_install(int signo, rt_sighandler_t handler);
其中,rt_sighandler_t
是定义信号处理函数的函数指针类型。rt_signal_install()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
signo | 信号值 (只有 SIGUSR1 和 SIGUSR2 可以被用户使用)。 |
handler | 设置对信号值的处理方式。 |
返回值 | 描述 |
SIG_ERR | 错误的信号。 |
安装信号前的 handler 值 | 安装成功。 |
安装信号时,通过设置 handler
参数,可以定义信号的不同处理方式:
-
自定义处理函数: handler
参数指向用户自定义的处理函数,当信号发生时,系统将调用该函数处理信号。 -
忽略信号: 将 handler
参数设置为SIG_IGN
,则忽略该信号,不进行任何处理。 -
默认处理: 将 handler
参数设置为SIG_DFL
,系统将调用默认的处理函数_signal_default_handler()
。
2.2 阻塞信号
信号阻塞可以理解为屏蔽信号。如果某个信号被阻塞,则该信号不会传递给安装此信号的线程,也不会触发软中断处理。可以使用 rt_signal_mask()
函数阻塞信号:
void rt_signal_mask(int signo);
rt_signal_mask()
函数的参数说明如下:
参数 | 描述 |
---|---|
signo | 信号值。 |
2.3 解除信号阻塞
线程可以安装多个信号,使用 rt_signal_unmask()
函数可以解除对某些信号的阻塞。被解除阻塞的信号,发送到线程时会触发线程的软中断。
void rt_signal_unmask(int signo);
rt_signal_unmask()
函数的参数说明如下:
参数 | 描述 |
---|---|
signo | 信号值。 |
2.4 发送信号
当需要进行异常处理时,可以向设定了处理异常的线程发送信号。可以使用 rt_thread_kill()
函数向任何线程发送信号:
int rt_thread_kill(rt_thread_t tid, int sig);
rt_thread_kill()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
tid | 接收信号的线程。 |
sig | 信号值。 |
返回值 | 描述 |
RT_EOK | 发送成功。 |
-RT_EINVAL | 参数错误。 |
2.5 等待信号
可以使用 rt_signal_wait()
函数等待特定信号的到来。如果指定的信号没有到达,线程将会被挂起,直到等待到信号或等待时间超过设定的超时时间。如果接收到了信号,会将指向该信号信息的指针存储在 si
中。
int rt_signal_wait(const rt_sigset_t *set,
rt_siginfo_t[] *si, rt_int32_t timeout);
其中,rt_siginfo_t
是定义信号信息的数据类型。rt_signal_wait()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
set | 指定等待的信号集合。 |
si | 指向存储接收到信号的信息的指针。 |
timeout | 指定的等待时间。 |
返回值 | 描述 |
RT_EOK | 等待到信号。 |
-RT_ETIMEOUT | 超时。 |
-RT_EINVAL | 参数错误。 |
3. 信号应用示例
以下示例演示了如何使用信号进行线程间的异步通信。此示例创建了一个线程。在线程安装信号时,将其处理方式设置为自定义处理,即当接收到信号时调用 thread1_signal_handler()
函数。线程启动并安装信号后,通过 rt_thread_kill()
函数给该线程发送信号。该线程接收到信号后会打印信息。
#include <rtthread.h>
#define THREAD_PRIORITY 25
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5
static rt_thread_t tid1 = RT_NULL;
/* 线程 1 的信号处理函数 */
void thread1_signal_handler(int sig)
{
rt_kprintf("thread1 received signal %d\n", sig);
}
/* 线程 1 的入口函数 */
static void thread1_entry(void *parameter)
{
int cnt = 0;
/* 安装信号 */
rt_signal_install(SIGUSR1, thread1_signal_handler);
rt_signal_unmask(SIGUSR1);
/* 运行 10 次 */
while (cnt < 10)
{
/* 线程 1 采用低优先级运行,一直打印计数值 */
rt_kprintf("thread1 count : %d\n", cnt);
cnt++;
rt_thread_mdelay(100);
}
}
/* 信号示例的初始化 */
int signal_sample(void)
{
/* 创建线程 1 */
tid1 = rt_thread_create("thread1",
thread1_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
rt_thread_mdelay(300);
/* 发送信号 SIGUSR1 给线程 1 */
rt_thread_kill(tid1, SIGUSR1);
return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(signal_sample, signal sample);
运行结果如下:
\ | /
- RT - Thread Operating System
/ | \ 3.1.0 build Aug 24 2018
2006 - 2018 Copyright by rt-thread team
msh >signal_sample
thread1 count : 0
thread1 count : 1
thread1 count : 2
msh >thread1 received signal 10
thread1 count : 3
thread1 count : 4
thread1 count : 5
thread1 count : 6
thread1 count : 7
thread1 count : 8
thread1 count : 9
在示例代码中,线程首先安装信号并解除阻塞,然后发送信号给该线程。线程接收到信号并打印了接收到的信号值 SIGUSR1
(10)。
好的,这次的内容就到这里啦
感谢你的阅读,欢迎点赞、关注、转发
我们,下次再见!