rt-thread消息队列
定义
消息队列定义
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 */
};
typedef struct rt_messagequeue *rt_mq_t;
消息头,就是单向链表
struct rt_mq_message
{
struct rt_mq_message *next;
};
rt_mq_create函数
参数:名字,消息大小,最多消息数max_msgs,flag
flag只支持按优先级或先进先出两种。在函数体中首先定义消息队列指针,父类为rt_ipc_object类型。分配消息队列内存,爷爷类(rt_object)flag赋值为传入参数,调用父类初始化(其实就是初始化ipc对象链表为空,链表指向挂起线程,通过这个链表,可以找到对应的阻塞线程的控制块,从而能恢复线程)。传入参数赋值给消息队列成员,根据这两个参数,分配内存空间,内存大小为消息大小*消息数。然后使用循环,将内存池里的各个块以消息链表的形式串起来。
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;//单向链表
rt_base_t temp;
RT_ASSERT((flag == RT_IPC_FLAG_FIFO) || (flag == RT_IPC_FLAG_PRIO));
RT_DEBUG_NOT_IN_INTERRUPT;
/* allocate object */
mq = (rt_mq_t)rt_object_allocate(RT_Object_Class_MessageQueue, name);
if (mq == RT_NULL)
return mq;
/* set parent */
mq->parent.parent.flag = flag;
/* initialize ipc object */
_ipc_object_init(&(mq->parent));
/* initialize message queue */
/* get correct message size */
mq->msg_size = RT_ALIGN(msg_size, RT_ALIGN_SIZE);
mq->max_msgs = max_msgs;
/* allocate message pool 分配内存池 */
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;
}
/* initialize message list 消息头和消息尾初始化为空,暂不使用*/
mq->msg_queue_head = RT_NULL;
mq->msg_queue_tail = RT_NULL;
/* initialize message empty list 将内存池各个块串起来行程单链表,最终让控制块mq的空闲消息指针指向最后1块*/
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;
}
/* the initial entry is zero 消息个数初始化为0 */
mq->entry = 0;
/* initialize an additional list of sender suspend thread 控制块的链表初始化为单节点*/
rt_list_init(&(mq->suspend_sender_thread));
return mq;
}
RTM_EXPORT(rt_mq_create);
初始化中申请了内存池,将内存池划分为很多个小块,每个小块均由消息头+消息内容组成。
循环执行:
在循环中,形成链表:
rt_mq_creat执行完成后,消息队列对象mq的msg_queue_free成员,表示了内存池最后1块的地址,认为最后1块是空闲消息 ,内存池的所有块串起来形成单向链表。最后1块指向倒数第2块。创建后,形成了空闲链表,控制块中的空闲变量msg_queue_free表示空闲链表的表头,为内存池中的最后一个内存块。
rt_mq_delete函数
消息队列删除主要做的事情为:1.恢复由于访问队列而阻塞的线程;2.释放内存池;3.删除object对象
rt_mq_send函数
线程或者中断服务程序都可以给消息队列发送消息。send函数嵌套send wait,直接调用,只是超时时间设置为0。
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_err_t rt_mq_send_wait(rt_mq_t mq,
const void *buffer,
rt_size_t size,
rt_int32_t timeout)
{
rt_base_t level;
struct rt_mq_message *msg;//单向链表
rt_uint32_t tick_delta;
struct rt_thread *thread;
/* parameter check */
RT_ASSERT(mq != RT_NULL);
RT_ASSERT(rt_object_get_type(&mq->parent.parent) == RT_Object_Class_MessageQueue);
RT_ASSERT(buffer != RT_NULL);
RT_ASSERT(size != 0);
/* current context checking */
RT_DEBUG_SCHEDULER_AVAILABLE(timeout != 0);
/* greater than one message size 如果发送的消息超过创建时设置的消息大小,则直接退出*/
if (size > mq->msg_size)
return -RT_ERROR;
/* initialize delta tick */
tick_delta = 0;
/* get current thread 获取当前线程(消息发送方)*/
thread = rt_thread_self();
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mq->parent.parent)));
/* disable interrupt */
level = rt_hw_interrupt_disable();
/* get a free list, there must be an empty item 根据消息控制块中的free地址获取空闲消息内存块地址*/
msg = (struct rt_mq_message *)mq->msg_queue_free;
/* for non-blocking call */
if (msg == RT_NULL && timeout == 0)
{
/* enable interrupt */
rt_hw_interrupt_enable(level);
return -RT_EFULL;
}
/* message queue is full 循环判断:如果没有空闲消息内存块则直接退出*/
while ((msg = (struct rt_mq_message *)mq->msg_queue_free) == RT_NULL)
{
/* reset error number in thread */
thread->error = RT_EOK;
/* no waiting, return timeout */
if (timeout == 0)
{
/* enable interrupt */
rt_hw_interrupt_enable(level);
return -RT_EFULL;
}
/* suspend current thread */
_ipc_list_suspend(&(mq->suspend_sender_thread),
thread,
mq->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, ("mq_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(level);
/* re-schedule */
rt_schedule();
/* resume from suspend state */
if (thread->error != RT_EOK)
{
/* return error */
return thread->error;
}
/* disable interrupt */
level = 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;
}
}
/* move free list pointer 创建的时候,msg->next是倒数第2个内存块*/
mq->msg_queue_free = msg->next;
/* enable interrupt */
rt_hw_interrupt_enable(level);
/* the msg is the new tailer of list, the next shall be NULL */
msg->next = RT_NULL;
/* copy buffer 将线程发送的数据拷贝到内存块地址+1的地方,因为第一个地址用来存链表指针*/
rt_memcpy(msg + 1, buffer, size);
/* disable interrupt */
level = rt_hw_interrupt_disable();
/* link msg to message queue */
if (mq->msg_queue_tail != RT_NULL)
{
/* if the tail exists 第一次创建后,尾部是为空的。如果有尾部的情况下,就将尾部串上当前这个已使用的消息块*/
((struct rt_mq_message *)mq->msg_queue_tail)->next = msg;
}
/* set new tail */
mq->msg_queue_tail = msg;//如果没有尾部,现在就让当前这个已使用的消息块作为尾部
/* if the head is empty, set head */
if (mq->msg_queue_head == RT_NULL)//如果没有头部,也让当前这个已使用的消息块作为头部
mq->msg_queue_head = msg;
if(mq->entry < RT_MQ_ENTRY_MAX)
{
/* increase message entry */
mq->entry ++;//消息个数++
}
else
{
rt_hw_interrupt_enable(level); /* enable interrupt */
return -RT_EFULL; /* value overflowed 消息满,溢出返回错误*/
}
/* resume suspended thread */
if (!rt_list_isempty(&mq->parent.suspend_thread))
{
_ipc_list_resume(&(mq->parent.suspend_thread));
/* enable interrupt */
rt_hw_interrupt_enable(level);
rt_schedule();
return RT_EOK;
}
/* enable interrupt */
rt_hw_interrupt_enable(level);
return RT_EOK;
}
RTM_EXPORT(rt_mq_send_wait)
使用消息队列发送函数,会将最后一个内存块作为首先使用,然后消息控制块mq的msg_queue_head成员变量会永久记录首先使用的这个内存块,第一次调用头部和尾部都是最后一个内存块。如下所示,发送第一个消息执行到最后,这一个链表已经断开了,内存池最后1块自成一派形成一个新的链表,只是这个链表只有1个节点,相当于最后一个内存块填充消息之后,从空闲链表中剥离出来,形成了消息链表,原来的空闲链表节点减少1个。
第二次发送消息的时候,过程如下。消息链表逐渐壮大,空闲链表逐渐萎缩。
发送时,消息依次插到消息链表的尾部。在rtthread中支持加急,使用rt_mq_urgent函数,可将消息插到消息链表头部。
接下来分析,发送消息对于发送方有何影响?
当空闲链表用完了,程序会在while ((msg = (struct rt_mq_message *)mq->msg_queue_free) == RT_NULL)进行循环,由于传参时timeput为0,因此直接退出循环,返回错误。如果在其他地方直接调用这个底层函数并且超时时间大于0时,遇到空闲链表用完的情况则会启动线程定时器,线程阻塞,一会儿再看链表是否空出来。
如果空闲链表没有用完,则会直接跳过这个while循环,函数末尾判断ipc挂起链表是否为空,即如果当前有线程因为访问队列而进入阻塞,现在 有消息了则可以将该线程从阻塞中恢复,执行任务调度。
在发送消息操作的时候,为了保护数据,当且仅当空闲消息链表上有可用的空闲消息块时,发送者才能成功发送消息;当空闲消息链表上无可用消息块,说明消息队列已满, 此时,发送消息的的线程或者中断程序会收到一个错误码(-RT_EFULL),发送消息并不 带有阻塞机制的,因为发送消息的环境可能是在中断中,不允许有阻塞的情况。
rt_mq_recv函数
当消息队列中有消息时,接收线程才能接收到消息,接收消息是有阻塞机制的,用户 可以自定义等待时间,RT-Thread 的接收消息过程是:接收一个消息后消息队列的头链表 消息被转移到了空闲消息链表中。
rt_err_t rt_mq_recv(rt_mq_t mq,
void *buffer,
rt_size_t size,
rt_int32_t timeout)
{
struct rt_thread *thread;
rt_base_t level;
struct rt_mq_message *msg;//单向链表
rt_uint32_t tick_delta;
/* parameter check */
RT_ASSERT(mq != RT_NULL);
RT_ASSERT(rt_object_get_type(&mq->parent.parent) == RT_Object_Class_MessageQueue);
RT_ASSERT(buffer != RT_NULL);
RT_ASSERT(size != 0);
/* current context checking */
RT_DEBUG_SCHEDULER_AVAILABLE(timeout != 0);
/* initialize delta tick */
tick_delta = 0;
/* get current thread */
thread = rt_thread_self();//消息接收方
RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mq->parent.parent)));
/* disable interrupt */
level = rt_hw_interrupt_disable();
/* for non-blocking call */
if (mq->entry == 0 && timeout == 0)//如果消息个数为0并且不允许等待则直接返回超时
{
rt_hw_interrupt_enable(level);
return -RT_ETIMEOUT;
}
/* message queue is empty */
while (mq->entry == 0)//消息个数等于0,但是允许等待的情况
{
/* reset error number in thread */
thread->error = RT_EOK;//接收方error重置为OK
/* no waiting, return timeout */
if (timeout == 0)//如果等待时间为0则返回超时
{
/* enable interrupt */
rt_hw_interrupt_enable(level);
thread->error = -RT_ETIMEOUT;
return -RT_ETIMEOUT;
}
/* suspend current thread 消息个数为0,且未超时的情况下,进行接收方挂起*/
_ipc_list_suspend(&(mq->parent.suspend_thread),
thread,
mq->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, ("set thread:%s to timer list\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(level);
/* re-schedule */
rt_schedule();
/* recv message */
if (thread->error != RT_EOK)
{
/* return error */
return thread->error;
}
/* disable interrupt */
level = 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;
}
}
/* get message from queue 消息个数大于0的情况,首先获取消息链表头部节点*/
msg = (struct rt_mq_message *)mq->msg_queue_head;
/* move message queue head 让下一个节点作为新的头部*/
mq->msg_queue_head = msg->next;
/* reach queue tail, set to NULL 如果消息链表头部和尾部相等,说明消息链表空了*/
if (mq->msg_queue_tail == msg)
mq->msg_queue_tail = RT_NULL;
/* decrease message entry */
if(mq->entry > 0)
{
mq->entry --;//消息个数减1
}
/* enable interrupt */
rt_hw_interrupt_enable(level);
/* copy message 将消息的内容拷贝给buffer*/
rt_memcpy(buffer, msg + 1, size > mq->msg_size ? mq->msg_size : size);
/* disable interrupt */
level = rt_hw_interrupt_disable();
/* put message to free list */
msg->next = (struct rt_mq_message *)mq->msg_queue_free;
mq->msg_queue_free = msg;
/* resume suspended thread */
if (!rt_list_isempty(&(mq->suspend_sender_thread)))
{
_ipc_list_resume(&(mq->suspend_sender_thread));
/* enable interrupt */
rt_hw_interrupt_enable(level);
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mq->parent.parent)));
rt_schedule();
return RT_EOK;
}
/* enable interrupt */
rt_hw_interrupt_enable(level);
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mq->parent.parent)));
return RT_EOK;
}
RTM_EXPORT(rt_mq_recv);
继续以发送了两个消息的情况为例。执行接收之后,消息链表头部节点的信息得以提取,然后消息链表头部节点重新作为空闲链表头部,并从消息链表中剥离,下一次接收时提取新的消息链表头部里的内容。由此可见,内存池中靠后的内存块使用率更高一些,内存池靠前的部分有可能不会用到。
假设有一个线程 A 对某个队列进行读操作的时候(也就是我们所说的出队),发现它没有消息,那么此时线程 A 有 3 个选择:第一个选择,线程 A 扭头就走,既然队列没有消息, 那我也不等了,干其它事情去,这样子线程 A 不会进入阻塞态;第二个选择,线程 A 还是 在这里等等吧,可能过一会队列就有消息,此时线程 A 会进入阻塞状态,在等待着消息的 道来,而线程 A 的等待时间就由我们自己定义,比如设置 1000 个 tick 的等待,在这 1000 个 tick 到来之前线程 A 都是处于阻塞态,当阻塞的这段时间线程 A 等到了队列的消息,那么线程 A 就会从阻塞态变成就绪态,如果此时线程 A 比当前运行的线程优先级还高,那么, 线程 A 就会得到消息并且运行;假如 1000 个tick 都过去了,队列还没消息,那线程 A 就不等了,从阻塞态中唤醒,返回一个没等到消息的错误代码,然后继续执行线程 A 的其他代码;第三个选择,线程 A死等,不等到消息就不走了,这样子线程 A 就会进入阻塞态, 直到完成读取队列的消息。
/* 队列读取(接收),等待时间为一直等待 */
uwRet = rt_mq_recv(test_mq, /* 读取(接收)队列的 ID(句柄) */
&r_queue, /* 读取(接收)的数据保存位置 */
sizeof(r_queue), /* 读取(接收)的数据的长度 */
RT_WAITING_FOREVER); /* 等待时间:一直等 */
if (RT_EOK == uwRet){
rt_kprintf("本次接收到的数据是:%d\n",r_queue);
} else{
rt_kprintf("数据接收出错,错误代码: 0x%lx\n",uwRet);
}