Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
2024-RSOC
【2024-RSOC】RT-Thread进程间通信方式对比【Day3】
发布于 2024-07-24 23:18:12 浏览:322
订阅该版
[tocm] # 【2024-RSOC】RT-Thread进程间通信方式对比【Day3】 ## RT-Thread官方仓库使用过程记录 记录一下RT-Thread官方仓库使用星火一号开发板配置rt软件包基本功能的使用过程,预先需要安装env环境与openocd工具 在`rt-thread\bsp\stm32\stm32f407-rt-spark`目录下右键选项中点击`ConEmu Here`打开env环境,开始输入以下代码初始化 ``` menuconfig scons --target=vsc >然后可以将配置好的.vscode下c_cpp_properties.json、launch.json文件复制到当前生成的.vscode目录下 scons -j12 >就可以测试是否可以正常编译 scons --dist >将文件、库等全部打包到了当前目录下的dist目录下 ``` 然后我们就可以进入`dist\project`目录使用其中的标准工程了,同样我们在当前目录下右键选项中点击`ConEmu Here`打开env环境,输入以下命令,通过menuconfig添加一些示例 ``` code . menuconfig > RT-Thread online packages ---> >miscellaneous packages ---> >samples: kernel and components samples ---> >[*] a kernel_samples package for rt-thread ---> >[*] [kernel] thread [*] [kernel] semphore [*] [kernel] mutex [*] [kernel] mailbox [*] [kernel] event [*] [kernel] message queue pkgs --update scons -j12 ``` ## 一、线程间同步 线程的同步核心思想:**在访问临界区的时候只允许一个 (或一类) 线程运行。** 进入 / 退出临界区的方式: > 1)调用 rt_hw_interrupt_disable() 进入临界区,调用 rt_hw_interrupt_enable() 退出临界区; > > 2)调用 rt_enter_critical() 进入临界区,调用 rt_exit_critical() 退出临界区。 ### 1 信号量(semaphore) 信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。每个信号量对象都有一个信号量值和一个线程等待队列,信号量的值对应了信号量对象的实例数目、资源数目。 信号量通常分为两类:二值信号量和计数信号量。 - **二值信号量**:信号量值只能是0或1。它通常用于保护临界区,确保任何时候只有一个线程可以访问共享资源。当一个线程获得信号量时,信号量值设为0,其他试图获取信号量的线程将被阻塞直到信号量可用。当持有信号量的线程释放信号量时,信号量值恢复为1,等待队列中的一个线程(根据调度策略)将被唤醒并获得信号量。 - **计数信号量**:信号量值可以是任意非负整数。它们用于跟踪可用资源的数量。例如,如果系统中有5个可用的硬件设备,那么计数信号量的初始值可以设置为5。每当一个线程占用一个设备时,信号量值减1;当线程不再使用设备并释放信号量时,信号量值加1。 #### 数据结构解析 信号量控制块是操作系统用于管理信号量的一个数据结构,由结构体`struct rt_semaphore` 表示。另外一种表达方式 `rt_sem_t`,表示的是信号量的句柄,在 C 语言中的实现是指向信号量控制块的指针。 ```c struct rt_semaphore { struct rt_ipc_object parent; /**< inherit from ipc_object */ rt_uint16_t value; /**< value of semaphore. */ rt_uint16_t max_value; struct rt_spinlock spinlock; }; typedef struct rt_semaphore *rt_sem_t; ``` 这个结构体包含了信号量的当前值、最大值、一个自旋锁用于保护内部数据结构,以及一个从`struct rt_ipc_object`继承的成员,后者又包含了一个线程挂起队列。如下所示: ```c struct rt_ipc_object { struct rt_object parent; /**< inherit from rt_object */ rt_list_t suspend_thread; /**< threads pended on this resource */ }; ``` ```c struct rt_list_node { struct rt_list_node *next; /**< point to next node. */ struct rt_list_node *prev; /**< point to prev node. */ }; typedef struct rt_list_node rt_list_t; /**< Type for lists. */ ``` #### 相关API接口 ![06sem_ops.png](https://oss-club.rt-thread.org/uploads/20240724/3b314a91970ba84b30154c37ffc99ad3.png) ```c rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag) 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_take(rt_sem_t sem, rt_int32_t time) rt_err_t rt_sem_trytake(rt_sem_t sem) rt_err_t rt_sem_release(rt_sem_t sem) rt_err_t rt_sem_delete(rt_sem_t sem) ``` 在`rt_sem_init`函数源代码中有这样一段说明说明:For the static semaphore object, its memory space is allocated by the compiler during compiling, and shall placed on the read-write data segment or on the uninitialized data segment. By contrast, the rt_sem_create() function will allocate memory space automatically and initialize the semaphore. 对于静态信号量对象,它的内存空间在编译时期就被编译器分配出来,放在读写数据段或未初始化数据段上,只需在使用前对它进行初始化即可。静态初始化函数一般以`init`结尾,此时不能使用`delete`函数删除,而应该使用`detach`脱离信号量。 flag只能取以下两个值: **RT_IPC_FLAG_PRIO** The pending threads will queue in order of priority.属于非实时调度方式,除非应用程序非常在意先来后到,并且你清楚地明白所有涉及到该信号量的线程都将会变为非实时线程,方可使用 **RT_IPC_FLAG_FIFO** The pending threads will queue in the first-in-first-out method (also known as first-come-first-served (FCFS) scheduling strategy). `rt_sem_trytake(rt_sem_t sem)`与 `rt_sem_take(sem, RT_WAITING_NO)` 的作用相同,即当线程申请的信号量资源实例不可用的时候,它不会等待在该信号量上,而是直接返回 - RT_ETIMEOUT。 #### 信号量的使用场合 - 线程同步 - ~~锁~~(二值信号量来保护临界区,使用信号量保护临界区会导致无界优先级反转的问题) - 中断与线程同步 - 资源计数 ### 2 互斥量(mutex) 互斥量是一种更高级的同步机制,相比于信号量,它提供了更复杂的锁定行为,特别设计来解决多线程环境下的资源独占问题。互斥量不仅保证了同一时刻只有一个线程可以访问共享资源,而且还支持递归锁定,即一个线程可以多次锁定同一个互斥量而不引起死锁。 拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。 ```c struct rt_mutex { struct rt_ipc_object parent; /**< inherit from ipc_object */ rt_uint8_t ceiling_priority; /**< the priority ceiling of mutexe */ rt_uint8_t priority; /**< the maximal priority for pending thread */ rt_uint8_t hold; /**< numbers of thread hold the mutex */ rt_uint8_t reserved; /**< reserved field */ struct rt_thread *owner; /**< current owner of mutex */ rt_list_t taken_list; /**< the object list taken by thread */ struct rt_spinlock spinlock; }; typedef struct rt_mutex *rt_mutex_t; ``` - `parent`: 继承自`struct rt_ipc_object`,包含互斥量的通用IPC信息,如挂起线程的链表。 - `ceiling_priority`: 互斥量的优先级天花板,用于优先级继承协议,防止优先级翻转。 - `priority`: 最大等待线程的优先级,用于优化调度。 - `hold`: 记录当前线程递归锁定互斥量的次数。 - `reserved`: 保留字段,供未来扩展使用。 - `owner`: 当前持有互斥量的线程。 - `taken_list`: 由线程持有的对象链表。 - `spinlock`: 用于保护互斥量内部数据结构的自旋锁。 #### 相关API接口 ![06mutex_ops.png](https://oss-club.rt-thread.org/uploads/20240724/55d99b780eaa64d8b5e5ecce889d2087.png) ```c rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag) rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time) rt_err_t rt_mutex_trytake(rt_mutex_t mutex) rt_err_t rt_mutex_release(rt_mutex_t mutex) rt_err_t rt_mutex_detach(rt_mutex_t mutex) rt_err_t rt_mutex_delete(rt_mutex_t mutex) ``` #### 使用场景 互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量永远都处于开锁的状态,而被线程持有的时候则立刻转为闭锁的状态。 互斥量最适合以下场景: 1. **递归锁定**:如果一个线程可能需要多次锁定同一个互斥量,互斥量的递归特性可以防止因重复锁定而造成的死锁。 2. **防止优先级翻转**:在多线程系统中,低优先级线程持有互斥量时,高优先级线程无法抢占,这可能导致优先级翻转。互斥量通过优先级继承机制解决了这个问题,即持有互斥量的线程暂时提升到最高等待线程的优先级,确保高优先级线程不会被低优先级线程阻塞。 #### 注意事项 互斥量不能在中断服务例程中使用,这是因为中断服务例程需要快速响应和执行,而互斥量的锁定和解锁操作可能涉及线程的上下文切换,这在中断上下文中是不允许的。此外,互斥量的锁定和解锁必须成对出现,否则可能导致资源泄露或死锁。 ### 3 事件集(event) 事件集是RT-Thread中另一种重要的同步机制,它允许线程等待一组事件的发生。 可以实现一对多,多对多的同步。即一个线程与多个事件的关系可设置为:其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件。这种多个事件的集合可以用一个 32 位无符号整型变量来表示,变量的每一位代表一个事件,线程通过 “逻辑与” 或“逻辑或”将一个或多个事件关联起来,形成事件组合。事件的 “逻辑或” 也称为是独立型同步,指的是线程与任何事件之一发生同步;事件 “逻辑与” 也称为是关联型同步,指的是线程与若干事件都发生同步。 #### 数据结构解析 ```c struct rt_event { struct rt_ipc_object parent; /**< inherit from ipc_object */ rt_uint32_t set; /**< event set */ struct rt_spinlock spinlock; }; typedef struct rt_event *rt_event_t; ``` - `parent`: 继承自`struct rt_ipc_object`,用于管理事件集的通用IPC信息。 - `set`: 一个32位无符号整型变量,用于存储当前激活的事件集合。 - `spinlock`: 自旋锁,用于保护事件集内部数据结构免受并发访问的影响。 #### 相关API接口 ![06event_ops.png](https://oss-club.rt-thread.org/uploads/20240724/37407cc5e897c56359c0c2df10a735f8.png) ```c rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag) rt_event_t rt_event_create(const char *name, rt_uint8_t flag) 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) rt_err_t rt_event_delete(rt_event_t event) rt_err_t rt_event_detach(rt_event_t event) ``` #### 小案例 下面是一个简单的案例,展示如何使用事件集来同步三个线程,其中两个线程分别监控两个按钮的按下事件,而第三个线程只有在两个事件都接收到后才会执行打印和亮灯操作。 ```c #include
#include
#include
#ifndef RT_USING_NANO #include
#endif /* RT_USING_NANO */ #include "mydefine.h" #define BUTTON_A_EVENT (1 << 0) #define BUTTON_B_EVENT (1 << 1) #define LIGHT_EVENT (BUTTON_A_EVENT | BUTTON_B_EVENT) // 定义事件集 static rt_event_t button_events = RT_NULL; // LED线程栈 static char led_thread_stack[1024]; struct rt_thread led_thread; // LED线程入口函数 static void led_thread_entry(void *parameter) { rt_kprintf("LED thread running...\n"); while (1) { // 等待两个按钮事件的组合 rt_err_t result = rt_event_recv(button_events, BUTTON_A_EVENT | BUTTON_B_EVENT, RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, RT_NULL); if (result == RT_EOK) { // 两个按钮事件都收到,亮灯 rt_kprintf("Both buttons pressed, turning on the light.\n"); rt_pin_write(GPIO_LED_R, PIN_HIGH); rt_thread_mdelay(1000); rt_pin_write(GPIO_LED_R, PIN_LOW); } } } // 按键A线程栈 static char button_a_thread_stack[1024]; struct rt_thread button_a_thread; // 按键A线程入口函数 static void button_a_thread_entry(void *parameter) { while (1) { // 判断按键B是否按下 if (rt_pin_read(KEY_PINR) == PIN_LOW) { //消抖 rt_thread_mdelay(100); if (rt_pin_read(KEY_PINR) == PIN_LOW) { rt_kprintf("Button A pressed.\n"); rt_event_send(button_events, BUTTON_A_EVENT); } } rt_thread_mdelay(20); } } // 按键B线程栈 static char button_b_thread_stack[1024]; struct rt_thread button_b_thread; // 按键B线程入口函数 static void button_b_thread_entry(void *parameter) { while (1) { // 判断按键B是否按下 if (rt_pin_read(KEY_PINR) == PIN_LOW) { //消抖 rt_thread_mdelay(100); if (rt_pin_read(KEY_PINR) == PIN_LOW) { rt_kprintf("Button B pressed.\n"); rt_event_send(button_events, BUTTON_B_EVENT); } } rt_thread_mdelay(20); } } int key_event() { // 创建事件集 button_events = rt_event_create("button_events", RT_IPC_FLAG_PRIO); // 初始化LED线程 rt_thread_init(&led_thread, "led_thread", led_thread_entry, RT_NULL, led_thread_stack, sizeof(led_thread_stack), 20, 10); rt_thread_startup(&led_thread); // 初始化按键A线程 rt_thread_init(&button_a_thread, "button_a_thread", button_a_thread_entry, RT_NULL, button_a_thread_stack, sizeof(button_a_thread_stack), 25, 10); rt_thread_startup(&button_a_thread); // 初始化按键B线程 rt_thread_init(&button_b_thread, "button_b_thread", button_b_thread_entry, RT_NULL, button_b_thread_stack, sizeof(button_b_thread_stack), 25, 10); rt_thread_startup(&button_b_thread); return 0; } /* 导出到 msh 命令列表中 */ MSH_CMD_EXPORT(key_event, key event sample); ``` 实验现象如下: 当开发板烧录程序后,在终端输入我们预设好的msh命令key_event即可进入event测试程序,由于led任务优先级最高它预先打印出任务开始运行的字样,先按下key1后按下key2,任务1的事件被成功触发,led灯点亮1S后熄灭。 ``` \ | / - RT - Thread Operating System / | \ 5.2.0 build Jul 24 2024 14:55:56 2006 - 2024 Copyright by RT-Thread team msh >key key_event key_semaphore_sample msh >key_event msh >LED thread running... Button B pressed. Button B pressed. Button A pressed. Both buttons pressed, turning on the light. ``` ## 二、线程间通信 ### 1 邮箱(mailbox) RT-Thread 操作系统的邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针)。 **非阻塞方式的邮件发送过程能够安全的应用于中断服务中**,是线程、中断服务、定时器向线程发送消息的有效手段。 #### 邮箱数据结构 ```c struct rt_mailbox { struct rt_ipc_object parent; /**< inherit from ipc_object */ rt_ubase_t *msg_pool; /**< start address of message buffer */ rt_uint16_t size; /**< size of message pool */ rt_uint16_t entry; /**< index of messages in msg_pool */ rt_uint16_t in_offset; /**< input offset of the message buffer */ rt_uint16_t out_offset; /**< output offset of the message buffer */ rt_list_t suspend_sender_thread; /**< sender thread suspended on this mailbox */ struct rt_spinlock spinlock; }; typedef struct rt_mailbox *rt_mailbox_t; ``` #### 相关API接口 ![07mb_ops.png](https://oss-club.rt-thread.org/uploads/20240724/46140f1668d2c79be8864b50077a8540.png) ```c rt_err_t rt_mb_init(rt_mailbox_t mb, const char *name, void *msgpool, rt_size_t size, rt_uint8_t flag) rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag) rt_err_t rt_mb_send_wait(rt_mailbox_t mb, rt_ubase_t value, rt_int32_t timeout) rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value) rt_err_t rt_mb_send_interruptible(rt_mailbox_t mb, rt_ubase_t value) rt_err_t rt_mb_send_killable(rt_mailbox_t mb, rt_ubase_t value) 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_ubase_t *value, rt_int32_t timeout) rt_err_t rt_mb_recv_interruptible(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout) rt_err_t rt_mb_delete(rt_mailbox_t mb) rt_err_t rt_mb_detach(rt_mailbox_t mb) ``` 删除邮箱时,如果有线程被挂起在该邮箱对象上,内核先唤醒挂起在该邮箱上的所有线程(线程返回值是 -RT_ERROR),然后再释放邮箱使用的内存,最后删除邮箱对象。 邮箱是一种简单的线程间消息传递方式,特点是开销比较低,效率较高。 在 RT-Thread 操作系统的实现中能够一次传递一个 4 字节大小的邮件,并且邮箱具备一定的存储功能,能够缓存一定数量的邮件数 (邮件数由创建、初始化邮箱时指定的容量决定)。由于在 32 系统上 4 字节的内容恰好可以放置一个指针,因此当需要在线程间传递比较大的消息时,可以把指向一个缓冲区的指针作为邮件发送到邮箱中。 ### 消息队列(messagequeue) 消息队列能够接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中。 线程或中断服务例程可以将一条或多条消息放入消息队列中。一个或多个线程也可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常将先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即**先进先出原则 (FIFO**)。 ![07msg_work.png](https://oss-club.rt-thread.org/uploads/20240724/302e1421d16088743779776a7159cde9.png) ```c struct rt_messagequeue { struct rt_ipc_object parent; /**< inherit from ipc_object */ void *msg_pool; /**< start address of message queue */ rt_uint16_t msg_size; /**< message size of each message */ rt_uint16_t max_msgs; /**< max number of messages */ rt_uint16_t entry; /**< index of messages in the queue */ void *msg_queue_head; /**< list head */ void *msg_queue_tail; /**< list tail */ void *msg_queue_free; /**< pointer indicated the free node of queue */ rt_list_t suspend_sender_thread; /**< sender thread suspended on this message queue */ struct rt_spinlock spinlock; }; typedef struct rt_messagequeue *rt_mq_t; ``` #### 相关API接口 ![07msg_ops.png](https://oss-club.rt-thread.org/uploads/20240724/b01f0b259c5d2fd7b3d8f9a579572588.png) ```c rt_err_t rt_mq_init(rt_mq_t mq, const char *name, void *msgpool, rt_size_t msg_size, rt_size_t pool_size, rt_uint8_t flag) rt_err_t rt_mq_detach(rt_mq_t mq) rt_mq_t rt_mq_create(const char *name, rt_size_t msg_size, rt_size_t max_msgs, rt_uint8_t flag) rt_err_t rt_mq_delete(rt_mq_t mq) 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_send(rt_mq_t mq, const void *buffer, rt_size_t size) rt_err_t rt_mq_send_interruptible(rt_mq_t mq, const void *buffer, rt_size_t size) rt_err_t rt_mq_send_killable(rt_mq_t mq, const void *buffer, rt_size_t size) rt_err_t rt_mq_urgent(rt_mq_t mq, const 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) rt_ssize_t rt_mq_recv_interruptible(rt_mq_t mq, void *buffer, rt_size_t size, rt_int32_t timeout) rt_ssize_t rt_mq_recv_killable(rt_mq_t mq, void *buffer, rt_size_t size, rt_int32_t timeout) ``` #### 示例 使用动态方式创建两个线程和消息队列,线程1向线程2发送递增的消息,每隔5次发送一次紧急消息。 ```c #include
#define THREAD_PRIORITY 25 #define THREAD_STACK_SIZE 512 #define THREAD_TIMESLICE 5 /* 消息队列控制块 */ static rt_mq_t mq_handle; static rt_thread_t tid1 = RT_NULL; /* 线程1入口函数 */ static void thread1_entry(void *parameter) { char buf = 0; rt_uint8_t cnt = 0; while (1) { /* 从消息队列中接收消息 */ #if (RTTHREAD_VERSION >= RT_VERSION_CHECK(5, 0, 1)) if (rt_mq_recv(mq_handle, &buf, sizeof(buf), RT_WAITING_FOREVER) > 0) #else if (rt_mq_recv(mq_handle, &buf, sizeof(buf), RT_WAITING_FOREVER) == RT_EOK) #endif { rt_kprintf("thread1: recv msg from msg queue, the content:%c\n", buf); if (cnt == 19) { break; } } /* 延时50ms */ cnt++; rt_thread_mdelay(50); } rt_kprintf("thread1: delete mq \n"); rt_mq_delete(mq_handle); } static rt_thread_t tid2 = RT_NULL; /* 线程2入口 */ static void thread2_entry(void *parameter) { int result; char buf = 'A'; rt_uint8_t cnt = 0; while (1) { if (cnt % 5 == 0)/* 每发送5次消息发送一次紧急消息 */ { /* 发送紧急消息到消息队列中 */ result = rt_mq_urgent(mq_handle, &buf, 1); if (result != RT_EOK) { rt_kprintf("rt_mq_urgent ERR\n"); } else { rt_kprintf("thread2: send urgent message - %c\n", buf); } } else if (cnt >= 20)/* 发送20次消息之后退出 */ { rt_kprintf("message queue stop send, thread2 quit\n"); break; } else { /* 发送消息到消息队列中 */ result = rt_mq_send(mq_handle, &buf, 1); if (result != RT_EOK) { rt_kprintf("rt_mq_send ERR\n"); } rt_kprintf("thread2: send message - %c\n", buf); } buf++; cnt++; /* 延时5ms */ rt_thread_mdelay(12); } } /* 消息队列案例 */ int my_msgq_sample(void) { /* 初始化消息队列 */ mq_handle = rt_mq_create("mq", 1, 20, RT_IPC_FLAG_FIFO); if (mq_handle == RT_NULL) { rt_kprintf("init message queue failed.\n"); return -1; } /* 创建线程1,名称是thread1,入口是thread1_entry*/ tid1 = rt_thread_create("thread1", thread1_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); #ifdef RT_USING_SMP /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ rt_thread_control(tid1, RT_THREAD_CTRL_BIND_CPU, (void*)0); #endif /* 如果获得线程控制块,启动这个线程 */ if (tid1 != RT_NULL) rt_thread_startup(tid1); /* 创建线程2,名称是thread2,入口是thread2_entry*/ tid2 = rt_thread_create("thread2", thread2_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); #ifdef RT_USING_SMP /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ rt_thread_control(tid2, RT_THREAD_CTRL_BIND_CPU, (void*)0); #endif /* 如果获得线程控制块,启动这个线程 */ if (tid2 != RT_NULL) rt_thread_startup(tid2); return 0; } /* 导出到 msh 命令列表中 */ MSH_CMD_EXPORT(my_msgq_sample, my msgq sample); ``` ![Day3队列案例输出.png](https://oss-club.rt-thread.org/uploads/20240724/8173827f624fbd7940776de674dee87a.png) 线程 1 会从消息队列中收取消息;线程 2 定时给消息队列发送普通消息和紧急消息。线程1先接收了消息A,由于线程2插入紧急消息F,后面线程1接收到的下一个消息即F。 ### 调试经验 以消息队列的案例作为调试对象为例,我们首先可以在线程1入口函数与线程2入口函数的循环中打上端点,并且由于我们所监控的是消息队列,所以我们可以将消息队列的内存池与消息变量自身(静态定义)加入WATCH窗口便于我们观测,这里由于我是动态定义的的消息队列故而没有显式的自定义内存池,只有一个消息变量指针,这里我也将消息变量指针放入watch窗口 ![day3调试.png](https://oss-club.rt-thread.org/uploads/20240724/4f113bcea5654bcf1a52d4e3ae783c25.png.webp) 可以看到watch窗口中就出现了消息队列句柄的内部数据结构,其中的一些参数正是我们预定义好的(一开始会显示null,我这个是刚刚运行到了这里,队列初始化成功了才会显示) 好,我们开车,正式开始调试: 连接板子,下载程序后点击全速运行。然后通过串口软件连接板子,向终端输入预设命令,即可跳到我们在线程1和线程2所设的断点了。 ``` \ | / - RT - Thread Operating System / | \ 5.2.0 build Jul 24 2024 14:55:56 2006 - 2024 Copyright by RT-Thread team msh >my my_msgq_sample msh >my_msgq_sample msh > ``` 此时程序状态如下所示: ![day3调试2.png](https://oss-club.rt-thread.org/uploads/20240724/bf4ceec7c4ac91351a72d3e3e8bbff90.png.webp) 同时我们也可以使用鼠标移动到变量上方悬停一会,就可以显示出表量地址当前值等信息。 通过step over一步步调试运行配合观察变量的值,以及线程断点就可以大致弄清楚程序运行的逻辑。
2
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
luyism
用舍由时,行藏在我
文章
4
回答
0
被采纳
0
关注TA
发私信
相关文章
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
ulog
C++_cpp
at_device
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
a1012112796
13
个答案
2
次被采纳
张世争
9
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
5
次点赞
RTT_逍遥
1
篇文章
2
次点赞
ThinkCode
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部