RAW-OS学习之——消息队列(queue)
raw-os消息系统提供的机制有很多: queue,mqueue,queue_size,queue_buffer,task_queue,work_queue等,前四种为消息队列,可用于中断和任务之间,任务与任务之间通信。task_queue是任务级的消息队列,适用于已经明确知道哪个任务和哪个任务之间传递消息的情况。最后一种为工作队列,专用于ISR和TASK之间的通信。
queue是最基本的消息模块了(消息在程序中的体现为一个指向用户数据的指针),queue_size是对其扩展,在发送消息的同时发送消息的大小,事实上这两种机制是可以合起来的,在发送消息的同时也发送消息大小嘛,raw-os分开来实现,也易于理解和使用,显得模块清晰。
queue和queue_size发送的只是一个指向消息的指针,并无具体数据的copy,那queue_buffer机制则提供了这样的机制:发送消息时对源数据进行copy到其内部缓冲(FIFO),接收消息时再把数据copy出来,这适用于某些特殊的情况吧。
mqueue是对queue的特殊改进,即是对发送的消息进行优先级排序,紧急消息排在队列的前面,这样好像对应用程序设计的要求比较高,因为要事先评估好会发送哪些消息,怎么排列等。
task_queue就是任务级的队列,可以任务与任务之间直接发送消息,而不用维护一个全局的消息队列模块。
work_queue是raw-os的一种创新机制,是中断下半部原理的具体实现方式,它的本质是中断同步一个专有的任务来处理接下来的数据,当然,中断中是进行了前期预处理的,可以传递参数给这个任务,任务进行通用的处理后,还会执行用户的回调函数,来进行具体的数据处理。
一、RAW_QUEUE原理
1. 先分析一下最基本的queue 数据结构吧,在raw_queue.h中有对queue的结构进行定义:
typedef struct RAW_QUEUE /* RAW_QUEUE数据结构 */
{
RAW_COMMON_BLOCK_OBJECT common_block_obj; /* common block object */
RAW_MSG_Q msg_q; /* queue message */
RAW_VOID (*queue_send_notify)(struct RAW_QUEUE *queue_ptr);
} RAW_QUEUE;
RAW_COMMON_BLOCK_OBJECT 顾名思义:通用阻塞对像,所有raw_XXX_get()函数都会引起自身的阻塞,
所以信号量分析时也说过,这是raw-os从semaphore,mutex,event,queue等内核对像抽像提取出来的
共性,它的作用时维护挂接在内核对像上的pend list,当然了,raw-os对pend list的排序是分两种情
况的:基于FIFO和基于优先级,前者即是先等待的先服务,后者是先服务挂起的最高优先级的任务。
不再赘述。
注意到,RAW_QUEUE结构中第二个成员RAW_MSG_Q,它是queue中具体的消息队列,它管理着进来的消息
存在哪,出去的消息从哪出,结构定义如下,各个成员变量的含义也注释的很清楚。
typedef struct RAW_MSG_Q { /* QUEUE Message 结构 */
RAW_VOID **queue_start; /* Pointer to start of queue data */
RAW_VOID **queue_end; /* Pointer to end of queue data */
RAW_VOID **write; /* Pointer to where next message will be inserted in the Q */
RAW_VOID **read; /* Pointer to where next message will be extracted from the Q */
MSG_SIZE_TYPE size; /* Size of queue (maximum number of entries) */
MSG_SIZE_TYPE current_numbers; /* Current number of entries in the queue */
MSG_SIZE_TYPE peak_numbers; /* Peak number of entries in the queue */
} RAW_MSG_Q;
RAW_QUEUE和RAW_MSG_Q就是消息队列的基本数据结构了,注意到上面queue_start,queue_end,
write,read都是二极指针(指向指针的指针),原因是它们指向的变量,也是一个指针,即是
它们指向的指针指向了具体的消息,好吧,有点难以理解,不过慢慢来也能理解。
2. 消息队列原理框图如下:
图很简单,但是仔细想想以下问题,就会对接下来queue的实现理解很清楚:
1. TASK或ISR进行消息Post的时候,是Post给了哪个任务,是post一个,还是所有的任务?post一个任务具体又是什么情况?
2. TASK或ISR进行消息post的时候,把消息post到了哪里,post到了队列的前面还是后面?
答:1. 可以post一个,也可以post所有,post一个任务时,可以post优先级最高的,也可以post最先等待消息的(假如有多个任务挂在一个消息队列上)。
2. 可以把消息post到队列的最前面,也可以post到队列的最后面,raw-os提供不同的API供用户调用。
raw-os在queue机制上提供的API函数如下:
/* 使用前先创建消息队列 */
RAW_U16 raw_queue_create(RAW_QUEUE *p_q, RAW_U8 *p_name, RAW_VOID **msg_start, MSG_SIZE_TYPE number);
/* 发送一条消息到队列的头部 */
RAW_U16 raw_queue_front_post(RAW_QUEUE *p_q, RAW_VOID *p_void);
/* 发送一条消息到队列的尾部 */
RAW_U16 raw_queue_end_post(RAW_QUEUE *p_q, RAW_VOID *p_void);
/* 接收消息 */
RAW_U16 raw_queue_receive (RAW_QUEUE *p_q, RAW_TICK_TYPE wait_option, RAW_VOID **msg);
/* 给所有的任务都发送消息(broadcast) */
RAW_U16 raw_queue_all_post(RAW_QUEUE *p_q, RAW_VOID *p_void, RAW_U8 opt);
/* 多对像阻塞,暂不分析 */
RAW_U16 raw_queue_send_notify(RAW_QUEUE *p_q, QUEUE_SEND_NOTIFY notify_function);
/* 多对像阻塞,暂不分析 */
RAW_U16 raw_queue_post_notify(RAW_QUEUE *p_q, RAW_VOID *p_void);
/* 检查队列是否满 */
RAW_U16 raw_queue_full_check(RAW_QUEUE *p_q);
/* 内部消息发送函数 */
RAW_U16 msg_post(RAW_QUEUE *p_q, RAW_VOID *p_void, RAW_U8 opt_send_method, RAW_U8 opt_wake_all);
#if (CONFIG_RAW_QUEUE_FLUSH > 0) // 清空消息队列
RAW_U16 raw_queue_flush(RAW_QUEUE *p_q);
#endif
#if (CONFIG_RAW_QUEUE_DELETE > 0)
RAW_U16 raw_queue_delete(RAW_QUEUE *p_q); // 删除消息队列
#endif
二、RAW_MSG_Q处理原理
在使用消息队列之前,是必须像下面这样先定义消息队列变量的:
RAW_QUEUE app_queue; /* 定义RAW_QUEUE变量 */
void *queue_storge[10]; /* 定义一个指针数组(可存10条消息),数组的每个元素即是指向一条消息的指针 */
然后看消息队列创建函数:
RAW_U16 raw_queue_create(RAW_QUEUE *p_q, RAW_U8 *p_name, RAW_VOID **msg_start, MSG_SIZE_TYPE number)
{
#if (RAW_QUEUE_FUNCTION_CHECK > 0)
/* 检查消息队列变量的有效性 */
if (p_q == 0) {
return RAW_NULL_OBJECT;
}
/* 检查消息存储区有效性 */
if (msg_start == 0) {
return RAW_NULL_POINTER;
}
/* 检查消息存储区大小的有效性,0直接返回*/
if (number == 0u) {
return RAW_ZERO_NUMBER;
}
#endif
/*init the queue blocked list*/
list_init(&p_q->common_block_obj.block_list);
/* 消息队列的名子 */
p_q->common_block_obj.name = p_name;
/* 消息队列Post方式默认为基于优先级*/
p_q->common_block_obj.block_way = RAW_BLOCKED_WAY_PRIO;
/* 消息存储区首地址,可以直接写 */
p_q->msg_q.queue_start = msg_start;
/* 消息存储区尾地址 */
p_q->msg_q.queue_end = &msg_start[number];
/* 消息存储区写指针 */
p_q->msg_q.write = msg_start;
/* 消息存储区读指针 */
p_q->msg_q.read = msg_start;
/* 消息存储区大小 */
p_q->msg_q.size = number;
/* 消息存储区当前消息数量 */
p_q->msg_q.current_numbers = 0u;
/* 消息存储区峰值消息数量 */
p_q->msg_q.peak_numbers = 0u;
/* 用于多对像阻塞的回调函数 */
p_q->queue_send_notify = 0;
/* 内核阻塞对像类型 */
p_q->common_block_obj.object_type = RAW_QUEUE_OBJ_TYPE;
TRACE_QUEUE_CREATE(raw_task_active, p_q);
return RAW_SUCCESS;
}
看到里面这四个变量,又让我想起了FIFO中的缓形缓冲区了,下面看一下发消息,读消息的过程:

1. 初始化的时候,read与write均指向指针数组首地址,也即msg_start指向的地址,*msg_start是指针数组的元素,**msg_start即是指向消息的指针。
2. 发送两条消息后,write指针后移两个单位,指向下一条消息将要插入的位置。
3. 接收一条消息后,read指针后移两个单位,指向下一条将要读取的消息。
4. 发送一条消息到队列前头时,read指针向前移动一个单位,当队列头有消息时,消息就发送到尾指针处,因为是循环队列,所以,,,,你懂的。
5. write指向尾部时再向尾部发送消息,则又回循环到前头,这就是循环队列的牛B。
三、接收消息(raw_queue_receive)与发送消息(raw_queue_XXX_post)分析

队列中没有消息时就block在pend list上,有消息时就返回。
1.接收消息raw_queue_receive()源码分析:
RAW_U16 raw_queue_receive(RAW_QUEUE *p_q, RAW_TICK_TYPE wait_option, RAW_VOID **msg)
{
RAW_VOID *pmsg;
RAW_U16 result;
RAW_SR_ALLOC();
#if (RAW_QUEUE_FUNCTION_CHECK > 0)
/* 如果在中断中,资源不可用时立即返回,则也可以用,否则本函数不能在中断中调用 */
if (raw_int_nesting && (wait_option != RAW_NO_WAIT)) {
return RAW_NOT_CALLED_BY_ISR;
}
/* 无效的raw_queue object */
if (p_q == 0) {
return RAW_NULL_OBJECT;
}
/* 无效的消息存储空间 */
if (msg == 0) {
return RAW_NULL_POINTER;
}
#endif
/* 开最大0中断特性时,不能在中断中调用 */
#if (CONFIG_RAW_ZERO_INTERRUPT > 0)
if (raw_int_nesting) {
return RAW_NOT_CALLED_BY_ISR;
}
#endif
RAW_CRITICAL_ENTER();
/* common block object 错误*/
if (p_q->common_block_obj.object_type != RAW_QUEUE_OBJ_TYPE) {
RAW_CRITICAL_EXIT();
return RAW_ERROR_OBJECT_TYPE;
}
/* 消息队列中有消息 */
if (p_q->msg_q.current_numbers) {
/* 取出消息,并让读指针+1,指向下一条将要读取的消息*/
pmsg = *p_q->msg_q.read++;
/* 如果此时读指针到了队列末尾 */
if (p_q->msg_q.read == p_q->msg_q.queue_end) {
/* wrap around to start */
p_q->msg_q.read = p_q->msg_q.queue_start;
}
// *msg即pmsg是指向消息的指针
*msg = pmsg;
// 队列中当前消息数目减-1
p_q->msg_q.current_numbers--;
RAW_CRITICAL_EXIT();
TRACE_QUEUE_GET_MSG(raw_task_active, p_q, wait_option, *msg);
return RAW_SUCCESS;
}
// 程序运行到此是队列中无消息,此时超时选项为RAW_NO_WAIT
if (wait_option == RAW_NO_WAIT) {
*msg = (RAW_VOID *)0; // 空指针
RAW_CRITICAL_EXIT();
return RAW_NO_PEND_WAIT;
}
/*if system is locked, block operation is not allowed*/
SYSTEM_LOCK_PROCESS_QUEUE();
/* 程序运行到此,说明没有消息,且wait_option为超时阻塞或永远阻塞 */
raw_pend_object((RAW_COMMON_BLOCK_OBJECT *)p_q, raw_task_active, wait_option);
RAW_CRITICAL_EXIT();
TRACE_QUEUE_GET_BLOCK(raw_task_active, p_q, wait_option);
/* 执行任务调度,以便收到消息的任务可以被执行 */
raw_sched();
/* 清空消息 */
*msg = (RAW_VOID *)0;
result = block_state_post_process(raw_task_active, msg); /* 读取消息,非常重要 */
return result;
}
2.发送消息raw_queue_end_post()源码分析,此函数是把消息发到队列尾部:
RAW_U16 raw_queue_end_post(RAW_QUEUE *p_q, RAW_VOID *p_void) /* p_void为一个void类型的指针 */
{
#if (RAW_QUEUE_FUNCTION_CHECK > 0)
/* 无效的RAW_QUEUE对像 */
if (p_q == 0) {
return RAW_NULL_OBJECT;
}
/* 无效的消息存储地址 */
if (p_void == 0) {
return RAW_NULL_POINTER;
}
#endif
TRACE_QUEUE_EP_TIME_RECORD(p_q, p_void);
/*开了最大关中断0us,则要用int_msg_post转发 */
#if (CONFIG_RAW_ZERO_INTERRUPT > 0)
if (raw_int_nesting && raw_sched_lock) {
return int_msg_post(RAW_TYPE_Q_END, p_q, p_void, 0, 0, 0);
}
#endif
/* 正常情况下的消息发送,唤醒一个任务 */
return msg_post(p_q, p_void, SEND_TO_END, WAKE_ONE_QUEUE);
}
3.发送消息raw_queue_front_post()源码分析,此函数是把消息发到队列头部:
RAW_U16 raw_queue_front_post(RAW_QUEUE *p_q, RAW_VOID *p_void)
{
#if (RAW_QUEUE_FUNCTION_CHECK > 0)
/* 无效的RAW_QUEUE OBJECT */
if (p_q == 0) {
return RAW_NULL_OBJECT;
}
/* 无效的消息存储地址,p_void本身是一个指针变量 */
if (p_void == 0) {
return RAW_NULL_POINTER;
}
#endif
TRACE_QUEUE_FP_TIME_RECORD(p_q, p_void);
/* 最大关中断0特性打开,则通过task_0转发 */
#if (CONFIG_RAW_ZERO_INTERRUPT > 0)
if (raw_int_nesting && raw_sched_lock) {
return int_msg_post(RAW_TYPE_Q_FRONT, p_q, p_void, 0, 0, 0);
}
#endif
/*正常情况下发送消息,唤醒一个任务 */
return msg_post(p_q, p_void,SEND_TO_FRONT, WAKE_ONE_QUEUE);
}
注意到raw_queue_end_post和raw_queue_front_post最终都是调用了msg_post,下面看下msg_post源码,看它完成了什么工作
/*
函数名称:msg_post
参数说明:p_q:目标操作队列; p_void: 指向将要发送的消息的指针的地址
opt_send_method: 发送到头部(SEND_TO_FRONT)还尾部(SEND_TO_END)
opt_wake_all: 响醒一个任务还是唤醒所有任务
返回值: RAW_SUCCESS:发送成功;RAW_MSG_MAX:队列已满
*/
RAW_U16 msg_post(RAW_QUEUE *p_q, RAW_VOID *p_void, RAW_U8 opt_send_method, RAW_U8 opt_wake_all) {
LIST *block_list_head; /* pend list head */
RAW_SR_ALLOC();
RAW_CRITICAL_ENTER();
/* block object type error */
if (p_q->common_block_obj.object_type != RAW_QUEUE_OBJ_TYPE) {
RAW_CRITICAL_EXIT();
return RAW_ERROR_OBJECT_TYPE;
}
/* 指向Pend list头部 */
block_list_head = &p_q->common_block_obj.block_list;
/* 队列已满,直接返回,注意此时会丢弃数据 */
if (p_q->msg_q.current_numbers >= p_q->msg_q.size) {
RAW_CRITICAL_EXIT();
TRACE_QUEUE_MSG_MAX(raw_task_active, p_q, p_void, opt_send_method);
return RAW_MSG_MAX;
}
/* 程序运行到此说明队列不满,且pend list为空(没有任务阻塞),则把消息加到队中*/
if (is_list_empty(block_list_head)) {
/* 队列中的消息数目+1 */
p_q->msg_q.current_numbers++;
/*update peak_numbers for debug*/
if (p_q->msg_q.current_numbers > p_q->msg_q.peak_numbers) {
p_q->msg_q.peak_numbers = p_q->msg_q.current_numbers;
}
/* 发送消息到队列的尾部 */
if (opt_send_method == SEND_TO_END) {
/* 更新写指针指向最新加入的消息 */
*p_q->msg_q.write++ = p_void;
/* 写指针写到了尾部,则从头开始写 */
if (p_q->msg_q.write == p_q->msg_q.queue_end) {
p_q->msg_q.write = p_q->msg_q.queue_start;
}
}
/* 发送消息到队列的头部 */
else {
/* 如果读指针现在在头部,则要把消息加到队尾 */
if (p_q->msg_q.read == p_q->msg_q.queue_start) {
p_q->msg_q.read = p_q->msg_q.queue_end;
}
/* 读指针-1 */
p_q->msg_q.read--;
/* 更新读指针指向最新入队的消息 */
*p_q->msg_q.read = p_void;
}
RAW_CRITICAL_EXIT();
/* 用于多对像阻塞时的回调 */
if (p_q->queue_send_notify) {
p_q->queue_send_notify(p_q);
}
TRACE_QUEUE_MSG_POST(raw_task_active, p_q, p_void, opt_send_method);
return RAW_SUCCESS;
}
/*程序运行到此,说明pend list非空,如果是WAKE ALL,则逐个唤醒pend task */
if (opt_wake_all) {
while (!is_list_empty(block_list_head)) {
wake_send_msg(list_entry(block_list_head->next, RAW_TASK_OBJ, task_list), p_void);
TRACE_QUEUE_WAKE_TASK(raw_task_active, list_entry(block_list_head->next, RAW_TASK_OBJ, task_list), p_void, opt_wake_all);
}
}
/*wake hignhest priority task blocked on this queue and send msg to it*/
else {
wake_send_msg(list_entry(block_list_head->next, RAW_TASK_OBJ, task_list), p_void);
TRACE_QUEUE_WAKE_TASK(raw_task_active, list_entry(block_list_head->next, RAW_TASK_OBJ, task_list), p_void, opt_wake_all);
}
RAW_CRITICAL_EXIT();
/* 执行一次任务调度 */
raw_sched();
return RAW_SUCCESS;
}
上面程序中唤醒任务的同时把消息指针也给任务控制块的消息指针了了,wake_send_msg源码如下:
RAW_U16 wake_send_msg(RAW_TASK_OBJ *task_ptr, RAW_VOID *msg)
{
/* 把发送来的消息存到task->msg */
task_ptr->msg = msg;
/* 唤醒pend list上的任务 */
return pend_task_wake_up(task_ptr);
}
还记得raw_queue_receive()的最后一条语句吗?
result = block_state_post_process(raw_task_active, msg);
对,这条语句就是把消息指针从task-ptr->msg中取出赋给msg,同时返回任务状态。部分源码如下:
case RAW_B_OK: /* We got the message */
/* 把消息指针赋给msg */
if (msg) {
*msg = task_ptr->msg;
}
// 成功
error_status = RAW_SUCCESS;
break;
raw_queue模块的API函数还有:
raw_queue_all_post(): 唤醒所有阻塞的任务
aw_queue_full_check():队列满检测;
raw_queue_flush():队列清空。
再来分析一下牛B的raw_queue_delete(),它调用了delete_pend_obj()函数,当任务
处于阻塞或阻塞超时态时,他们阻塞依赖的队列删除了,那他们都重新加入就绪列表里了。
好了,raw_queue模块到此吧,弄懂了信号量和队列,编写程序就可以大展身手了。