没志青年
发布于 2025-07-28 / 20 阅读
0

RT-Thread 线程间通信

数据传递

邮箱

特性:

  • 每封邮件固定为 4 字节,

    • 可以直接传输 32 位无符号数

    • 恰好能容纳一个指针,可传递指向缓冲区的地址。但是不能显式的指定数据的长度,在使用的时候一定要注意越界的问题。

  • 多个线程可等待同一个邮箱。

邮箱控制块:

struct rt_mailbox
{
    struct rt_ipc_object parent;

    rt_uint32_t* msg_pool;                /* 邮箱缓冲区的开始地址 */
    rt_uint16_t size;                     /* 邮箱缓冲区的大小     */

    rt_uint16_t entry;                    /* 邮箱中邮件的数目     */
    rt_uint16_t in_offset, out_offset;    /* 邮箱缓冲的进出指针   */
    rt_list_t suspend_sender_thread;      /* 发送线程的挂起等待队列 */
};
typedef struct rt_mailbox* rt_mailbox_t;

动态创建与删除

rt_mailbox_t rt_mb_create (const char* name, rt_size_t size, rt_uint8_t flag)

rt_err_t rt_mb_delete (rt_mailbox_t mb)

静态初始化与脱离

发送邮件

无等待方式发送:

rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value)

等待方式发送:

rt_err_t rt_mb_send_wait (rt_mailbox_t mb,
                      rt_uint32_t value,
                      rt_int32_t timeout);

发送紧急邮件:

rt_err_t rt_mb_urgent (rt_mailbox_t mb, rt_ubase_t value);


接收邮件

rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout);

若传递的是地址,需要把这个数字强制转换为指针:

rt_uint32_t received_data;
rt_mb_recv(&mb, &received_data, RT_WAITING_FOREVER);

数据类型 *received_ptr = (数据类型 *)received_data;

消息队列

适合传输大块的不固定长度的数据。

指针传递:

  • 要保证指针越界的问题

  • 要保证指向的内存的生命周期

特性:

  • FIFO 先进先出队列

  • 长度有限制,单个数据大小有限制

  • 可以把紧急的数据写到消息队列的头部

动态创建与删除

rt_mq_t rt_mq_create ( const char* name,     // 队列名称
                       rt_size_t msg_size,   // 消息最大大小
                       rt_size_t max_msgs,   // 队列长度
                       rt_uint8_t flag );    // 唤醒标志

flags:

rt_err_t rt_mq_delete(rt_mq_t mq)

删除消息队列时,如果有线程在等待该队列,则内核会先唤醒这些线程(线程返回值 是 - RT_ERROR),然后再释放消息队列使用的内存,最后删除消息队列对象。

静态初始化与脱离

发送消息

无等待发送:

rt_err_t rt_mq_send (rt_mq_t mq, void* buffer, rt_size_t size);

等待发送:

rt_err_t rt_mq_send_wait(rt_mq_t     mq,
                         const void *buffer,
                         rt_size_t   size,
                         rt_int32_t  timeout);

发送紧急消息:

向队列头插入消息。

rt_err_t rt_mq_urgent(rt_mq_t mq, void* buffer, rt_size_t size);

接收消息

rt_ssize_t rt_mq_recv (rt_mq_t mq, void* buffer,
                    rt_size_t size, rt_int32_t timeout);


线程同步

信号量

简单的通知机制。

分为二进制信号量和计数信号量。

信号量控制块:

struct rt_semaphore
{
   struct rt_ipc_object parent;  /* 继承自 ipc_object 类 */
   rt_uint16_t value;            /* 信号量的值 */
};
/* rt_sem_t 是指向 semaphore 结构体的指针类型 */
typedef struct rt_semaphore* rt_sem_t;

动态创建与删除

 rt_sem_t rt_sem_create(const char *name,
                        rt_uint32_t value,
                        rt_uint8_t flag);

flag:

rt_err_t rt_sem_delete(rt_sem_t sem);

静态初始化与脱离

rt_err_t rt_sem_init(rt_sem_t       sem,
                    const char     *name,
                    rt_uint32_t    value,
                    rt_uint8_t     flag)

rt_err_t rt_sem_detach(rt_sem_t sem);

P 操作

等待获取信号量:

rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time);

time:

非阻塞方式获取信号量:

rt_err_t rt_sem_trytake(rt_sem_t sem);

V 操作

rt_err_t rt_sem_release(rt_sem_t sem);

释放信号量可以唤醒等待该信号量的线程。


事件集

通过设置标志位传递线程状态。

即 FreeRTOS 中的事件组。

特性:

  • 一个事件使用 1 bit 表示

  • 一对多,多对一、多对多的通信

  • 可选择是否清除事件

  • 若发送的事件,接收方还未

线程控制块中存储线程的事件数据。

struct rt_thread
{
 ......
##if defined(RT_USING_EVENT)
 /* thread event */
 rt_uint32_t event_set;
 rt_uint8_t event_info;
##endif
 ......
}
  • event_set:想等待哪些事件。事件使用(1<<30) | (1<<0) 表示事件 0 和 30

  • event_info:事件等待条件。

    • RT_EVENT_FLAG_AND:全部事件发生

    • RT_EVENT_FLAG_OR:任一事件发生

    • RT_EVENT_FLAG_CLEAR:事件到达时,是否清除事件

动态创建和删除

rt_event_t rt_event_create(const char* name, rt_uint8_t flag);

flag:唤醒策略,RT_IPC_FLAG_FIFO 先进先出 或 RT_IPC_FLAG_PRIO 高优先级

rt_err_t rt_event_delete(rt_event_t event);

静态初始化与脱离

rt_err_t rt_event_init(rt_event_t event, const char* name, rt_uint8_t flag);

rt_err_t rt_event_detach(rt_event_t event);

发送事件

rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);

接收事件

rt_err_t rt_event_recv ( rt_event_t event,
                         rt_uint32_t set,
                         rt_uint8_t option,
                         rt_int32_t timeout,
                         rt_uint32_t* recved);

  • set:期待哪些事件:一般使用宏定义定义事件,使用 | 多个事件

  • option:接收选项 :RT_EVENT_FLAG_OR 或 RT_EVENT_FLAG_AND  选择清除重置事件标志位:RT_EVENT_FLAG_CLEAR

  • timeout:指定超时时间

  • recved:指向接收到的事件

临界资源保护

互斥量

和 FreeRTOS 的互斥量一样:

  • 只能由持有的线程释放

  • 解决了优先级反转问题

  • 支持递归使用

动态创建和删除

rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag);

rt_err_t rt_mutex_delete (rt_mutex_t mutex);

静态初始化和脱离

rt_err_t rt_mutex_init (rt_mutex_t mutex, const char* name, rt_uint8_t flag);


rt_err_t rt_mutex_detach (rt_mutex_t mutex);

P 操作

rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time);

无等待方式获取信号量:

rt_err_t rt_mutex_trytake(rt_mutex_t mutex);

V 操作

rt_err_t rt_mutex_release(rt_mutex_t mutex);

信号通知

效率比较高。

应用程序能够使用的信号为 SIGUSR1(10)和 SIGUSR2(12)

信号的使用场景?

信号到达时,如果不是在运行态会怎么处理?

安装信号

rt_sighandler_t rt_signal_install(int signo, rt_sighandler_t[] handler);

将信号和线程绑定,这样线程才能接收信号并处理。

  • signo:信号

  • handler:信号处理函数

信号阻塞与解除

void rt_signal_mask(int signo);

void rt_signal_unmask(int signo);

发送信号

int rt_thread_kill(rt_thread_t tid, int sig);

等待信号

int rt_signal_wait(const rt_sigset_t *set,
                        rt_siginfo_t[] *si, rt_int32_t timeout);

示例

不阻塞等待,异步通知。

#include <rtthread.h>

#define THREAD_PRIORITY         25
#define THREAD_STACK_SIZE       512
#define THREAD_TIMESLICE        5

static rt_thread_t tid1 = RT_NULL;


void thread1_signal_handler(int sig)
{
    rt_kprintf("thread1 received signal %d\n", sig);
}


static void thread1_entry(void *parameter)
{
    int cnt = 0;

    /* 安装信号 */
    rt_signal_install(SIGUSR1, thread1_signal_handler);
    rt_signal_unmask(SIGUSR1);
  
    while (cnt < 10)
    {
        rt_kprintf("thread1 count : %d\n", cnt);

        cnt++;
        rt_thread_mdelay(100);
    }
}


int signal_sample(void)
{
    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_CMD_EXPORT(signal_sample, signal sample);