分析的内核版本截止到2014-04-15,基于1.05正式版,blogs会及时跟进最新版本的内核开发进度,若源码注释出现”???”字样,则是未深究理解部分。
Raw-OS官方网站:http://www.raw-os.org/
Raw-OS托管地址:https://github.com/jorya/raw-os/
接下去几篇都是关于Raw-OS的消息队列模块的解读
话说对于raw_queue.c这个内核文件的代码,浏览下去你就会发觉其实实时系统消息通信其实也是很简单的,关键是有关c指针的操作理解透彻一点就可以很明白地读懂,这指针的概念就不会多说了,或者可以补补c指针的概念再去读这个模块。
消息队列的原理很简单,就相当于给消息建立个中转站,然后由生产者提供消息,由消费者消费消息。
在queue里面,加入消息是队列,那么生产者提供消息的时候,就可以选择插入到queue的前面或者queue的后面,插入queue头部的消息可以理解为“紧急消息”;插入到queue尾部的消息可以理解为“一般消息”;
那么什么是消息,按照Raw-OS中queue传递消息的过程,消息是一个void指针~那么指针是可以指向任何数据类型的,也就等于说消息是一个可以指向任何数据类型的void指针,那么queue就可以中存放任何数据类型的消息。
至于queue内部的操作,基本上是有关指针的逻辑,所以,亲们~你懂的~
在queue内部,被消费的消息都是从queue的读指针开始取消息的,无论消息是发到queue的头部或者是尾部,queue读指针都是指向第一个没有被消费的消息,queue写指针指向最后一个没有被消费的消息。
接下来就简单看看queue内部,读写指针和queue内消息的关系,例子是一个拥有5个大小单位的queue以及queue内消息的关系:
当往queue发送消息缓存的时候,read指针和write指针就是如此来跑位的,至于有一个情况,如果queue满那读写指针是怎么排位的,这里,除了queue为空时(没有消息),read指针和write指针重合,指向同一个数据单元,当queue满的时候,在操作read指针和write指针前就会直接返回满标志。
之前讲过,消息是一个可以指向任何数据类型的void指针,也就是说,queue存放消息,就是存放指向用户数据的指针。
就是说,如果msg是一个用户数据,那么发往queue的就是&msg,那么在queue作缓存的内存以数组形式出现,这个存放内容是指针的数组是指针数组,所以queue内部用一个用户定义大小的指针数组来缓存消息,继续联想,再要继续对数组操作时,很多时候就需要用到二级指针,因为操作的数组的内容是指针;同样,对于消费消息的操作,就是发送消息的逆操作,废话一堆,看一个发送消息到消费消息过程图应该可以很好理解~
哈哈,至于阻塞的概念我们已经很熟了,讲神马tick,神马mutex都涉及有这个概念,对于queue也是一样,当queue为空(没有消息)时,任务想获取消息时,就会被阻塞在queue的阻塞链表上~阻塞的代码又回到原来的task block了,所以也就没什么难度了~
最后的最后,给出Raw-OS有关消息发送和接收的代码注释,至于创建queue,删除queue等等都是非常简单的~
queue发送
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; /* 定义CPU状态机字变量 */ RAW_SR_ALLOC(); /* 禁止CPU中断,保存CPU状态字 */ RAW_CRITICAL_ENTER(); /* 如果传入的queue控制的类型对象不是Raw-OS的队列对象,错误返回 */ if (p_q->common_block_obj.object_type != RAW_QUEUE_OBJ_TYPE) { RAW_CRITICAL_EXIT(); return RAW_ERROR_OBJECT_TYPE; } /* 获取queue的阻塞链表头 */ block_list_head = &p_q->common_block_obj.block_list; /* 如果当前存放在queue的消息数量大于或等于queue存放消息的消息数组的大小,就说明queue已满,返回满标志 */ 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; } /* 如果执行到这里,说明queue未满,并且没有任务阻塞在queue的阻塞链表上 */ if (is_list_empty(block_list_head)) { /* queue存放消息的数量++ */ p_q->msg_q.current_numbers++; /* 貌似peak_numbers就是用来存放可能出现在queue内消息的最大数量的值 */ 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; } /* 当消息发送到queue的末端时 */ if (opt_send_method == SEND_TO_END) { /* 将消息写入msg写指针指向的位置同时,queue控制块的消息写指针后移 */ *p_q->msg_q.write++ = p_void; /* 如果写指针后移到queue存放消息数组的末端时,那么就重新指向开头 */ if (p_q->msg_q.write == p_q->msg_q.queue_end) { /* 将queue消息写指针 */ p_q->msg_q.write = p_q->msg_q.queue_start; } } /* 当消息发送到queue的前端时 */ else { /* 先检查读指针是否在queue消息数组的起始地址,小心越界 */ if (p_q->msg_q.read == p_q->msg_q.queue_start) { p_q->msg_q.read = p_q->msg_q.queue_end; } /* queue控制块的消息读指针前移 */ p_q->msg_q.read--; /* 将消息写入读指针前移后的位置 */ *p_q->msg_q.read = p_void; } RAW_CRITICAL_EXIT(); /* 当创建queue控制块时注册有回调函数,那么在这里就会调用此回调函数 */ if (p_q->queue_send_notify) { /* 回调函数以queue控制块为传入参数 */ p_q->queue_send_notify(p_q); } TRACE_QUEUE_MSG_POST(raw_task_active, p_q, p_void, opt_send_method); return RAW_SUCCESS; } /* 如果有任务阻塞在queue的阻塞链表上时,在这里唤醒任务,根据用户标识选择唤醒所有任务还是单个任务 */ if (opt_wake_all) { /* 唤醒queue阻塞链表的所有任务 */ while (!is_list_empty(block_list_head)) { /* 将消息发送到queue阻塞链表所有阻塞任务中 */ 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); } } /* 唤醒queue阻塞链表中的第一个任务(最高优先级任务) */ else { /* 将消息发送到queue阻塞链表的第一个任务(阻塞的最高优先级任务) */ 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; }
queue接收
RAW_U16 raw_queue_receive(RAW_QUEUE *p_q, RAW_TICK_TYPE wait_option, RAW_VOID **msg) { RAW_VOID *pmsg; RAW_U16 result; /* 定义CPU状态机字变量 */ RAW_SR_ALLOC(); #if (RAW_QUEUE_FUNCTION_CHECK > 0) /* 检查中断嵌套,和用户设置的接收阻塞标志,仅当用户设置接收不到消息时不发生阻塞才能在中断中接收消息 */ if (raw_int_nesting && (wait_option != RAW_NO_WAIT)) { return RAW_NOT_CALLED_BY_ISR; } /* 检查传入消息队列控制块的地址,为空时,说明没有实体,错误返回 */ if (p_q == 0) { return RAW_NULL_OBJECT; } /* 这里传入的是用来存放接收数据后,数据存放的变量,一个二级指针 */ if (msg == 0) { return RAW_NULL_POINTER; } #endif /* 当开启task 0后,消息由task 0转发??? */ #if (CONFIG_RAW_ZERO_INTERRUPT > 0) if (raw_int_nesting) { return RAW_NOT_CALLED_BY_ISR; } #endif RAW_CRITICAL_ENTER(); /* 如果传入的queue控制的类型对象不是Raw-OS的队列对象,错误返回 */ if (p_q->common_block_obj.object_type != RAW_QUEUE_OBJ_TYPE) { RAW_CRITICAL_EXIT(); return RAW_ERROR_OBJECT_TYPE; } /* * 用传入的msg二级指针来接收queue中的数据 * * 实现过程: * 1.msg是一个二级指针,一个指针的地址,例如用户定义有接收数据的变量*tmp,那么传入的msg = &tmp * 2.在queue中建立存放的消息数组是msg_array[]这样的,那么queue的msg_start = &msg_array * 3.并且queue的读写指针的定义都是二级指针,都存放msg_array[x]某一项的地址 * write = &msg_array[x]; * read = &msg_array[y]; * 4.那么接收时pmsg变量指向读指针的内容,就是pmsg = msg_array[y]; * 5.当用用户定义的接收数据变量*tmp来接收时 * *msg = pmsg = msg_array[y] = *&tmp = tmp * 由此就可以得知用户定义的:tmp = msg_array[y],注意tmp是一个指针类型 * 注意啦,因为,因为,在给queue发送数据的时候,Raw-OS是直接发送数据的指针当做消息的, * 所以,msg_array[]里面存放的消息是一个一个的指针,那么tmp只是取出queue中的消息 * 而消息是指向数据的指针,那么要使用数据时,利用*tmp才能操作存在msg_array[]中元素指向的数据 * * 可以得出的结论是,当创建queue控制块时,存放消息的数组要定义成:指针数组 * 接收过程用定义一个指针变量:RAW_VOID *tmp,接收是传入&tmp,获取消息后,*tmp = 消息指向的数据 */ if (p_q->msg_q.current_numbers) { 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; p_q->msg_q.current_numbers--; RAW_CRITICAL_EXIT(); TRACE_QUEUE_GET_MSG(raw_task_active, p_q, wait_option, *msg); return RAW_SUCCESS; } /* 用户设置不阻塞标志时,返回空指针消息 */ if (wait_option == RAW_NO_WAIT) { *msg = (RAW_VOID *)0; RAW_CRITICAL_EXIT(); return RAW_NO_PEND_WAIT; } /* 当系统上锁后,不能进行任务阻塞 */ SYSTEM_LOCK_PROCESS_QUEUE(); /* 当queue没有消息时,对要获取queue消息进行阻塞,阻塞到queue的阻塞链表上 */ 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清空,用以接收阻塞任务控制的消息 */ *msg = (RAW_VOID *)0; /* 之前分析过post msg时,消息是直接发送到任务控制块中的,当阻塞任务被调度时,从任务控制块中取出消息给msg,返回调度后阻塞状态 */ result = block_state_post_process(raw_task_active, msg); return result; }