消息队列是另一种常用的线程间通讯方式,它能够接收来自线程的不固定长度的消息,并把消息缓存在自己的内存空间中。其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。而当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。消息队列是一种异步的通信方式。
如上图所示,通过消息队列服务,线程或中断服务子程序可以将一条或多条消息放入消息队列。同样,一个或多个线程可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则(FIFO)。
RT-Thread的消息队列对象由多个元素组成,当消息队列被创建时,它就被分配了消息队列控制块:队列名,内存缓冲区,消息大小以及队列长度等。同时每个消息队列对象中包含着多个消息框,每个消息框可以存放一条消息。消息队列中的第一个和最后一个消息框被分别称为队首和队尾,对应于消息队列控制块中的msg queue head和msg queue tail;有些消息框中可能是空的,它们通过msg queue free线程一个空闲消息框链表。所有消息队列中的消息框总数即是消息队列的长度,这个长度可在消息队列创建时指定。
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功能的.消息头只是用来做索引功能,通过它可以索引到下一个消息头的位置.如果大家还是有些不明白,那么可以先看看消息队列是如何初始化的,就会明白为什么会有这么一个消息头了.
/** * 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 这个消息队列在地址上是从后往前的)
/** * 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; }创建消息队列与初始化消息队列类似,只不过一个是把一片已经存在的内存变成消息队列,一个是动态分配一片内存,然后再将其变成消息队列.
/** * 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; }
/** * 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; }
/** * 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; }
/** * 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; }
/** * 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; }
/** * 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; }