type
status
slug
date
summary
tags
category
password
icon

邮箱机制

简述

邮箱作为uC/OSII的一种事件类型,是用于实现uC/OSII中任务之间或者任务与中断服务程序之间的一种通信机制,它可以使一个任务或者中断服务程序向另一个任务发送一个指针类型的变量。该指针指向一个包含了特定”信息”的数据结构
总共有6种对邮箱的操作: OSMboxCreate()、OSMboxDel()、OSMboxPend()、OSMboxPost()、OSMboxAccept()和OSMboxQuery()函数,这里这些函数和之前的信号量定义的函数挺像的。

消息队列

简述

uC/OSII 实现任务之间通信的另外一种方式是使用消息队列,他可以使一个任务或者中断服务程序向另一个任务发送以指针方式定义的变量。每个指针指向的数据结构变量因具体的应用不同也会有所不同。
总共7个操作:OSQCreate()、OSQPend()、OSQPOST()、OSQPostFront()、OSQAccept()、OSQFlush()、OSQQuery()
这里有两个函数不一样,还少了delete函数感觉。

废话不多说我们走入实现

notion image
下面这张图很重要,最好能记住👇
notion image
When a message queue is created, a queue control block (i.e., an OS_Q, see OS_Q.C) is allocated and linked to the ECB using the .OSEventPtr field in OS_EVENT. 让我们看看OS_EVENT的结构体定义,其实就是和上面图对应的,图很形象,这里只是让我们能对应到代码:

OS_Q的初始化

from OS_Q.c
The configuration constant   OS_MAX_QS   in   OS_CFG.H   specifies how many queues you are allowed to have in your application and must be greater than 0. When µC/OS-II is initialized, a list of free queue control blocks is created as shown in Figure11.3.

OS_QInit筛选版

我觉得它这里的宏很没有意义,它就是如果OS_MAX_QS只有1的话,就单独处理这个情况
但是这个情况完全可以合并到下面这个情况里面👇,唯一能解释这个原因在于为1的情况太普遍了,想要更快的处理
和之前建立空闲事件链表以及更早之前建立空闲的TCBFreelist很像,可以说是一模一样
这样建成了OSQFreeList的表,具象化如下:
notion image
A queue control block is a data structure used to maintain information about the queue. It contains the fields described below. Note that the fields are preceded with a dot to show that they are members of a structure as opposed to simple variables.
.OSQPtr links queue control blocks in the list of free queue control blocks. Once the queue is created, this field is not used.
.OSQStart contains a pointer to the start of the message queue storage area. Your application must declare this storage area before creating the queue.
.OSQEnd is a pointer to one location past the end of the queue. This pointer is used to make the queue a circular buffer.
.OSQIn is a pointer to the location in the queue where the next message will be inserted. .OSQIn is adjusted back to the beginning of the message storage area when .OSQIn equals .OSQEnd.
.OSQOut is a pointer to the next message to be extracted (取出)from the queue. .OSQOut is adjusted back to the beginning of the message storage area when .OSQOut equals .OSQEnd.OSQOut is also used to insert a message [see OSQPostFront() and OSQPostOpt()].
.OSQSize contains the size of the message storage area. The size of the queue is determined by your application when the queue is created. Note that µC/OS-II allows the queue to contain up to 65,535 entries.
.OSQEntries contains the current number of entries in the message queue. The queue is empty when .OSQEntries is 0 and full when it equals .OSQSize. The message queue is empty when the queue is created.
notion image
是时候看看OS_Q 这个结构体的内容了:
好图就得反复看
notion image
前面提到一句很重要:Your application must declare this storage area before creating the queue.
所以才来了MsgTbl的定义。
这个Tbl里面存储的也全部是地址,这些地址是指向一个个message的

Creating a message queue, OSQCreate()

A message queue (or simply a queue) needs to be created before it can be used. Creating a queue is accomplished by calling OSQCreate() and passing it two arguments: a pointer to an array that will hold the messages and the size of this array. The array must be declared as an array of pointers to void as follows:
void *MyArrayOfMsg[SIZE];
You would pass the address of MyArrayOfMsg[] to OSQCreate() as well as the size of this array. The message queue is assumed to be initially empty – it doesn’t contain any messages.
(2) OSQCreate() starts by making sure you are not calling this function from an ISR because this is not allowed. All kernel objects need to be created from task level code or before multitasking starts. (3) OSQCreate() then attempts to obtain an ECB from the free list of ECBs and adjusts the linked list accordingly. (4) If there is an ECB available, OSQCreate() attempts to allocate a queue control block (OS_Q) from the free list of queue control blocks (see Figure 11.3) and adjusts the linked list accordingly. (6) If a queue control block was available from the free list, the fields of the queue control block are initialized followed by the ones of the ECB. You should note that the .OSEventType field is set to OS_EVENT_TYPE_Q so that subsequent message queue services can check the validity of the ECB.
OS_EventWaitListInit()函数是公共的,之前信号量的初始化创建过程中也是调用的这个,就是用来初始化(清空)事件的等待列表的:
(8) If an ECB was available but a queue control block was not then, the ECB is returned to the free list since we cannot satisfy the request to create a queue unless we also have a queue control block.
就是这个else语句:
(9) OSQCreate() returns either a pointer to the ECB upon successfully creating a message queue or, a NULL pointer if not. This pointer must be used (if not NULL) in subsequent calls that operate on message queues. The pointer is basically used as the queue’s handle.
那么该怎么使用消息队列呢?怎么让任务之间通信呢?首先想到的就是一个任务需要去向消息队列中请求:

Waiting for a message at a queue (blocking), OSQPend()筛选版

里面的OS_EventTaskWait就是复用的事件操作的公共函数,之前在信号量已经见过了,这里再看看:
就是加入到事件等待队列,并从任务的就绪队列中移出
接下来就是发送一个信息,这里有几个方法可以做到这一点,我们一个个说:

Sending a message to a queue (FIFO), OSQPost()

同样的这里在发现有任务等待这个消息队列的时候,会触发一个OS_EventTaskRdy来主动唤醒等待队列的任务
(4) OSQPost() then checks to see if any task is waiting for a message to arrive at the queue. There are tasks waiting when the .OSEventGrp field in the ECB contains a nonzero value.
(注意,等待超时的任务在systick中断处理的时候就已经做了处理即:
这里设置ptcb->OSTCBStatPend = OS_STAT_PEND_TO;/* Indicate PEND timeout */ 很有用,回顾一下,是在前面任务pend等待信号量后,被恢复的时候
也就是说超时的任务被systick踢出等待,到任务就绪队列之后,还需要在这里主动的从事件的等待队列里面踢出

ok,回到OS_EventTaskRdy,筛选进化版

和之前信号量相比,进化就是多了
ptcb->OSTCBMsg = pmsg; /* Send message directly to waiting task */
 
(5) The highest priority task waiting for the message is removed from the wait list by OS_EventTaskRdy()
后面的操作就是
(6) OS_Sched() is then called to see if the task made ready is now the highest priority task ready to run. If it is, a context switch results [only if OSQPost() is called from a task] and the readied task is executed. If the readied task is not the highest priority task, OS_Sched() returns and the task that called OSQPost() continues execution.

当没有任务等待消息队列,那么会继续把向消息队列中加入msg(前提是消息队列没有满)

 
(9) If there are no tasks waiting for a message to arrive at the queue and the queue is not already full then the message to post is inserted in the next free location (FIFO order) and the number of entries in the queue is incremented.
注意中断的时候也是可以调用Post的,但是任务切换就不会起效果了:
Note that a context switch does not occur if OSQPost() is called by an ISR because context switching from an ISR only occurs when OSIntExit() is called at the completion of the ISR and from the last nested ISR (see section 3.10, Interrupts under µC/OS-II).

Sending a message to a queue (LIFO), OSQPostFront() 筛选版

和FIFO的过程挺像,不一样的在于当没有任务等待消息队列,需要向消息队列里面增加元素的时候,你需要通过OSQOut向队列里面添加元素,并且OSQOut是指向第一个需要出队列的元素,也就是指向的有效元素,所以当你需要从out插入的时候,需要让out回退,也即是out--这点很重要,如果out最开始指向start,说明需要回卷,得把它置为OSQEnd,这一点和通过in插入的时候,如果in是end,回卷为start是一致的。

最灵活的:Sending a message to a queue (FIFO or LIFO), OSQPostOpt()

You can also post a message to a queue using an alternate a more flexible function called OSQPostOpt(). The reason there are three post calls is for backwards compatibility with previous versions of µC/OS-II. OSQPostOpt() is the newer function and can replace both OSQPost() and OSQPostFront() with a single call. In addition, OSQPostOpt() allows posting a message to all tasks (i.e. broadcast) waiting on the queue. 
看起来后面都可以使用这个OSQPostOpt()了,不过它这里的广播是啥意思感觉不是很懂,看看代码吧:

OSQPostOpt()筛选版

广播的意思就是把这个消息传给所有等待在这个事件上的TCB
剩下的很简单,就是多加了一个是否需要重调度,并且还有对于需要把msg加入到消息队列的情况,根据opt的选项决定是front的LIFO还是别的FIFO,代码也即是把之前的代码融合:
还有一个OSQAccept()是一个无等待的获得消息队列的方法,如果得不到消息队列,它不会把自己加入到消息队列的等待队列上,而是会直接函数返回

Getting a Message without Waiting, OSQAccept()

ou can obtain a message from a queue without putting a task to sleep by calling OSQAccept() if the queue is empty. 

OSQAccept()筛选版

 
uc/osII 0 启动和信号量uc/osII 2 内存管理实现
Loading...
liamY
liamY
Chasing Possible
Announcement
🎉Liam’s blog🎉
-- 全新上线 ---
👏欢迎comment👏
⚠️由于浏览器缓存的原因,有些内容是更新了的但是需要手动刷新3次左右,页面才会显示更新内容