消息队列是另一种常用的线程间通讯方式,它能够接收来自线程的不固定长度的消息,并把消息缓存在自己的内存空间中。其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。而当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。消息队列是一种异步的通信方式。
如上图所示,通过消息队列服务,线程或中断服务子程序可以将一条或多条消息放入消息队列。同样,一个或多个线程可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则(FIFO)。
RT-Thread的消息队列对象由多个元素组成,当消息队列被创建时,它就被分配了消息队列控制块:队列名,内存缓冲区,消息大小以及队列长度等。同时每个消息队列对象中包含着多个消息框,每个消息框可以存放一条消息。消息队列中的第一个和最后一个消息框被分别称为队首和队尾,对应于消息队列控制块中的msg queue head和msg queue tail;有些消息框中可能是空的,它们通过msg queue free线程一个空闲消息框链表。所有消息队列中的消息框总数即是消息队列的长度,这个长度可在消息队列创建时指定。
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; //指向空闲队列
};
typedef struct rt_messagequeue *rt_mq_t;
以上是消息队列控制块的定义,那么消息队列内部包含的每条消息元素又是如何定义的呢?且看如下:
struct rt_mq_message
{
struct rt_mq_message *next;//指向下一条消息元素
};
可能大家会有个疑问,怎么消息元素没有消息的具体数据内容呢?
其实上面这个消息元素只能算作是消息头,就好比短信的标题一般,真正内部跟随在这个消息头后面,一直到下一个消息头之前的所有内存中的数据就是消息的真正内容.rt-thread采用静态队列的方式来实现消息队列的IPC功能的.消息头只是用来做索引功能,通过它可以索引到下一个消息头的位置.如果大家还是有些不明白,那么可以先看看消息队列是如何初始化的,就会明白为什么会有这么一个消息头了.
2 消息队列接口
2.1 初始化消息队列
/**
* This function will initialize a message queue and put it under control of
* resource management.
*
* @param mq the message object
* @param name the name of message queue
* @param msgpool the beginning address of buffer to save messages
* @param msg_size the maximum size of message
* @param pool_size the size of buffer to save messages
* @param flag the flag of message queue
*
* @return the operation status, RT_EOK on successful
*/
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;
/* parameter check */
RT_ASSERT(mq != RT_NULL);
/* init object */
rt_object_init(&(mq->parent.parent), RT_Object_Class_MessageQueue, name);//初始化消息队列对应的内核对象
/* set parent flag */
mq->parent.parent.flag = flag;//设置标志
/* init ipc object */
rt_ipc_object_init(&(mq->parent));//初始化IPC对象
/* set messasge pool */
mq->msg_pool = msgpool;//设置消息池
/* get correct message size */
mq->msg_size = RT_ALIGN(msg_size, RT_ALIGN_SIZE);//设置对齐后的每条消息大小
mq->max_msgs = pool_size / (mq->msg_size + sizeof(struct rt_mq_message));//计算消息池内总共可容纳多少条消息
/* init message list */
mq->msg_queue_head = RT_NULL;//队首
mq->msg_queue_tail = RT_NULL;//队尾
/* init message empty list */
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 = mq->msg_queue_free;
mq->msg_queue_free = head;
}
/* the initial entry is zero */
mq->entry = 0;//消息队列内包含的消息条数为0
return RT_EOK;
}
这里需要注意地是,消息队列会将消息池(即一片内存空间)做为静态队列来用,msg_queue_free指向位于最末尾的那个消息头(rt_mq_message 这个消息队列在地址上是从后往前的)
2.2 创建消息队列
/**
* This function will create a message queue object from system resource
*
* @param name the name of message queue
* @param msg_size the size of message
* @param max_msgs the maximum number of message in queue
* @param flag the flag of message queue
*
* @return the created message queue, RT_NULL on error happen
*/
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;
RT_DEBUG_NOT_IN_INTERRUPT;//确保本函数不是在ISR中使用
/* 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;//设置内核对象标志
/* init ipc object */
rt_ipc_object_init(&(mq->parent));//IPC对象初始化
/* init 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_malloc((mq->msg_size + sizeof(struct rt_mq_message))* mq->max_msgs);//动态分配消息池
if (mq->msg_pool == RT_NULL)
{
rt_mq_delete(mq);
return RT_NULL;
}
/* init message list */
mq->msg_queue_head = RT_NULL;
mq->msg_queue_tail = RT_NULL;
/* init message empty list */
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 = mq->msg_queue_free;
mq->msg_queue_free = head;
}
/* the initial entry is zero */
mq->entry = 0;
return mq;
}
创建消息队列与初始化消息队列类似,只不过一个是把一片已经存在的内存变成消息队列,一个是动态分配一片内存,然后再将其变成消息队列.
2.3 脱离消息队列
/**
* This function will detach a message queue object from resource management
*
* @param mq the message queue object
*
* @return the operation status, RT_EOK on successful
*/
rt_err_t rt_mq_detach(rt_mq_t mq)
{
/* parameter check */
RT_ASSERT(mq != RT_NULL);
/* resume all suspended thread */
rt_ipc_list_resume_all(&mq->parent.suspend_thread);//还原所有挂起在消息队列中的线程
/* detach message queue object */
rt_object_detach(&(mq->parent.parent));//脱离消息队列的内核对象
return RT_EOK;
}
2.4 删除消息队列
/**
* This function will delete a message queue object and release the memory
*
* @param mq the message queue object
*
* @return the error code
*/
rt_err_t rt_mq_delete(rt_mq_t mq)
{
RT_DEBUG_NOT_IN_INTERRUPT;
/* parameter check */
RT_ASSERT(mq != RT_NULL);
/* resume all suspended thread */
rt_ipc_list_resume_all(&(mq->parent.suspend_thread));//唤醒所有挂起的线程
#if defined(RT_USING_MODULE) && defined(RT_USING_SLAB)
/* the mq object belongs to an application module */
if (mq->parent.parent.flag & RT_OBJECT_FLAG_MODULE)
rt_module_free(mq->parent.parent.module_id, mq->msg_pool);//卸载模块
else
#endif
/* free message queue pool */
rt_free(mq->msg_pool);//释放消息池
/* delete message queue object */
rt_object_delete(&(mq->parent.parent));//删除消息队列的内核对象
return RT_EOK;
}
2.5 发送消息
/**
* This function will send a message to message queue object, if there are
* threads suspended on message queue object, it will be waked up.
*
* @param mq the message queue object
* @param buffer the message
* @param size the size of buffer
*
* @return the error code
*/
rt_err_t rt_mq_send(rt_mq_t mq, void *buffer, rt_size_t size)
{
register rt_ubase_t temp;
struct rt_mq_message *msg;
RT_ASSERT(mq != RT_NULL);
RT_ASSERT(buffer != RT_NULL);
RT_ASSERT(size != 0);
/* greater than one message size */
if (size > mq->msg_size)//如果发送的消息大小比消息队列允许每条消息的的大小,则直接返回错误
return -RT_ERROR;
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mq->parent.parent)));
/* disable interrupt */
temp = rt_hw_interrupt_disable();//关中断
/* get a free list, there must be an empty item */
msg = (struct rt_mq_message*)mq->msg_queue_free;//从空闲队列中获取一个空闲位置
/* message queue is full */
if (msg == RT_NULL)//如果消息队列已满
{
/* enable interrupt */
rt_hw_interrupt_enable(temp);
return -RT_EFULL;//返回消息队列已满错误
}
/* move free list pointer *///获得空闲位置成功
mq->msg_queue_free = msg->next;//空闲指针指向下一条消息
/* enable interrupt */
rt_hw_interrupt_enable(temp);//开中断
/* the msg is the new tailer of list, the next shall be NULL */
msg->next = RT_NULL;
/* copy buffer */
rt_memcpy(msg + 1, buffer, size);//保存消息内容,msg+1是指消息头后面的位置
/* disable interrupt */
temp = 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;
/* increase message entry */
mq->entry ++;//消息队列包含的消息条数加1
/* resume suspended thread */
if (!rt_list_isempty(&mq->parent.suspend_thread))//如果存在挂起线程
{
rt_ipc_list_resume(&(mq->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;
}
2.6 发送紧急消息
/**
* This function will send an urgent message to message queue object, which
* means the message will be inserted to the head of message queue. If there
* are threads suspended on message queue object, it will be waked up.
*
* @param mq the message queue object
* @param buffer the message
* @param size the size of buffer
*
* @return the error code
*/
rt_err_t rt_mq_urgent(rt_mq_t mq, void *buffer, rt_size_t size)
{
register rt_ubase_t temp;
struct rt_mq_message *msg;
RT_ASSERT(mq != RT_NULL);
RT_ASSERT(buffer != RT_NULL);
RT_ASSERT(size != 0);
/* greater than one message size */
if (size > mq->msg_size)//如果消息大小超过限制
return -RT_ERROR;
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mq->parent.parent)));
/* disable interrupt */
temp = rt_hw_interrupt_disable();//关中断
/* get a free list, there must be an empty item */
msg = (struct rt_mq_message *)mq->msg_queue_free;//获取空闲位置
/* message queue is full */
if (msg == RT_NULL)
{
/* enable interrupt */
rt_hw_interrupt_enable(temp);
return -RT_EFULL;
}
/* move free list pointer */
mq->msg_queue_free = msg->next;//空闲链表指向下一个位置
/* enable interrupt */
rt_hw_interrupt_enable(temp);
/* copy buffer */
rt_memcpy(msg + 1, buffer, size);//保存消息
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* link msg to the beginning of message queue */
msg->next = mq->msg_queue_head;//将当前消息放到消息首之前
mq->msg_queue_head = msg;
/* if there is no tail */
if (mq->msg_queue_tail == RT_NULL)//如果没有消息尾,则将消息尾也指向此消息
mq->msg_queue_tail = msg;
/* increase message entry */
mq->entry ++;//消息条数加1
/* resume suspended thread */
if (!rt_list_isempty(&mq->parent.suspend_thread))//如果存在挂起线程,则唤醒第一个线程
{
rt_ipc_list_resume(&(mq->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;
}
2.7 接收消息
/**
* This function will receive a message from message queue object, if there is
* no message in message queue object, the thread shall wait for a specified
* time.
*
* @param mq the message queue object
* @param buffer the received message will be saved in
* @param size the size of buffer
* @param timeout the waiting time
*
* @return the error code
*/
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;
RT_ASSERT(mq != RT_NULL);
RT_ASSERT(buffer != RT_NULL);
RT_ASSERT(size != 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 */
temp = rt_hw_interrupt_disable();//关中断
/* for non-blocking call */
if (mq->entry == 0 && timeout == 0)//如果当前消息队列中没有消息,且当前等待时间参数为0,则直接返回错误
{
rt_hw_interrupt_enable(temp);
return -RT_ETIMEOUT;
}
/* message queue is empty */
while (mq->entry == 0)//一直等待到有消息到达
{
RT_DEBUG_NOT_IN_INTERRUPT;
/* 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;
}
/* suspend current thread */
rt_ipc_list_suspend(&(mq->parent.suspend_thread),//如果时间参数不为0,则挂起当前线程
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(temp);
/* re-schedule */
rt_schedule();//重新调度
/* recv message */
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;
}
}
/* get message from queue */
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 */
mq->entry --;//消息条数减1
/* enable interrupt */
rt_hw_interrupt_enable(temp);
/* copy message */
rt_memcpy(buffer, msg + 1, size > mq->msg_size ? mq->msg_size : size);//取出消息内容
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* put message to free list */
msg->next = (struct rt_mq_message *)mq->msg_queue_free;//更新空闲消息链表,将读出后的消息队列放到空闲链表
mq->msg_queue_free = msg;
/* enable interrupt */
rt_hw_interrupt_enable(temp);
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mq->parent.parent)));
return RT_EOK;
}
2.8 消息控制
/**
* This function can get or set some extra attributions of a message queue
* object.
*
* @param mq the message queue object
* @param cmd the execution command
* @param arg the execution argument
*
* @return the error code
*/
rt_err_t rt_mq_control(rt_mq_t mq, rt_uint8_t cmd, void *arg)
{
rt_ubase_t level;
struct rt_mq_message *msg;
RT_ASSERT(mq != RT_NULL);
if (cmd == RT_IPC_CMD_RESET)//消息重置
{
/* disable interrupt */
level = rt_hw_interrupt_disable();
/* resume all waiting thread */
rt_ipc_list_resume_all(&mq->parent.suspend_thread);
/* release all message in the queue */
while (mq->msg_queue_head != RT_NULL)
{
/* get message from queue */
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;
/* put message to free list */
msg->next = (struct rt_mq_message *)mq->msg_queue_free;
mq->msg_queue_free = msg;
}
/* clean entry */
mq->entry = 0;
/* enable interrupt */
rt_hw_interrupt_enable(level);
rt_schedule();
return RT_EOK;
}
return -RT_ERROR;
}
此接口只支持消息重置,它基本上与消息队列初始化类似,就不做解释了.