RT-Thread版本:4.0.5
MCU型号:STM32F103RCT6(ARM Cortex-M3 内核)
1 消息队列控制块
struct rt_messagequeue
{
struct rt_ipc_object parent; /* 继承ipc对象 */
void *msg_pool; /*指向存放消息的缓冲区的指针 */
rt_uint16_t msg_size; /* 每条消息大小, 单位为字节 */
rt_uint16_t max_msgs; /* 最大消息数量 */
rt_uint16_t entry; /* 队列中已有的消息数 */
void *msg_queue_head; /* 消息链表头:指向第一个有数据的消息块(读数据) */
void *msg_queue_tail; /* 消息链表尾:指向最后一个有数据的消息块(写数据) */
void *msg_queue_free; /* 指向空闲的消息块(初始化时指向最后一个消息块) */
rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */
};
typedef struct rt_messagequeue *rt_mq_t;
数据结构:队列
存储方式:链式
因此,需要有指向自身的指针结构:
struct rt_mq_message
{
struct rt_mq_message *next;
};
2 消息队列工作机制
2.1 传输数据
消息队列能够接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中,用于线程间的消息交换、中断服务程序给线程发送数据(中断服务程序不能接收消息)
RT-Thread 中使用队列数据结构实现线程异步通信工作,具有如下特性:
-
可以允许不同长度(不超过队列节点最大值)的任意类型消息
-
最大消息数量
max_msgs
= 消息缓冲区msg_pool
大小/(每条消息msg_size
大小+4)[sizeof(struct rt_mq_message) = 4
] -
每条消息可容纳的数据大小是一样的(消息队列创建时确定)
-
数据的操作采用先进先出的方法(FIFO):队尾写数据,队头读数据
-
消息链表头
msg_queue_head
指向第一个有数据的消息块(用于读数据),消息链表尾msg_queue_tail
指向最后一个有数据的消息块(用于写数据) -
从消息缓冲区的末尾开始读写数据(消息缓冲区低地址作为队尾,高地址作为队头),
msg_queue_free
指向空闲的消息块 -
紧急消息数据直接写到消息队列的头部
-
mq->msg_queue_free == NULL
表示消息队列已满,mq->entry == 0
表示消息队列已空
使用消息队列传输数据时有两种方法:
- 拷贝:把数据、把变量的值复制进消息队列里
- 引用:把数据、把变量的地址复制进消息队列里
一般使用拷贝方式传输数据。
消息队列操作流程举例如下:
最后线程B取出第一个消息后,该消息块变为空闲消息块,将插入到msg_queue_free
位置,然后修改msg_queue_free
指向此内存块:
msg->next = (struct rt_mq_message *)mq->msg_queue_free;
mq->msg_queue_free = msg;
2.2 阻塞访问
- 发送线程阻塞挂在
mq->suspend_sender_thread
链表上,接收线程阻塞挂在mq->parent->suspend_thread
链表上 - 对于接收线程,当它取消息时消息队列为空:
- 超时时间为0:直接返回
- 超时时间不为0则挂起:
- 从就绪链表移除
- 放入
mq->parent->suspend_thread
链表上(按优先级PRIO或先进先出FIFO顺序插入) - 启动线程内置定时器,计数超时时间
- 发起线程调度
- 被唤醒:
- 其他线程发送消息,且此接收线程位于
mq->parent->suspend_thread
链表头部,则唤醒之(从挂起链表移除,插入到就绪链表中,并停止线程内置定时器) - 定时器超时,唤醒之,并返回-TIMEOUT
- 其他线程发送消息,且此接收线程位于
- 对于发送线程,当它发送消息时消息队列已满时,阻塞访问机制与接收线程相同。
3 消息队列函数接口
3.1 创建与删除
3.1.1 创建消息队列
/**
* @param name mq对象指针
* @param msg_size 消息队列中消息的最大长度 (Unit: Byte).
* @param max_msgs 消息队列中的最大消息数
* @param RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
* @return 成功: rt_mq_t对象, 失败: RT_NULL
* @warning 不能在中断上下文中使用
*/
rt_mq_t rt_mq_create(const char *name,
rt_size_t msg_size,
rt_size_t max_msgs,
rt_uint8_t flag)
{
struct rt_messagequeue *mq;
struct rt_mq_message *head;
register rt_base_t temp;
mq = (rt_mq_t)rt_object_allocate(RT_Object_Class_MessageQueue, name);
if (mq == RT_NULL)
return mq;
mq->parent.parent.flag = flag;
/* 即初始化接收线程挂起链表ipc->suspend_thread */
_ipc_object_init(&(mq->parent));
/* 每条消息大小对齐到RT_ALIGN_SIZE整数倍(默认为4) */
mq->msg_size = RT_ALIGN(msg_size, RT_ALIGN_SIZE);
mq->max_msgs = max_msgs;
/* 分配内存大小 = [消息大小 + 消息头大小] * 消息队列容量 */
mq->msg_pool = RT_KERNEL_MALLOC((mq->msg_size + sizeof(struct rt_mq_message)) * mq->max_msgs);
if (mq->msg_pool == RT_NULL)
{
rt_object_delete(&(mq->parent.parent));
return RT_NULL;
}
mq->msg_queue_head = RT_NULL;
mq->msg_queue_tail = RT_NULL;
mq->msg_queue_free = RT_NULL;
for (temp = 0; temp < mq->max_msgs; temp ++)
{
// 按 消息大小+指针结构大小 分割消息内存池 (从后往前链接消息块)
head = (struct rt_mq_message *)((rt_uint8_t *)mq->msg_pool +
temp * (mq->msg_size + sizeof(struct rt_mq_message)));
head->next = (struct rt_mq_message *)mq->msg_queue_free;
mq->msg_queue_free = head;
}
/* 队列中已有的消息数为0 */
mq->entry = 0;
/* 初始化发送线程挂起链表 */
rt_list_init(&(mq->suspend_sender_thread));
return mq;
}
rt_object_allocate
函数分配消息队列对象大小,并将其插入到内核对象管理链表中- 初始化接收/发送线程的挂起链表,即prev与next均指向自身
- 每条消息大小
mq->msg_size
必须对齐到RT_ALIGN_SIZE
整数倍(默认为4) - 分配消息缓冲区大小
mq->msg_pool
= [每条消息大小 + 消息头大小(指针结构大小)] * 消息队列最大容量 mq->msg_queue_head
与mq->msg_queue_tail
均初始化为0- 按 消息大小+指针结构大小 分割分配的消息缓冲区,后面的消息块指向前面的消息块,
mq->msg_queue_free
指向最后一个消息块,即从后往前链接消息块 - 队列中已有的消息数
mq->entry
初始化为0
3.1.2 删除消息队列
rt_mq_create
创建的消息队列对象大小和消息缓冲区大小,可调用rt_mq_delete
函数删除并释放内存:
/**
* @brief 这个函数将删除一个messagequeue对象并释放内存
* @param mq 指向消息队列对象的指针.
* @return 成功: RT_EOK, 失败: 其他值
* @warning 不能在中断上下文中调用
*/
rt_err_t rt_mq_delete(rt_mq_t mq)
{
/* 恢复所有挂在此链表上的接收线程 */
_ipc_list_resume_all(&(mq->parent.suspend_thread));
/* 恢复所有挂在此链表上的发送线程 */
_ipc_list_resume_all(&(mq->suspend_sender_thread));
/* 释放消息队列内存 */
RT_KERNEL_FREE(mq->msg_pool);
/* 删除消息队列对象并释放消息队列内核对象的内存 */
rt_object_delete(&(mq->parent.parent));
return RT_EOK;
}
- 释放消息队列缓冲区内存&消息队列内核对象的内存
- 唤醒挂在此消息队列链表上的所有线程,将线程错误码设为-RT_ERROR
3.2 初始化与脱离
3.2.1 初始化消息队列
/**
* @brief 初始化静态消息对象对象
* @param mq 指向要初始化的消息队列对象的指针。
* @param name 消息对象名称
* @param msgpool 指向消息队列缓冲区起始地址的指针
* @param msg_size 每条消息大小 (Unit: Byte).
* @param pool_size 预先分配给消息队列的内存空间大小
* @param RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
*
* @return 成功: RT_EOK, 失败: 其他值
*
* @warning 仅能在线程中调用
*/
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)
{
struct rt_mq_message *head;
register rt_base_t temp;
/* initialize object */
rt_object_init(&(mq->parent.parent), RT_Object_Class_MessageQueue, name);
mq->parent.parent.flag = flag;
/* 初始化接收线程挂起链表ipc->suspend_thread */
_ipc_object_init(&(mq->parent));
mq->msg_pool = msgpool;
/* 每条消息大小对齐到RT_ALIGN_SIZE整数倍(默认为4) */
mq->msg_size = RT_ALIGN(msg_size, RT_ALIGN_SIZE);
mq->max_msgs = pool_size / (mq->msg_size + sizeof(struct rt_mq_message));
mq->msg_queue_head = RT_NULL;
mq->msg_queue_tail = RT_NULL;
mq->msg_queue_free = RT_NULL;
for (temp = 0; temp < mq->max_msgs; temp ++)
{
// 按 消息大小+指针结构大小 分割消息内存池 (从后往前链接消息块)
head = (struct rt_mq_message *)((rt_uint8_t *)mq->msg_pool +
temp * (mq->msg_size + sizeof(struct rt_mq_message)));
head->next = (struct rt_mq_message *)mq->msg_queue_free;
mq->msg_queue_free = head;
}
/* 队列中已有的消息数为0 */
mq->entry = 0;
/* 初始化发送线程挂起链表 */
rt_list_init(&(mq->suspend_sender_thread));
return RT_EOK;
}
rt_object_init
函数将消息队列插入到内核对象管理链表中,并标记为静态对象(type | RT_Object_Class_Static)- 消息队列最大容量
mq->max_msgs
= 分配消息缓冲区大小mq->msg_pool
/ [每条消息大小 + 消息头大小(指针结构大小)] - 消息队列缓冲区与消息队列对象是提前初始化好的,其他与动态创建函数无区别
3.2.2 脱离消息队列
rt_mq_init
初始化的消息队列,可以调用rt_mq_detach
函数将其从内核管理链表中脱离:
rt_err_t rt_mq_detach(rt_mq_t mq)
{
/* 恢复所有挂在此链表上的接收线程 */
_ipc_list_resume_all(&mq->parent.suspend_thread);
/* 恢复所有挂在此链表上的发送线程 */
_ipc_list_resume_all(&(mq->suspend_sender_thread));
/* 将消息队列对象从内核对象管理链表中脱离 */
rt_object_detach(&(mq->parent.parent));
return RT_EOK;
}
与删除消息队列对象一样,也会唤醒挂在此消息队列链表上的所有线程,将线程错误码设为-RT_ERROR
3.3 发送消息
3.3.1 等待方式发送
消息队列对象先从空闲消息链表上取下一个空闲消息块,把线程或者中断服务程序发送的消息内容复制到消息块上,然后把该消息块挂到消息队列的尾部。当且仅当空闲消息链表上有可用的空闲消息块时,发送者才能
成功发送消息,否则根据超时时间决定错误返回还是阻塞挂起。
/**
* @brief 这个函数将向消息队列对象发送一条消息。如果消息队列上挂起了一个线程,则该线程将被恢复。
* @param mq 指向要发送消息队列对象的指针
* @param buffer 消息内容
* @param size 消息长度(Unit: Byte).
* @param timeout 超时时间 (unit: an OS tick).
* @return 成功: RT_EOK, 失败: 其他值
* @warning 这个函数可以在中断上下文和线程中调用
*/
rt_err_t rt_mq_send_wait(rt_mq_t mq,
const void *buffer,
rt_size_t size,
rt_int32_t timeout)
{
register rt_ubase_t temp;
struct rt_mq_message *msg;
rt_uint32_t tick_delta;
struct rt_thread *thread;
/* 判断消息的大小 */
if (size > mq->msg_size)
return -RT_ERROR;
tick_delta = 0;
thread = rt_thread_self();
temp = rt_hw_interrupt_disable();
/* 获取一个空闲消息链表,必须有一个空闲链表节点 */
msg = (struct rt_mq_message *)mq->msg_queue_free;
/* 非阻塞调用 */
if (msg == RT_NULL && timeout == 0)
{
rt_hw_interrupt_enable(temp);
return -RT_EFULL;
}
/* 消息队列已满 */
while ((msg == RT_NULL)
{
/* 设置线程错误码 */
thread->error = RT_EOK;
/* 不等待,直接返回 */
if (timeout == 0)
{
rt_hw_interrupt_enable(temp);
return -RT_EFULL;
}
/* 挂起当前线程 */
_ipc_list_suspend(&(mq->suspend_sender_thread),
thread,
mq->parent.parent.flag);
/* 有超时时间,启动定时器 */
if (timeout > 0)
{
/* 获取tick */
tick_delta = rt_tick_get();
/* 设置超时时间并启动定时器 */
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&timeout);
rt_timer_start(&(thread->thread_timer));
}
rt_hw_interrupt_enable(temp);
rt_schedule();
if (thread->error != RT_EOK) // 超时返回-RT_TIMEOUT
{
return thread->error;
}
temp = rt_hw_interrupt_disable();
/* 如果不是永久等待,则重新计算超时等待时间 */
if (timeout > 0)
{
tick_delta = rt_tick_get() - tick_delta;
timeout -= tick_delta;
if (timeout < 0)
timeout = 0;
}
}
/* 移动空闲链表指针,指向下一个节点 */
mq->msg_queue_free = msg->next;
rt_hw_interrupt_enable(temp);
/* 这个消息是新的链表尾部, 其next指针需要置为NULL */
msg->next = RT_NULL;
/* 拷贝数据, 因为空闲节点有消息头,所以其真正存放消息的地址是msg + 1, 即移动4个字节 */
rt_memcpy(msg + 1, buffer, size);
temp = rt_hw_interrupt_disable();
/* 将消息挂载到消息队列尾部 */
if (mq->msg_queue_tail != RT_NULL)
{
/* 若队尾非空,即已有数据,则直接将发送的消息挂载到尾部链表后面 */
((struct rt_mq_message *)mq->msg_queue_tail)->next = msg;
}
/* 重置消息队列尾指针指向新消息节点 */
mq->msg_queue_tail = msg;
/* 若头指针为空, 则将头指针指向新消息节点 */
if (mq->msg_queue_head == RT_NULL)
mq->msg_queue_head = msg;
if(mq->entry < RT_MQ_ENTRY_MAX)
{
/* 消息数量+1 */
mq->entry ++;
}
else
{
rt_hw_interrupt_enable(temp);
return -RT_EFULL; /* 溢出 */
}
/* 若有接收线程挂起,唤醒之 */
if (!rt_list_isempty(&mq->parent.suspend_thread))
{
_ipc_list_resume(&(mq->parent.suspend_thread));
rt_hw_interrupt_enable(temp);
rt_schedule();
return RT_EOK;
}
rt_hw_interrupt_enable(temp);
return RT_EOK;
}
- 若发送的消息大小>
mq->msg_size
直接返回-RT_ERROR; - 若消息队列已满(
mq->msg_queue_free == NULL
),根据超时时间 判断阻塞挂起或直接返回 - 消息队列未满正常发送流程:
- 新消息msg存放消息块为
mq->msg_queue_free
- 将
mq->msg_queue_free
指向新消息节点的下一节点msg->next
- 将新消息节点msg的next指针置为NULL,因为新节点成为了尾节点
- 拷贝数据, 因为消息节点都一个消息头(链队的指针域),所以其真正存放消息的地址是msg + 1, 即移动4个字节
- 若
mq->msg_queue_tail
不为空,则说明消息队列中已有数据,则直接将新消息挂在尾指针的下一节点 - 将尾指针
mq->msg_queue_tail
指向新的队尾msg - 若
mq->msg_queue_head
为空,则将头指针指向新节点(说明此消息为消息队列中第一条) - 当前消息数量
mq->entry
+1(若变量溢出则返回-RT_EFUL) - 若有接收线程挂起,则唤醒消息队列接收线程挂起链表中第一个线程
- 新消息msg存放消息块为
消息发送流程如下图所示(F: mq->msg_queue_free, t: mq->msg_queue_tail, h: mq->msg_queue_head):
3.3.2 无等待发送
rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size)
{
return rt_mq_send_wait(mq, buffer, size, 0);
}
将rt_mq_send_wait
函数超时时间设为0
3.3.3 发送紧急消息
/**
* @brief 这个函数将向消息队列对象发送一条紧急消息
* @note 在发送紧急消息时,消息被放置在消息队列的头部,以便接收方可以首先收到紧急消息。
* @param mq 指向要发送消息队列对象的指针
* @param buffer 消息内容
* @param size 单条消息长度(Unit: Byte).
* @return 成功: RT_EOK, 失败: 其他值
*/
rt_err_t rt_mq_urgent(rt_mq_t mq, const void *buffer, rt_size_t size)
{
register rt_ubase_t temp;
struct rt_mq_message *msg;
/* 超过单条消息大小 */
if (size > mq->msg_size)
return -RT_ERROR;
temp = rt_hw_interrupt_disable();
/* 获取空闲消息块 */
msg = (struct rt_mq_message *)mq->msg_queue_free;
/* 消息队列已满 */
if (msg == RT_NULL)
{
rt_hw_interrupt_enable(temp);
return -RT_EFULL;
}
/* 移动空闲链表指针,指向下一个节点 */
mq->msg_queue_free = msg->next;
rt_hw_interrupt_enable(temp);
/* 拷贝数据, 因为空闲节点有消息头,所以其真正存放消息的地址是msg + 1, 即移动4个字节 */
rt_memcpy(msg + 1, buffer, size);
temp = rt_hw_interrupt_disable();
/* 将新消息挂在队头 */
msg->next = (struct rt_mq_message *)mq->msg_queue_head;
mq->msg_queue_head = msg;
/* 队尾指针为空 */
if (mq->msg_queue_tail == RT_NULL)
mq->msg_queue_tail = msg;
if(mq->entry < RT_MQ_ENTRY_MAX)
{
/* 消息数量+1 */
mq->entry ++;
}
else
{
rt_hw_interrupt_enable(temp);
return -RT_EFULL; /* 溢出 */
}
/* 唤醒挂起线程 */
if (!rt_list_isempty(&mq->parent.suspend_thread))
{
_ipc_list_resume(&(mq->parent.suspend_thread));
rt_hw_interrupt_enable(temp);
rt_schedule();
return RT_EOK;
}
rt_hw_interrupt_enable(temp);
return RT_EOK;
}
rt_mq_urgent属于无阻塞调用,不会阻塞当前发送线程,它会将新消息插入到队列头部,这样接收线程从队头取数据时,可以直接获取新消息,具体流程:
- 消息队列已满,返回-RT_EFULL
- 消息队列未满:
mq->msg_queue_free
指向下一个消息块- 拷贝数据
- 将新消息插入到队头,同时修改
mq->msg_queue_head
指向新消息节点 - 若此时队尾指针为空(
mq->msg_queue_tail == RT_NULL
),将队尾指针指向新消息(说明此消息为消息队列中第一条) - 当前消息数量
mq->entry
+1(若变量溢出则返回-RT_EFUL) - 若有接收线程挂起,则唤醒消息队列接收线程挂起链表中第一个线程
紧急消息发送示例(续上图):
3.4 接收消息
/**
* @brief 这个函数将从消息队列对象接收消息,如果消息队列对象中没有消息,线程将等待指定的时间。
* @param mq 指向要发送消息队列对象的指针
* @param buffer 接收消息缓冲区
* @param size 单条消息长度(Unit: Byte).
* @param timeout 超时时间 (unit: an OS tick).
* @return 成功: RT_EOK, 失败: 其他值
*/
rt_err_t rt_mq_recv(rt_mq_t mq,
void *buffer,
rt_size_t size,
rt_int32_t timeout)
{
struct rt_thread *thread;
register rt_ubase_t temp;
struct rt_mq_message *msg;
rt_uint32_t tick_delta;
tick_delta = 0;
thread = rt_thread_self();
temp = rt_hw_interrupt_disable();
/* 对于无阻塞调用 */
if (mq->entry == 0 && timeout == 0)
{
rt_hw_interrupt_enable(temp);
return -RT_ETIMEOUT;
}
/* 消息队列为空 */
while (mq->entry == 0)
{
RT_DEBUG_IN_THREAD_CONTEXT;
/* 重置线程中的错误码 */
thread->error = RT_EOK;
/* 不等待 */
if (timeout == 0)
{
rt_hw_interrupt_enable(temp);
thread->error = -RT_ETIMEOUT;
return -RT_ETIMEOUT;
}
/* 挂起当前线程 */
_ipc_list_suspend(&(mq->parent.suspend_thread),
thread,
mq->parent.parent.flag);
/* 有等待时间,启动线程计时器 */
if (timeout > 0)
{
/* 获取systick 定时器时间 */
tick_delta = rt_tick_get();
/* 重置线程计时器的超时时间并启动它 */
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&timeout);
rt_timer_start(&(thread->thread_timer));
}
rt_hw_interrupt_enable(temp);
rt_schedule();
if (thread->error != RT_EOK)
{
return thread->error;
}
temp = rt_hw_interrupt_disable();
/* *如果它不是永远等待,然后重新计算超时时间 */
if (timeout > 0)
{
tick_delta = rt_tick_get() - tick_delta;
timeout -= tick_delta;
if (timeout < 0)
timeout = 0;
}
}
/* 获取消息 */
msg = (struct rt_mq_message *)mq->msg_queue_head;
/* 将队头指针移动到下一节点 */
mq->msg_queue_head = msg->next;
/* 到达队尾,队尾指针设为NULL */
if (mq->msg_queue_tail == msg)
mq->msg_queue_tail = RT_NULL;
/* 消息数量-1 */
if(mq->entry > 0)
{
mq->entry --;
}
rt_hw_interrupt_enable(temp);
/* 拷贝消息到指定存储地址 */
rt_memcpy(buffer, msg + 1, size > mq->msg_size ? mq->msg_size : size);
temp = rt_hw_interrupt_disable();
/* 移到空闲链表指向已被读取的消息块 */
msg->next = (struct rt_mq_message *)mq->msg_queue_free;
mq->msg_queue_free = msg;
/* 唤醒被挂起的发送线程 */
if (!rt_list_isempty(&(mq->suspend_sender_thread)))
{
_ipc_list_resume(&(mq->suspend_sender_thread));
rt_hw_interrupt_enable(temp);
rt_schedule();
return RT_EOK;
}
rt_hw_interrupt_enable(temp);
return RT_EOK;
}
- 若消息队列已空,根据超时时间 判断阻塞挂起或直接返回
- 若消息队列非空:
- 从队头
mq->msg_queue_head
获取消息 - 将队头指针移动到下一节点
- 若当前消息已到达队尾,将队尾指针置空(说明此消息为消息队列中最后一条)
- 消息数量
mq->entry
-1 - 拷贝消息到指定存储地址
- 移到空闲链表
mq->msg_queue_free
指向已被读取的消息块,这样可以保证消息队列的循环利用,而不会导致头链表指针移动到队列尾部时没有可用的消息节点。 - 若有发生线程被阻塞,则唤醒发送线程挂起链表中的第一个线程
- 从队头
消息接收示例(续上图):
F:mq->msg_queue_free
指向的即为被读取的消息块,该消息块next指针指向的位置是mq->msg_queue_free
先前指向的位置。
同时需要注意被读取的消息块虽然被视为空闲消息块,但其中的数据并没有被清除,下次写消息时会将其直接覆盖。
4 应用示例
创建两个线程,thread1用于接收数据,thread2用于发送数据;同时创建一个静态消息队列对象,用于发送和接收消息。
#define my_printf(fmt, ...) rt_kprintf("[%u]"fmt"\n", rt_tick_get(), ##__VA_ARGS__)
#define THREAD_STACK_SIZE 512
#define THREAD_PRIORITY 20
#define THREAD_TIMESLICE 5
typedef enum
{
common_msg,
urgent_msg,
}id_t;
typedef struct
{
id_t id;
char* str;
}msg_data_t;
msg_data_t msg_send_data[4] =
{
{
common_msg, "hello!"},
{
common_msg, "how are you?"},
{
common_msg, "send over!"},
{
urgent_msg, "SOS!"}
};
static struct rt_messagequeue mq; // 静态消息队列对象
static char msg_pool[512]; // 静态消息队列缓冲区
static rt_thread_t thread1;
static rt_thread_t thread2;
static void thread1_entry(void* param)
{
msg_data_t buf;
while(1)
{
if (RT_EOK == rt_mq_recv(&mq, &buf, sizeof(msg_data_t), RT_WAITING_FOREVER))
{
if (buf.id == common_msg)
{
my_printf("[%s] common_msg: %s", thread1->name, buf.str);
if (strcmp(buf.str, "send over!") == 0)
break;
}
else if (buf.id == urgent_msg)
my_printf("[%s] urgent_msg: %s", thread1->name, buf.str);
}
rt_thread_mdelay(200);
}
my_printf("[%s] detach mq", thread1->name);
rt_mq_detach(&mq); // 将消息队列对象脱离
}
static void thread2_entry(void* param)
{
rt_uint8_t cnt = 0;
rt_err_t result;
while(cnt++ < 10)
{
if (cnt & 0x1) // 奇数
result = rt_mq_send(&mq, &msg_send_data[0], sizeof(msg_data_t));
else // 偶数
result = rt_mq_send(&mq, &msg_send_data[1], sizeof(msg_data_t));
if (result != RT_EOK)
my_printf("[%s] msg send error!", thread2->name);
else
my_printf("[%s] send message: %s", thread2->name,
((msg_data_t *)((int *)mq.msg_queue_tail + 1))->str);
rt_thread_mdelay(100);
}
rt_mq_urgent(&mq, &msg_send_data[3], sizeof(msg_data_t)); // 发送紧急消息
rt_thread_mdelay(300); // 等待线程1收到紧急消息
rt_mq_send(&mq, &msg_send_data[2], sizeof(msg_data_t)); // 发送结束消息
my_printf("[%s] message queue stop send, thread2 quit", thread2->name);
}
int msgq_sample(void){
rt_err_t result;
/* 静态初始化邮箱 */
result = rt_mq_init(&mq,
"test_mq",
&msg_pool[0],
sizeof(msg_data_t),
sizeof(msg_pool),
RT_IPC_FLAG_FIFO);
if (result != RT_EOK)
return -RT_ERROR;
thread1 = rt_thread_create("thread1",
thread1_entry,
RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY,
THREAD_TIMESLICE - 1);
if (thread1 == RT_NULL)
return -RT_ERROR;
else
rt_thread_startup(thread1);
thread2 = rt_thread_create("thread2",
thread2_entry,
RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY,
THREAD_TIMESLICE);
if (thread2 == RT_NULL)
return -RT_ERROR;
else
rt_thread_startup(thread2);
return RT_EOK;
}
每次发送数据,队尾指针都会指向新发送的数据,即可通过(msg_data_t *)((int *)mq.msg_queue_tail + 1))->str
获取新发送数据的字符串,这里将mq.msg_queue_tail 转换成int*,是因为struct rt_mq_message *
也恰好是指向4字节变量的指针,最后+1是因为每条消息还需要存储包含自身结构的指针,如下图所示:
因此(int *)mq.msg_queue_tail + 1
才是指向消息的数据域。
打印log信息如下:
[3][thread2] send message: hello!
[6][thread1] common_msg: hello!
[106][thread2] send message: how are you?
[209][thread1] common_msg: how are you?
[213][thread2] send message: hello!
[316][thread2] send message: how are you?
[413][thread1] common_msg: hello!
[420][thread2] send message: hello!
[523][thread2] send message: how are you?
[616][thread1] common_msg: how are you?
[627][thread2] send message: hello!
[730][thread2] send message: how are you?
[820][thread1] common_msg: hello!
[834][thread2] send message: hello!
[937][thread2] send message: how are you?
[1023][thread1] common_msg: how are you?
[1227][thread1] urgent_msg: SOS!
[1341][thread2] message queue stop send, thread2 quit
[1430][thread1] common_msg: hello!
[1633][thread1] common_msg: how are you?
[1837][thread1] common_msg: hello!
[2040][thread1] common_msg: how are you?
[2244][thread1] common_msg: send over!
[2247][thread1] detach mq
可以看到最后发送的一条紧急消息SOS,优先打印了出来。
5 总结
- 消息队列可以发送任意字节的数据(最大65535),但是每条消息的大小在初始化就已确定,之后不可以更改
- 消息队列采用链式存储的队列结构,将队列缓冲区切割成
mq->max_msgs
个大小为mq->msg_size + sizeof(struct rt_mq_message)
字节的节点,其中struct rt_mq_message
为指向自身的结构,即链队的next指针,最后一个节点(最高地址)作为队头,从后往前链接消息节点 - 每条消息前4个字节存储指针自身结构的指针(指针域),后面才开始存储数据(数据域)
- 发送消息的线程阻塞挂起在
mq->suspend_sender_thread
链表上,接收消息的线程阻塞挂起在mq->parent.suspend_thread
链表上 - 发送紧急消息可以直接在队头插入数据,同时修改队头指针指向此紧急数据,下次从队头读取数据时,先取走它
END