[RT_Thread]内核中线程间的同步与通信(事件集与邮箱的记录)
目录
写在前面
- 本次写线程间的通信,熟悉与stm32裸机开发时,经常有很多问题,比如:
- 在多个外设的使用同一的PIN引脚时怎么避免冲突?
- 突然来一个事件能否及时得到处理?
- 一系列事件发生需要有一定逻辑条件,如严格的先后顺序,几个事件同时发送引发其他事件发生,如何同步?
以前我只能做许许多多的标记来进行判断,比如互斥,就得定义一个全局变量,当一个资源使用时修改全局变量的值表示封锁,事件的中断不能访问。这种做标记显得逻辑繁杂且乱。
于是,试着上操作系统看能不能把问题变简洁。
本次记录
事件集
新建的一个事件集可以有8个事件标记,每个事件标记相当于一个信号量,可实现一对多,多对多的事件同步。
三个线程的同步运行 F1->F2->F3
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-10-14 RT-Thread first version
*/
#include <rtthread.h>
#include "board.h"
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "app.h"
rt_thread_t th0;
rt_thread_t cap_th;
rt_thread_t th3;
rt_event_t even1;
#define EVENT_F1 (1<<0)
#define EVENT_F2 (1<<1)
#define EVENT_F3 (1<<2)
void cap_entry(void * param){
rt_uint32_t e;
while(1){
rt_event_recv(even1,
EVENT_F2,
RT_EVENT_FLAG_AND|RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER,
&e);
rt_kprintf("th1\r\n");
rt_event_send(even1, EVENT_F3);
rt_thread_mdelay(1000);
}
}
void th0_entry(void * para){
rt_uint32_t e;
static u8 Flag=0;
while(1){
rt_event_recv(even1,
EVENT_F1,
RT_EVENT_FLAG_AND|RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER,
&e);
if(Flag){
rt_pin_write(LEDSTATE, PIN_LOW);
Flag=0;
}
else
{
rt_pin_write(LEDSTATE, PIN_HIGH);
Flag=1;
}
rt_kprintf(" th2\r\n");
rt_event_send(even1, EVENT_F2);
rt_thread_mdelay(1000);
}
}
void th3_entry(void * param){
while(1){
rt_event_recv(even1,
EVENT_F3,
RT_EVENT_FLAG_AND| RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER,
RT_NULL);
rt_kprintf("th3\r\n");
rt_event_send(even1, EVENT_F1);
rt_thread_mdelay(1000);
}
}
int main(void)
{
MY_PIN_INIT();
th0=rt_thread_create("state", th0_entry, NULL, 1024, 20, 10);
cap_th=rt_thread_create("cap", cap_entry, NULL, 1012, 20, 10);
th3=rt_thread_create("th3", th3_entry, NULL, 1024, 20, 10);
even1 =rt_event_create("even1", RT_IPC_FLAG_FIFO);
rt_thread_startup(cap_th);
rt_thread_startup(th3);
rt_thread_startup(th0);
rt_event_send(even1, EVENT_F1);
return RT_EOK;
}
终端运行
邮箱
rt_mailbox
所定义的句柄
msg_bool为邮件所存放的位置。
size为邮件的个数,类型为uint32_t,32位4个字节。
动态创建邮箱 rt_mb_create:分配邮箱空间,初始化等待邮箱事件挂起线程链表,初始化邮箱参数(size=邮件个数,entry=当前邮件个数,out_offset=接受(消费邮件位置)in_offset=输入(生产邮件的位置))
见源码:
/**
* This function will create a mailbox object from system resource
*
* @param name the name of mailbox
* @param size the size of mailbox
* @param flag the flag of mailbox
*
* @return the created mailbox, RT_NULL on error happen
*/
rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag)
{
rt_mailbox_t mb;
RT_DEBUG_NOT_IN_INTERRUPT;
/* allocate object */
mb = (rt_mailbox_t)rt_object_allocate(RT_Object_Class_MailBox, name);
if (mb == RT_NULL)
return mb;
/* set parent */
mb->parent.parent.flag = flag;
/* initialize ipc object */
rt_ipc_object_init(&(mb->parent));
/* initialize mailbox */
mb->size = size;
mb->msg_pool = (rt_ubase_t *)RT_KERNEL_MALLOC(mb->size * sizeof(rt_ubase_t));
if (mb->msg_pool == RT_NULL)
{
/* delete mailbox object */
rt_object_delete(&(mb->parent.parent));
return RT_NULL;
}
mb->entry = 0;
mb->in_offset = 0;
mb->out_offset = 0;
/* initialize an additional list of sender suspend thread */
rt_list_init(&(mb->suspend_sender_thread));
return mb;
}
接受邮件
rt_mb_recv:若邮件中有邮件则读(从out_offset读),没有则把当前线程挂起,若设置了等待时间则开启一个定时器等待,若有线程在被邮箱阻塞则唤醒被阻塞的线程。
/**
* This function will receive a mail from mailbox object, if there is no mail
* in mailbox object, the thread shall wait for a specified time.
*
* @param mb the mailbox object
* @param value the received mail will be saved in
* @param timeout the waiting time
*
* @return the error code
*/
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout)
{
struct rt_thread *thread;
register rt_ubase_t temp;
rt_uint32_t tick_delta;
/* parameter check */
RT_ASSERT(mb != RT_NULL);
RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
/* initialize delta tick */
tick_delta = 0;
/* get current thread */
thread = rt_thread_self();
RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mb->parent.parent)));
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* for non-blocking call */
if (mb->entry == 0 && timeout == 0)
{
rt_hw_interrupt_enable(temp);
return -RT_ETIMEOUT;
}
/* mailbox is empty */
while (mb->entry == 0)
{
/* reset error number in thread */
thread->error = RT_EOK;
/* no waiting, return timeout */
if (timeout == 0)
{
/* enable interrupt */
rt_hw_interrupt_enable(temp);
thread->error = -RT_ETIMEOUT;
return -RT_ETIMEOUT;
}
RT_DEBUG_IN_THREAD_CONTEXT;
/* suspend current thread */
rt_ipc_list_suspend(&(mb->parent.suspend_thread),
thread,
mb->parent.parent.flag);
/* has waiting time, start thread timer */
if (timeout > 0)
{
/* get the start tick of timer */
tick_delta = rt_tick_get();
RT_DEBUG_LOG(RT_DEBUG_IPC, ("mb_recv: start timer of thread:%s\n",
thread->name));
/* reset the timeout of thread timer and start it */
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&timeout);
rt_timer_start(&(thread->thread_timer));
}
/* enable interrupt */
rt_hw_interrupt_enable(temp);
/* re-schedule */
rt_schedule();
/* resume from suspend state */
if (thread->error != RT_EOK)
{
/* return error */
return thread->error;
}
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* if it's not waiting forever and then re-calculate timeout tick */
if (timeout > 0)
{
tick_delta = rt_tick_get() - tick_delta;
timeout -= tick_delta;
if (timeout < 0)
timeout = 0;
}
}
/* fill ptr */
*value = mb->msg_pool[mb->out_offset];
/* increase output offset */
++ mb->out_offset;
if (mb->out_offset >= mb->size)
mb->out_offset = 0;
/* decrease message entry */
if(mb->entry > 0)
{
mb->entry --;
}
/* resume suspended thread */
if (!rt_list_isempty(&(mb->suspend_sender_thread)))
{
rt_ipc_list_resume(&(mb->suspend_sender_thread));
/* enable interrupt */
rt_hw_interrupt_enable(temp);
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mb->parent.parent)));
rt_schedule();
return RT_EOK;
}
/* enable interrupt */
rt_hw_interrupt_enable(temp);
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mb->parent.parent)));
return RT_EOK;
}
发送邮件
rt_mb_send()
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value)
{
return rt_mb_send_wait(mb, value, 0);
}
rt_mb_send_wait()
超时时间指若邮箱已经满,若在超时时间内继续等待还没有空间剩余则返回。
发送邮件,在邮箱未满时:offset:邮件数自加1,从in_offset位置输入发送邮件的数据,随即in_offset指向下一个生产位置
/**
* This function will send a mail to mailbox object. If the mailbox is full,
* current thread will be suspended until timeout.
*
* @param mb the mailbox object
* @param value the mail
* @param timeout the waiting time
*
* @return the error code
*/
rt_err_t rt_mb_send_wait(rt_mailbox_t mb,
rt_ubase_t value,
rt_int32_t timeout)
{
struct rt_thread *thread;
register rt_ubase_t temp;
rt_uint32_t tick_delta;
/* parameter check */
RT_ASSERT(mb != RT_NULL);
RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
/* initialize delta tick */
tick_delta = 0;
/* get current thread */
thread = rt_thread_self();
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mb->parent.parent)));
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* for non-blocking call */
if (mb->entry == mb->size && timeout == 0)
{
rt_hw_interrupt_enable(temp);
return -RT_EFULL;
}
/* mailbox is full */
while (mb->entry == mb->size)
{
/* reset error number in thread */
thread->error = RT_EOK;
/* no waiting, return timeout */
if (timeout == 0)
{
/* enable interrupt */
rt_hw_interrupt_enable(temp);
return -RT_EFULL;
}
RT_DEBUG_IN_THREAD_CONTEXT;
/* suspend current thread */
rt_ipc_list_suspend(&(mb->suspend_sender_thread),
thread,
mb->parent.parent.flag);
/* has waiting time, start thread timer */
if (timeout > 0)
{
/* get the start tick of timer */
tick_delta = rt_tick_get();
RT_DEBUG_LOG(RT_DEBUG_IPC, ("mb_send_wait: start timer of thread:%s\n",
thread->name));
/* reset the timeout of thread timer and start it */
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&timeout);
rt_timer_start(&(thread->thread_timer));
}
/* enable interrupt */
rt_hw_interrupt_enable(temp);
/* re-schedule */
rt_schedule();
/* resume from suspend state */
if (thread->error != RT_EOK)
{
/* return error */
return thread->error;
}
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* if it's not waiting forever and then re-calculate timeout tick */
if (timeout > 0)
{
tick_delta = rt_tick_get() - tick_delta;
timeout -= tick_delta;
if (timeout < 0)
timeout = 0;
}
}
/* set ptr */
mb->msg_pool[mb->in_offset] = value;
/* increase input offset */
++ mb->in_offset;
if (mb->in_offset >= mb->size)
mb->in_offset = 0;
if(mb->entry < RT_MB_ENTRY_MAX)
{
/* increase message entry */
mb->entry ++;
}
else
{
rt_hw_interrupt_enable(temp); /* enable interrupt */
return -RT_EFULL; /* value overflowed */
}
/* resume suspended thread */
if (!rt_list_isempty(&mb->parent.suspend_thread))
{
rt_ipc_list_resume(&(mb->parent.suspend_thread));
/* enable interrupt */
rt_hw_interrupt_enable(temp);
rt_schedule();
return RT_EOK;
}
/* enable interrupt */
rt_hw_interrupt_enable(temp);
return RT_EOK;
}
邮件案例代码
官方的案例用的是发送二级指针的形式:这种好处在于可以得到不定长的数据,指针的类型在32位系统中恰恰是4个字节,拿到指针就意味着拿到了数据引用,前提是数据所在内存没有被释放,否则就是非法地址引用了,一般还是在发送时动态开辟内存,在接受时释放指针所指向内存,若指针所指向内存又放了指针(比如邮件传送一个结构体指针,结构体指针里定义了一个数组的地址),则注意从下级往上级逐级释放内存,否则将会一直占用内存。
我的案例,简单的发送一个4个字节的整形,主线程发送,3个子线程接受,表现出了三个子线程从邮件中接受数据是不同步的。
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-10-14 RT-Thread first version
*/
#include <rtthread.h>
#include "board.h"
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "app.h"
rt_thread_t th0;
rt_thread_t th1;
rt_thread_t th2;
rt_mailbox_t mb1;
rt_err_t ret;
void th0_entry(void * param){
u16 str;
while(1){
if(rt_mb_recv(mb1, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK)rt_kprintf("th0:%d\r\n",str);
if(str == 0) rt_kprintf("th0 -- 0\r\n");
rt_thread_mdelay(1000);
}
}
void th1_entry(void * para){
u16 str;
static u8 Flag=0;
while(1){
if( rt_mb_recv(mb1, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK)rt_kprintf("th1:%d\r\n",str);
if(str == 1) rt_kprintf(" th1 --1\r\n");
if(Flag){
rt_pin_write(LEDSTATE, PIN_LOW);
Flag=0;
}
else
{
rt_pin_write(LEDSTATE, PIN_HIGH);
Flag=1;
}
rt_thread_mdelay(800);
}
}
void th2_entry(void * param){
u16 str;
while(1){
if( rt_mb_recv(mb1, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK)rt_kprintf("th2:%d\r\n",str);
if(str == 2) rt_kprintf("th2--2\r\n");
rt_thread_mdelay(500);
}
}
int main(void)
{
//初始化GPIO引脚
MY_PIN_INIT();
th0=rt_thread_create("th0", th0_entry, NULL, 1024, 20, 10);
th1=rt_thread_create("th1", th1_entry, NULL, 1012, 20, 10);
th2=rt_thread_create("th2", th2_entry, NULL, 1024, 20, 10);
//开启三个线程
rt_thread_startup(th0);
rt_thread_startup(th1);
rt_thread_startup(th2);
//创建邮箱
mb1=rt_mb_create("mb1",20,RT_IPC_FLAG_FIFO);
u8 t=0;
while(1){
if(t%3 == 0){
ret= rt_mb_send(mb1, (rt_uint32_t)0);
// if(ret == RT_EOK)
// rt_kprintf("send th0 success\r\n");
}
else if(t%3 == 1){
ret= rt_mb_send(mb1, (rt_uint32_t)1);
// if(ret == RT_EOK)
// rt_kprintf("send th1 success\r\n");
}
else if(t%3 == 2){
ret= rt_mb_send(mb1, (rt_uint32_t)2);
// if(ret == RT_EOK)
// rt_kprintf("send th2 success\r\n");
}
t++;
rt_kprintf("mb1 inoffset:%d\r\n",mb1->in_offset);
rt_kprintf("mb1 outoffset:%d\r\n",mb1->out_offset);
rt_thread_mdelay(500);
}
return RT_EOK;
}
运行结果:
小结
邮件的最大邮件数是size,当前邮件数是entry,当前生产邮件的位置是in_offset,当前接(消耗)邮件数是out_offset,邮件的存放缓存 rt_ubase_t *msg_pool,是一个循环的环型数组。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?