加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

ucos II 任务间通信详解

(2016-01-24 14:46:48)
标签:

it

分类: uCOS、NuttX、ROS、SYS/BIOS
ucos II 任务间通信详解

ucos II 任务间通信之一 :全局变量
任务创建好了之后,只是完成了系统编程的一小步,更为重要的是任务间的通信。比如
在mcu21 的项目里,有通信任务,有液晶显示任务,有控制任务。控制任务需要用到通信
任务接受到的数据,液晶显示任务也显示控制任务的数据。这就需要用到任务间的通信了。
Mcu21 总结了一下,在ucos II 里任务间通信可以采用以下几种方式。
􀁺 共享全局变量,这是最快捷有效的方式,实现这种通信可以采用以下两种方式:一是利
用宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()来关闭中断和打开中断,二是利用函数
OSSchedLock()和OSSchedUnlock()对μC/OS‐II 中的任务调度函数上锁和开锁.
􀁺 使用信号量
􀁺 使用邮箱
􀁺 使用消息队列
下面介绍下共享全局变量的实现过程。
(1)宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()是在移植ucos II 过程中由用户定义
的。在os_cpu.h 这个文件中。代码如下,这部分代码的作用是关,开中断,具体和CPU 有
关。当我们调用OS_ENTER_CRITICAL()时,系统中断被关闭,我们知道,任务切换时基于定
时器中断的,当系统中断别关闭时,其它中断,包括定时器中断也就被关闭,任务切换也不
可能发生,所以确保在访问变量的时候,不会有其它的任务或中断也在同时访问这个变量。
这两个宏非常好用,在mcu21 的项目里经常用到。尤其在中断处理函数里面。
因为现在的很多CPU 是支持中断嵌套的,为了防止中断执行的时候不被其它的中断打断,
就可以调用这两个宏。
(2)第二种方法是给任务调度函数上锁,开锁。这种方法和使用宏OS_ENTER_CRITICAL()
和OS_EXIT_CRITICAL()最大的区别是:中断是可以执行的。尽管不执行任务切换,变量依然
有可以被中断函数访问。
给任务调度器上锁的函数如下
void OSSchedLock (void)
{
if (OSRunning == TRUE) {
OS_ENTER_CRITICAL();
OSLockNesting++;
OS_EXIT_CRITICAL();
}
}
给任务调度器解锁的函数如下
void OSSchedUnlock (void)
{
if (OSRunning == TRUE) {
OS_ENTER_CRITICAL();
if (OSLockNesting > 0) {
OSLockNesting‐‐;
if ((OSLockNesting | OSIntNesting) == 0) { (1)
OS_EXIT_CRITICAL();
OSSched(); (2)
} else {
OS_EXIT_CRITICAL();
}
} else {
OS_EXIT_CRITICAL();
}
}
}
它实现的原理大致是这样的。
调用一次OSSchedLock(),就会对全局变量OSLockNesting 加1,调用OSSchedUnlock ()
一次就对全局变量OSLockNesting 减1。当OSLockNesting 是零的时候,系统才能进行任务调
度。
http://blog.csdn.net/h32dong809/article/details/7082488

ucos II 任务间 通信之二:事件控制块
信号,邮箱,消息队列都是需要先创建才能使用的,创建以上三者需要用到事件控制块
这一东西。
什么是事件控制块呢?
可以这样理解,前面学习我们已经知道,创建一个任务需要给这个任务分配一个任务控
制块,这个任务控制块存储着关于这个任务的重要信息。那么,事件控制块就好比任务里的
任务控制块。它存储着这个事件的重要信息,我们说创建一个事件(信号,邮箱,消息队列),
其本质的过程就是初始化这个事件控制块。
一个任务或者中断服务子程序可以通过事件控制块ECB(Event Control Blocks)来向另
外的任务发信号。这里,所有的信号都被看成是事件(Event)。一个任务还可以等待另一个
任务或中断服务子程序给它发送信号。这里要注意的是,只有任务可以等待事件发生,中断
服务子程序是不能这样做的。这点要注意,记得mcu21 刚开始的时候就傻傻地在中断处理
函数里等待信号量,后果就是系统直接死机。
对于处于等待状态的任务,还可以给它指定一个最长等待时间,以此来防止因为等待的
事件没有发生而无限期地等下去。
多个任务可以同时等待同一个事件的发生[F6.1B]。在这种情况下,当该事件发生后,所
有等待该事件的任务中,优先级最高的任务得到了该事件并进入就绪状态,准备执行。上面
讲到的事件,可以是信号量、邮箱或者消息队列等。当事件控制块是一个信号量时,任务可
以等待它,也可以给它发送消息。
事件控制块是一个数据结构,其定义如下:
typedef struct {
void *OSEventPtr;
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];
INT16U OSEventCnt;
INT8U OSEventType;
INT8U OSEventGrp;
} OS_EVENT;
.OSEventPtr 指针,只有在所定义的事件是邮箱或者消息队列时才使用。当所定义的事件
是邮箱时,它指向一个消息,而当所定义的事件是消息队列时,它指向一个数据结构。
.OSEventTbl[] 和 .OSEventGrp 很像前面讲到的OSRdyTbl[]和OSRdyGrp,只不过前两者包
含的是等待某事件的任务,而后两者包含的是系统中处于就绪状态的任务。
.OSEventCnt 当事件是一个信号量时,.OSEventCnt 是用于信号量的计数器。
.OSEventType 定义了事件的具体类型。它可以是信号量(OS_EVENT_SEM)、邮箱
(OS_EVENT_TYPE_MBOX)或消息队列(OS_EVENT_TYPE_Q)中的一种。用户要根据该域的
具体值来调用相应的系统函数,以保证对其进行的操作的正确性。

ucos II 任务间 通信之三:信号量
信号量是什么?信号量有什么用?
信号量一是可以用来表示一个或多个事件的发生,二是用来对共享资源的访问。
ucos II 提供了5 个对信号量进行操作的函数。它们是:
􀁺 建立一个信号量, OSSemCreate()
􀁺 等待一个信号量, OSSemPend()
􀁺 发送一个信号量, OSSemPost()
􀁺 无等待地请求一个信号量, OSSemAccept()
􀁺 查询一个信号量的当前状态, OSSemQuery()
OSSemCreate()的实现代码如下:
在mcu21 看来,创建一个信号量,简单来说,就是申请一个事件控制块,接着初始化这个
事件控制块。
首先,它从空闲任务控制块链表中得到一个事件控制块[ (1)],并对空闲事件控制链表的
指针进行适当的调整,使它指向下一个空闲的事件控制块[ (2)]。如果这时有任务控制块可用
[ (3)],就将该任务控制块的事件类型设置成信号量OS_EVENT_TYPE_SEM[ (4)]。其它的信号
量操作函数OSSem???()通过检查该域来保证所操作的任务控制块类型的正确。例如,这可
OS_EVENT *OSSemCreate (INT16U cnt)
{
OS_EVENT *pevent;
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; (1)
if (OSEventFreeList != (OS_EVENT *)0) { (2)
OSEventFreeList = (OS_EVENT *)OSEventFreeList‐>OSEventPtr;
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) { (3)
pevent‐>OSEventType = OS_EVENT_TYPE_SEM; (4)
pevent‐>OSEventCnt = cnt; (5)
OSEventWaitListInit(pevent); (6)
}
return (pevent); (7)
}
以防止调用OSSemPost()函数对一个用作邮箱的任务控制块进行操作。
接着,用信号量的初始值对任务控制块进行初始化[(5)](如果信号量是用来表示一个或者
多个事件的发生,那么该信号量的初始值应设为0, 如果信号量是用于对共享资源的访问,
那么该信号量的初始值应设为1.),并调用OSEventWaitListInit()函数对事件控制任务控制块的
等待任务列表进行初始化 [(6)]。因为信号量正在被初始化,所以这时没有任何任务等待该
信号量。
最后,OSSemCreate()返回给调用函数一个指向任务控制块的指针。以后对信号量的所
有操作,如OSSemPend(), OSSemPost(), OSSemAccept()和OSSemQuery()都是通过该指针完成
的。因此,这个指针实际上就是该信号量的句柄。如果系统中没有可用的任务控制块,
OSSemCreate()将返回一个NULL 指针。
创建好一个信号之后,可以调用OSSemQuery()查询一个信号的状态。该函数有两个参数:
一个是指向信号量对应事件控制块的指针pevent。;另一个是指向用于记录信号量信息的数
据结构OS_SEM_DATA。
简单来说就是把信号量对应的事件控制块的信息复制到数据结构OS_SEM_DATA。
OSSemQuery()程序代码如下:
INT8U OSSemQuery (OS_EVENT *pevent, OS_SEM_DATA *pdata)
{
INT8U i;
INT8U *psrc;
INT8U *pdest;
OS_ENTER_CRITICAL();
if (pevent‐>OSEventType != OS_EVENT_TYPE_SEM) { (1)
OS_EXIT_CRITICAL();
return (OS_ERR_EVENT_TYPE);
}
pdata‐>OSEventGrp = pevent‐>OSEventGrp; (2)
psrc = &pevent‐>OSEventTbl[0];
pdest = &pdata‐>OSEventTbl[0];
for (i = 0; i < OS_EVENT_TBL_SIZE; i++) {
*pdest++ = *psrc++;
}
pdata‐>OSCnt = pevent‐>OSEventCnt; (3)
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}

ucos II 任务间 通信之三:信号量2
当我们创建好了信号量之后,就可以使用信号量了。使用信号量需要调用OSSemPost()
和OSSemPend()。关于具体怎么使用信号量,就得先看看这两个系统函数的代码。
先看等待信号量OSSemPend()的代码:
void OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
{
OS_ENTER_CRITICAL();
if (pevent‐>OSEventType != OS_EVENT_TYPE_SEM) { (1)
OS_EXIT_CRITICAL();
*err = OS_ERR_EVENT_TYPE;
}
if (pevent‐>OSEventCnt > 0) { (2)
pevent‐>OSEventCnt‐‐; (3)
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
} else if (OSIntNesting > 0) { (4)
OS_EXIT_CRITICAL();
*err = OS_ERR_PEND_ISR;
} else {
OSTCBCur‐>OSTCBStat |= OS_STAT_SEM; (5)
OSTCBCur‐>OSTCBDly = timeout; (6)
OSEventTaskWait(pevent); (7)
OS_EXIT_CRITICAL();
OSSched(); (8)
OS_ENTER_CRITICAL();
if (OSTCBCur‐>OSTCBStat & OS_STAT_SEM) { (9)
OSEventTO(pevent); (10)
OS_EXIT_CRITICAL();
*err = OS_TIMEOUT;
} else {
OSTCBCur‐>OSTCBEventPtr = (OS_EVENT *)0; (11)
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
}
}
}
在muc21 看来,这么多代码就是做了一个重要的事情:就是将任务控制块中的状态标
志.OSTCBStat 置1,也就是把任务置于睡眠状态。这样任务就处于等待信号量来激活任务的
状态了。可以这样理解,当任务等待一个信号量的时候,任务是被挂起了,需要等待发送信
号量来激活。
下面具体分析下代码的实现过程。
首先检查指针pevent 所指的任务控制块是否是由OSSemCreate()建立的[(1)]。如果信号
量当前是可用的(信号量的计数值大于0)[ (2)],将信号量的计数值减1[(3)],然后函数将“无
错”错误代码返回给它的调用函数。显然,如果正在等待信号量,这时的输出正是我们所希
望的,也是运行OSSemPend()函数最快的路径。
如果此时信号量无效(计数器的值是0),OSSemPend()函数要进一步检查它的调用函数
是不是中断服务子程序[ (4)]。在正常情况下,中断服务子程序是不会调用OSSemPend()函数
的。这里加入这些代码,只是为了以防万一。当然,在信号量有效的情况下,即使是中断服
务子程序调用的OSSemPend(),函数也会成功返回,不会出任何错误。
OSSemPend()函数通过将任务控制块中的状态标志.OSTCBStat 置1,把任务置于睡眠状态
[ (5)],等待时间也同时置入任务控制块中[ (6)],该值在OSTimeTick()函数中被逐次递减。注
意,OSTimeTick()函数对每个任务的任务控制块的.OSTCBDly 域做递减操作(只要该域不为0)。
真正将任务置入睡眠状态的操作在OSEventTaskWait()函数中执行 [ (7)]。
因为当前任务已经不是就绪态了,所以任务调度函数将下一个最高优先级的任务调入,
准备运行[ (8)]。当信号量有效或者等待时间到后,调用OSSemPend()函数的任务将再一次成
为最高优先级任务。这时OSSched()函数返回。这之后,OSSemPend()要检查任务控制块中的
状态标志,看该任务是否仍处于等待信号量的状态[ (9)]。如果是,说明该任务还没有被
OSSemPost()函数发出的信号量唤醒。事实上,该任务是因为等待超时而由TimeTick()函数把
它置为就绪状态的。这种情况下,OSSemPend()函数调用OSEventTO()函数将任务从等待任务
列表中删除[ (10)],并返回给它的调用任务一个“超时”的错误代码。如果任务的任务控制块
中的OS_STAT_SEM 标志位没有置位,就认为调用OSSemPend()的任务已经得到了该信号量,
将指向信号量ECB 的指针从该任务的任务控制块中删除,并返回给调用函数一个“无错”的错
误代码[ (11)]。
接下来看下发送信号量的代码:
INT8U OSSemPost (OS_EVENT *pevent)
{
OS_ENTER_CRITICAL();
if (pevent‐>OSEventType != OS_EVENT_TYPE_SEM) { (1)
OS_EXIT_CRITICAL();
return (OS_ERR_EVENT_TYPE);
}
if (pevent‐>OSEventGrp) { (2)
OSEventTaskRdy(pevent, (void *)0, OS_STAT_SEM); (3)
OS_EXIT_CRITICAL();
OSSched(); (4)
return (OS_NO_ERR);
} else {
if (pevent‐>OSEventCnt < 65535) {
pevent‐>OSEventCnt++; (5)
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
} else {
OS_EXIT_CRITICAL();
return (OS_SEM_OVF);
}
}
}
理解了等待信号量函数的源码后理解发送信号量函数的源码也就很容易了。
以上源码的作用简单来说就是查找有没有任务在等待这个信号量,如果有,就把该任务从睡
眠态拉回就绪态。
首先检查参数指针pevent 指向的任务控制块是否是OSSemCreate()函数建立的[ (1)],接
着检查是否有任务在等待该信号量[ (2)]。如果该任务控制块中的.OSEventGrp 域不是0,说
明有任务正在等待该信号量。这时,就要调用函数OSEventTaskRdy(),把其中的最高优先级
任务从等待任务列表中删除[(3)]并使它进入就绪状态。然后,调用OSSched()任务调度函数
检查该任务是否是系统中的最高优先级的就绪任务[ (4)]。如果是,这时就要进行任务切换[当
OSSemPost()函数是在任务中调用的],准备执行该就绪任务。如果不是,OSSched()直接返回,
调用OSSemPost()的任务得以继续执行。如果这时没有任务在等待该信号量,该信号量的计
数值就简单地加1[ (5)]。

ucos II 任务间 通信之四:邮箱
学过信号量之后再来看邮箱,发现他们是非常相似的,甚至有时候邮箱可以当做信号量
来使用,邮箱相对信号量而言,只是多传递了一个指针变量。和信号量很相似,ucos II 提供
了5 个对邮箱进行操作的函数。它们是:
􀁺 建立一个邮箱,OSMboxCreate()
􀁺 等待一个邮箱中的消息,OSMboxPend()
􀁺 发送一个消息到邮箱中,OSMboxPost()
􀁺 无等待地从邮箱中得到一个消息, OSMboxAccept()
􀁺 查询一个邮箱的状态, OSMboxQuery()
使用邮箱之前,必须先建立该邮箱。该操作可以通过调用OSMboxCreate()函数来完成,
并且要指定指针的初始值。一般情况下,这个初始值是NULL,但也可以初始化一个邮箱,
使其在最开始就包含一条消息。如果使用邮箱的目的是用来通知任务某一个事件已经发生
(发送一条消息),那么就要初始化该邮箱为NULL。如果用户用邮箱来共享某些资源,那么
就要初始化该邮箱为一个非NULL 的指针。在这种情况下,邮箱被当成一个二值信号量使用。
下面来看看创建一个邮箱函数的实现代码:
OS_EVENT *OSMboxCreate (void *msg)
{
OS_EVENT *pevent;
OS_ENTER_CRITICAL();
pevent = OSEventFreeList;
if (OSEventFreeList != (OS_EVENT *)0) {
OSEventFreeList = (OS_EVENT *)OSEventFreeList‐>OSEventPtr;
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) {
pevent‐>OSEventType = OS_EVENT_TYPE_MBOX; (1)
pevent‐>OSEventPtr = msg; (2)
OSEventWaitListInit(pevent);
}
return (pevent); (3)
}
仔细看看,其实和创建一个信号量的过程几乎是一样的,先申请一个空事件控制块,接
着初始化这个事件控制块。最后返回一个指向这个事件控制块的指针。不同之处在于事件控
制块的类型被设置成OS_EVENT_TYPE_MBOX[ (1)],以及使用.OSEventPtr 域来容纳消息指针。
接着来看看等待邮箱函数实现代码:
void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
{
void *msg;
OS_ENTER_CRITICAL();
if (pevent‐>OSEventType != OS_EVENT_TYPE_MBOX) { (1)
OS_EXIT_CRITICAL();
*err = OS_ERR_EVENT_TYPE;
return ((void *)0);
}
msg = pevent‐>OSEventPtr;
if (msg != (void *)0) { (2)
pevent‐>OSEventPtr = (void *)0; (3)
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
} else if (OSIntNesting > 0) { (4)
OS_EXIT_CRITICAL();
*err = OS_ERR_PEND_ISR;
} else {
OSTCBCur‐>OSTCBStat |= OS_STAT_MBOX; (5)
OSTCBCur‐>OSTCBDly = timeout;
OSEventTaskWait(pevent);
OS_EXIT_CRITICAL();
OSSched();
OS_ENTER_CRITICAL();
if ((msg = OSTCBCur‐>OSTCBMsg) != (void *)0) { (6)
OSTCBCur‐>OSTCBMsg = (void *)0;
OSTCBCur‐>OSTCBStat = OS_STAT_RDY;
OSTCBCur‐>OSTCBEventPtr = (OS_EVENT *)0;
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
} else if (OSTCBCur‐>OSTCBStat & OS_STAT_MBOX) { (7)
OSEventTO(pevent); (8)
OS_EXIT_CRITICAL();
msg = (void *)0; (9)
*err = OS_TIMEOUT;
} else {
msg = pevent‐>OSEventPtr; (10)
pevent‐>OSEventPtr = (void *)0; (11)
OSTCBCur‐>OSTCBEventPtr = (OS_EVENT *)0; (12)
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
}
}
return (msg);
}
同样,它和OSSemPend()也很相似,说白了就是先看有没有有用的消息,要是没有,就
把该任务挂起来。
OSMboxPend() 首先检查该事件控制块是由OSMboxCreate() 函数建立的[ (1)] 。
当.OSEventPtr 域是一个非NULL 的指针时,说明该邮箱中有可用的消息[ (2)]。这种情况下,
OSMboxPend()函数将该域的值复制到局部变量msg 中,然后将.OSEventPtr 置为NULL[ (3)]。
这正是我们所期望的,也是执行OSMboxPend()函数最快的路径。
如果此时邮箱中没有消息是可用的(OSEventPtr 域是NULL 指针),OSMboxPend()函数检查
它的调用者是否是中断服务子程序[ (4)]。象OSSemPend()函数一样,不能在中断服务子程序
中调用OSMboxPend(),因为中断服务子程序是不能等待的。这里的代码同样是为了以防万
一。但是,如果邮箱中有可用的消息,即使从中断服务子程序中调用OSMboxPend()函数,
也一样是成功的。
如果邮箱中没有可用的消息,OSMboxPend()的调用任务就被挂起,直到邮箱中有了消
息或者等待超时[ (5)]。当有其它的任务向该邮箱发送了消息后(或者等待时间超时),这时,
该任务再一次成为最高优先级任务,OSSched()返回。这时,OSMboxPend()函数要检查是否
有消息被放到该任务的任务控制块中[ (6)]。如果有,那么该次函数调用成功,对应的消息被
返回到调用函数。
发送一个消息到邮箱中OSMboxPost()的代码如下:
INT8U OSMboxPost (OS_EVENT *pevent, void *msg)
{
OS_ENTER_CRITICAL();
if (pevent‐>OSEventType != OS_EVENT_TYPE_MBOX) { (1)
OS_EXIT_CRITICAL();
return (OS_ERR_EVENT_TYPE);
}
if (pevent‐>OSEventGrp) { (2)
OSEventTaskRdy(pevent, msg, OS_STAT_MBOX); (3)
OS_EXIT_CRITICAL();
OSSched(); (4)
return (OS_NO_ERR);
} else {
if (pevent‐>OSEventPtr != (void *)0) { (5)
OS_EXIT_CRITICAL();
return (OS_MBOX_FULL);
} else {
pevent‐>OSEventPtr = msg; (6)
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
}
}
发送一个消息到邮箱和发送一个信号量也很相似,就是查看有没有任务在等待这个消息,
如果有就把那个任务从睡眠态拉回就绪态。
代码的详细解释如下:
检查了事件控制块是否是一个邮箱后[ (1)],OSMboxPost()函数还要检查是否有任务在等
待该邮箱中的消息[ (2)]。如果事件控制块中的OSEventGrp 域包含非零值,就暗示着有任务
在等待该消息。这时,调用OSEventTaskRdy()将其中的最高优先级任务从等待列表中删除[ (3)],
加入系统的就绪任务列表中,准备运行。然后,调用OSSched()函数[ (4)],检查该任务是否
是系统中最高优先级的就绪任务。如果是,执行任务切换[仅当OSMboxPost()函数是由任务
调用时],该任务得以执行。如果该任务不是最高优先级的任务,OSSched()返回,OSMboxPost()
的调用函数继续执行。如果没有任何任务等待该消息,指向消息的指针就被保存到邮箱中
[ (6)](假设此时邮箱中的指针不是非NULL 的[ (5)])。这样,下一个调用OSMboxPend()函数
的任务就可以立刻得到该消息了。

ucos II 任务间 通信之五:消息队列1
如果把邮箱比作是信号量的升级版,那消息队列就是邮箱的升级版。邮箱可以实现从一
个任务向另外一个任务发送一个指针变量,消息队列则可以实现从一个任务向另外一个任务
发送很多个指针变量。而且每个指针指向的数据结构变量也可以有所不同。
使用消息队列需要注意的地方是:一个任务或者中断服务子程序可以调用OSQPost(),
OSQPostFront(),OSQFlush(),或者OSQAccept()函数。但是,只有任务可以调用OSQPend()
和OSQQuery()函数。
我们知道,每个事件有一个对应事件控制块,用于记录有关于这个事件的信息。消息队
列也不例外,一个消息队列对应着一个队列控制块。
相对信号量,邮箱而言,ucos II 多定义了一个数据结构来存储消息队列的信息。定义
如下:
typedef struct os_q {
struct os_q *OSQPtr;
void **OSQStart;
void **OSQEnd;
void **OSQIn;
void **OSQOut;
INT16U OSQSize;
INT16U OSQEntries;
} OS_Q;
.OSQPtr 在空闲队列控制块中链接所有的队列控制块。一旦建立了消息队列,该域就不
再有用了。
.OSQStart 是指向消息队列的指针数组的起始地址的指针。用户应用程序在使用消息队
列之前必须先定义该数组。
.OSQEnd 是指向消息队列结束单元的下一个地址的指针。该指针使得消息队列构成一个
循环的缓冲区。
.OSQIn 是指向消息队列中插入下一条消息的位置的指针。当.OSQIn 和.OSQEnd 相等
时,.OSQIn 被调整指向消息队列的起始单元。
.OSQOut 是指向消息队列中下一个取出消息的位置的指针。当.OSQOut 和.OSQEnd 相等
时,.OSQOut 被调整指向消息队列的起始单元。
.OSQSize 是消息队列中总的单元数。该值是在建立消息队列时由用户应用程序决定的。
在μC/OS‐II 中,该值最大可以是65,535。
.OSQEntries 是消息队列中当前的消息数量。当消息队列是空的时,该值为0。当消息队
列满了以后,该值和.OSQSize 值一样。 在消息队列刚刚建立时,该值为0。
Ucos II 提供了7 个对消息队列进行操作的函数
􀁺 建立一个消息队列,OSQCreate()
􀁺 等待一个消息队列中的消息,OSQPend()
􀁺 向消息队列发送一个消息(FIFO),OSQPost()
􀁺 向消息队列发送一个消息(后进先出 LIFO),OSQPostFront()
􀁺 无等待地从一个消息队列中取得消息, OSQAccept()
􀁺 清空一个消息队列, OSQFlush()
􀁺 查询一个消息队列的状态,OSQQuery()
消息队列最根本的部分是一个循环缓冲区,其中的每个单元包含一个指针。队列未满
时,.OSQIn 指向下一个存放消息的地址单元。如果队列已满(.OSQEntries 与.OSQSize 相
等),.OSQIn 则与.OSQOut 指向同一单元。如果在.OSQIn 指向的单元插入新的指向消息的指
针,就构成FIFO(First‐In‐First‐Out)队列。相反,如果在.OSQOut 指向的单元的下一个单元
插入新的指针,就构成LIFO 队列(Last‐In‐First‐Out)。当.OSQEntries 和.OSQSize 相等时,说
明队列已满。消息指针总是从.OSQOut 指向的单元取出。指针.OSQStart 和.OSQEnd 定义了消
息指针数组的头尾,以便在.OSQIn 和.OSQOut 到达队列的边缘时,进行边界检查和必要的指
针调整,实现循环功能

ucos II 任务间 通信之五:消息队列2
消息队列通常可以应用以下两个地方;
􀁺 存储外部事件:外部事件由中断收集,然后存储到队列。
􀁺 串口接收程序中的接收循环缓冲区,可理解为消息队列。
使用一个消息队列的步骤如下;
􀁺 建立一个指向消息数组的指针和数组的大小,该指针数组必须申明为void 类型,如下:
void *MyArrayOfMsg[SIZE];
􀁺 声明一个 OS_EVENT 类型的指针指向生成的队列,如下:
OS_EVENT *QSem;
􀁺 调用 OSQcreate()函数创建消息队列,如下:
􀁺 QSem = OSQcreate(&MyArrayOfMsg[0],SIZE);
􀁺 等待消息队列中的消息,OSQPend()。
􀁺 向消息队列发送一则消息。
建立一个消息队列,OSQCreate()的实现代码如下:
OS_EVENT *OSQCreate (void **start, INT16U size)
{
OS_EVENT *pevent;
OS_Q *pq;
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; (1)
if (OSEventFreeList != (OS_EVENT *)0) {
OSEventFreeList = (OS_EVENT *)OSEventFreeList‐>OSEventPtr; (2)
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) {
OS_ENTER_CRITICAL();
pq = OSQFreeList; (3)
if (OSQFreeList != (OS_Q *)0) {
OSQFreeList = OSQFreeList‐>OSQPtr;
}
OS_EXIT_CRITICAL();
if (pq != (OS_Q *)0) {
pq‐>OSQStart = start; (4)
pq‐>OSQEnd = &start[size];
pq‐>OSQIn = start;
pq‐>OSQOut = start;
pq‐>OSQSize = size;
pq‐>OSQEntries = 0;
pevent‐>OSEventType = OS_EVENT_TYPE_Q; (5)
pevent‐>OSEventPtr = pq; (6)
OSEventWaitListInit(pevent); (7)
} else {
OS_ENTER_CRITICAL();
pevent‐>OSEventPtr = (void *)OSEventFreeList; (8)
OSEventFreeList = pevent;
OS_EXIT_CRITICAL();
pevent = (OS_EVENT *)0;
}
}
return (pevent); (9)
}
它和创建邮箱,创建信号量过程是很相似的,首先申请控制块,接着初始化这个控制块,
和创建邮箱,信号量不同的,创建消息队列过程是多申请了一个队列控制块。具体看下文解
释。
OSQCreate()首先从空闲事件控制块链表中取得一个事件控制块 [ (1)],并对剩下的空闲
事件控制块列表的指针做相应的调整,使它指向下一个空闲事件控制块[ (2)]。接着,
OSQCreate()函数从空闲队列控制块列表中取出一个队列控制块[ (3)]。如果有空闲队列控制块
是可以的, 就对其进行初始化[ (4)] 。然后该函数将事件控制块的类型设置为
OS_EVENT_TYPE_Q [ (5)],并使其.OSEventPtr 指针指向队列控制块[ (6)]。OSQCreate()还要调
用OSEventWaitListInit()函数对事件控制块的等待任务列表初始化[ (7)]。因为此时消息队列正
在初始化,显然它的等待任务列表是空的。最后,OSQCreate()向它的调用函数返回一个指
向事件控制块的指针[ (9)]。该指针将在调用OSQPend(),OSQPost(),OSQPostFront(),OSQFlush(),
OSQAccept()和OSQQuery()等消息队列处理函数时使用。因此,该指针可以被看作是对应消
息队列的句柄。值得注意的是,如果此时没有空闲的事件控制块,OSQCreate()函数将返回
一个NULL 指针。如果没有队列控制块可以使用,为了不浪费事件控制块资源,OSQCreate()
函数将把刚刚取得的事件控制块重新返还给空闲事件控制块列表 [(8)]。
http://blog.csdn.net/h32dong809/article/details/7082501

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有