目录
系列文章目录
前言:
我们都知道其实是操作系统中存在很多个任务,之前的文章也提到了任务的相关操作方法,具体内容可以翻看之前的文章,这篇文章我们主要介绍一下操作系统中的各个任务之间的通讯方式。
操作系统是一个大的应用程序,它需要各个任务之间的有效合作才能完成工作。比如说任务之间的共享内存的访问,或者是相互之间的依赖,一个任务需要另一个任务提供必要的执行条件等等。那么任务之间的通讯与同步就显得尤为重要了。
本节的主要内容有:
●事件及描述事件的数据结构事件控制块;
●信号量及其使用;
●消息邮箱及其使用;
●消息队列及其使用。
一:任务间的同步
为了实现各个任务之间的合作和无冲突的运行,在各任务之间必须建立一些制约关系,其中一种制约关系叫做直接制约关系,另一种制约关系则叫做间接制约关系。
直接制约关系源于任务之间的合作。例如,有任务A和任务B两个任务,它们需要通过访问同一个数据缓冲区合作完成一项工作,任务A负责向缓冲区写入数据,任务B负责从缓冲区读取该数据。显然,当任务A还未向缓冲区写入数据时(缓冲区为空时),任务B因不能从缓冲区得到有效数据而应该处于等待状态;只有等任务A向缓冲区写入了数据之后,才应该通知任务B去取数据。相反,当缓冲区的数据还未被任务B读取时(缓冲区为满时),任务A就不能向缓冲区写入新的数据而应该处于等待状态;只有等任务B自缓冲区读取数据后才应该通知任务A写入数据。显然,如果这两个任务不能如此协调工作,将势必造成严重的后果。
间接制约关系源于对资源的共享。例如,任务A和任务B共享一台打印机,如果系统已经把打印机分配给了任务A,则任务B因不能获得打印机的使用权而应该处于等待状态;只有当任务A把打印机释放后,系统才能唤醒任务B使其获得打印机的使用权。如果这两个任务不这样做,那么也会造成极大的混乱。
由上可知,在多任务合作工作的过程中,操作系统应该解决两个问题:
- 一是各任务间应该具有一种互斥关系,即对于某个共享资源,如果一个任务正在使用,则其他任务只能等待,等到该任务释放该资源后,等待的任务之一才能使用它;
- 二是相关的任务在执行上要有先后次序一个任务要等其伙伴发来通知,或建立了某个条件后才能继续执行,否则只能等待。任务之间的这种制约性的合作运行机制叫做任务间的同步。
二:事件
uC/os-I使用信号量、邮箱(消息邮箱)和消息队列这些中间环节来实现任务之间的通信。为了方便起见,这些中间环节都统一被称作“事件”。

上图是两个任务通过事件进行通信的示意图。任务1是发信方,任务2是收信方。作为发信方,任务1的责任是把信息发送到事件上,这项操作叫做发送事件。作为收信方,任务2的责任是通过读事件操作对事件进行查询:如果有信息,则读取信息;否则等待。读事件作叫做请求事件。
1.信号量
信号量是一类事件。使用信号量的最初目的,是为了给共享资源设立一个标志,该标志表示该共享资源被占用情况。这样,当一个任务在访问共享资源之前,就可以先对这个标志进查询,从而在了解资源被占用的情况之后再来决定自己的行为。
观察一下人们日常生活中常用的一种共享资源公用电话亭的使用规则,就会发现这种规则很适合在协调某种资源用户关系时使用。如果一个电话亭只允许一个人进去打电话,那么电话亭的门上就应该有一个可以变换两种颜色的牌子(例如,用红色表示“有人”,用绿色表示“无人”)。当有人进去时,牌子会变成色;出来时,牌子又会变成绿色。这样来打电话的人就可根据牌子的颜色来了解电话亭的被占用情况。路口上的交通信号灯,所以人们最初给这种标志起的名称就是信号灯,后来因为它含有了量的概念,所以又叫做信号量。
显然,对于上面介绍的红绿标志来说,这是一个二值信号量,而且由于它可以实现共享资源的独占式占用,所以被叫做互斥型信号量。
如果电话亭可以允许多人打电话,那么电话亭门前就不应该是那种只有红色和绿色两种颜色状态的牌子,而应该是一个计数器,计数器在每进去一个人时会自动减1,而每出去个人时会自动加1。如果其初值按电话亭的最大容量来设置,那么来人只要见到计数器的值大于0,就可以进去打电话;否则只好等待这种计数式的信号叫做信号量

上图两个任务在使用互斥型信号量进行通信,从而可使这两个任务无冲突地访问共享资源的示意图。任务1在访问共享资源之前先进行请求信号量的操作,当任务1发现信号量的标志为“1”时,它一方面把信号量的标志由“1”改为“0”,访问共享资源。任务2在任务1已经获得信号之后来请求信号量,那么由于它获得的标志值是“0”所以任务2就只有等待而不能访问共享资源了。显然,这种做法可以有效地止两个任务同时访问同一个共享资源所造成的冲突。
那么任务2何时可以访问共享资源呢?当然是在任务1使用完共享资源之后,由任务向信号量发信号使信号量标志的值由“0”再变为“1”时,任务2就有机会访问共享资源了。与任务1一样,任务2一旦获得了共享资源的访问权,那么在访问共享资源之前一定要把信号量标志的值由“1”变为“0”。
2.消息邮箱
在多任务操作系统中,常常需要在任务与任务之间通过传递一个数据(这种数据叫做“消息”)的方式来进行通信。为了达到这个目的,可以在内存中创建一个存储空间作为该数据的缓冲区。如果把这个缓冲区叫做消息缓冲区,那么在任务间传递数据(消息)的一个最简单的方法就是传递消息缓冲区的指针。因此,用来传递消息缓冲区指针的数据结构就叫做消息邮箱。

上图两个任务使用消息邮箱进行通信的示意图。任务1在向消息邮箱发送消息,任务2在从消息邮箱读取消息。读取消息也叫做请求消息。
3.消息队列
上面谈到的消息邮箱不仅可用来传递一个消息,而且也可定义一个指针数组。让数组的每个元素都存放一个消息缓冲区指针,那么任务就可通过传递这个指针数组指针的方法来传递多个消息了。这种可以传递多个消息的数据结构叫做消息队列。

上图是两个任务使用消息队列进行通信的示意图。任务1向消息队列发送消息缓冲区指针数组的指针,这个操作叫做发送消息队列;任务2在从消息队列读取消息缓冲区指针数组的指针,这个操作叫做请求消息队列。
三:事件控制块级相关操作函数
所有的通信信号都被看成是事件(event), uC/OS-II通过事件控制块(ECB)来管理每一个具体事件。
1:事件控制块结构
ECB数据结构如下,与TCB类似的结构,使用两个链表,空闲链表与使用链表。
typedef struct {
void *OSEventPtr; /*指向消息或消息队列的指针*/
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//等待任务列表
INT16U OSEventCnt; /*计数器(当事件是信号量时)*/
INT8U OSEventType; /*事件类型:信号量、邮箱等*/
INT8U OSEventGrp; /*等待任务组*/
} OS_EVENT;
每个等待事件发生的任务都被加入到该事件事件控制块中的等待任务列表中,该列表包括.OSEventGrp 和.OSEventTbl[]两个域。
所有的任务的优先级被分成 8 组(每组 8 个优先级),分别对应.OSEventGrp 中的 8 位,每个优先级就代表着一个任务。当某组中有任务处于等待该事件的状态时,.OSEventGrp 中对应的位就被置位。相应地,该任务在.OSEventTbl[]中的对应位也被置位。
.OSEventTbl[]数组的大小由系统中任务的最低优先级决定,这个值由 uCOS_II.H 中的 OS_LOWEST_PRIO 常数定义。当一个事件发生后,该事件的等待事件列表中优先级最高的任务,也即在.OSEventTbl[]中,所有被置 1 的位中,优先级代码最小的任务得到该事件。下图给出了.OSEventGrp和.OSEventTbl[]之间的对应关系。该关系可以描述为
| 当.OSEventTbl[0]中的任何一位为 1 时,.OSEventGrp 中的第 0 位为 1。 |
| 当.OSEventTbl[1]中的任何一位为 1 时,.OSEventGrp 中的第 1 位为 1。 |
| 当.OSEventTbl[2]中的任何一位为 1 时,.OSEventGrp 中的第 2 位为 1。 |
| 当.OSEventTbl[3]中的任何一位为 1 时,.OSEventGrp 中的第 3 位为 1。 |
| 当.OSEventTbl[4]中的任何一位为 1 时,.OSEventGrp 中的第 4 位为 1。 |
| 当.OSEventTbl[5]中的任何一位为 1 时,.OSEventGrp 中的第 5 位为 1。 |
| 当.OSEventTbl[6]中的任何一位为 1 时,.OSEventGrp 中的第 6 位为 1。 |
| 当.OSEventTbl[7]中的任何一位为 1 时,.OSEventGrp 中的第 7 位为 1。 |

2:相关操作函数
在µC/OS-II 中,事件控制块的总数由用户所需要的信号量、邮箱和消息队列的总数决定。该值由 OS_CFG.H 中的#define OS_MAX_EVENTS 定义。在调用 OSInit()时,所有事件控制块被链接成一个单向链表——空闲事件控制块链表,如下图所示。每当建立一个信号量、邮箱或者消息队列时,就从该链表中取出一个空闲事件控制块,并对它进行初始化。因为信号量、邮箱和消息队列一旦建立就不能删除,所以事件控制块也不能放回到空闲事件控制块链表中

对于事件控制块进行的一些通用操作包括:
- 初始化一个事件控制块
- 使一个任务进入就绪态
- 使一个任务进入等待该事件的状态
- 因为等待超时而使一个任务进入就绪态
为了避免代码重复和减短程代码长度,µC/OS-II 将上面的操作用 4 个系统函数实现,它们是:OSEventWaitListInit(),OSEventTaskRdy(),OSEventWait()和 OSEventTO()
OSEventWaitListInit(OS_EVENT *pevent)
初始化一个事件控制块。当创建一个信号量、邮箱或消息队列时,相应的创建函数会调用本函数对ECB的内容进行初始化,将OSEventGrp和OSEventTbl[]数组清零;
prevent:指向需要初始化的事件控制块的指针。
void OSEventWaitListInit (OS_EVENT *pevent)
{
INT8U i;
pevent->OSEventGrp = 0x00;
for (i = 0; i < OS_EVENT_TBL_SIZE; i++) {
pevent->OSEventTbl[i] = 0x00;
}
}
OSEventTaskRdy (OS_EVENT *pevent,void *msg, INT8U msk);
使一个任务进入就绪态。当一个事件发生时,需要将其等待任务列表中的最高优先级任务置为就绪态;
msg:指向消息的指针;msk:用于设置TCB的状态。
void OSEventTaskRdy (OS_EVENT *pevent, void *msg, INT8U msk)
{
OS_TCB *ptcb;
INT8U x;
INT8U y;
INT8U bitx;
INT8U bity;
INT8U prio;
y = OSUnMapTbl[pevent->OSEventGrp];
bity = OSMapTbl[y];
x = OSUnMapTbl[pevent->OSEventTbl[y]];
bitx = OSMapTbl[x];
prio = (INT8U)((y << 3) + x);
if ((pevent->OSEventTbl[y] &= ~bitx) == 0) {
pevent->OSEventGrp &= ~bity;
}
ptcb = OSTCBPrioTbl[prio];
ptcb->OSTCBDly = 0;
ptcb->OSTCBEventPtr = (OS_EVENT *)0;
#if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN
ptcb->OSTCBMsg = msg;
#else
msg = msg;
#endif
ptcb->OSTCBStat &= ~msk;
if (ptcb->OSTCBStat == OS_STAT_RDY) {
OSRdyGrp |= bity; (13)
OSRdyTbl[y] |= bitx;
}
}
OSEventTaskWait(OS_EVENT *pevent)
使一个任务进入等待状态。当某个任务要等待一个事件的发生时,需要调用本函数将该任务从就绪任务表中删除,并放到相应事件的等待任务表中;
void OSEventTaskWait (OS_EVENT *pevent)
{
OSTCBCur->OSTCBEventPtr = pevent;
if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) {
OSRdyGrp &= ~OSTCBCur->OSTCBBitY;
}
pevent->OSEventTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX;
pevent->OSEventGrp |= OSTCBCur->OSTCBBitY;
}
OSEventTO(OS_EVENT *pevent)
由于等待超时而将任务置为就绪态。如果一个任务等待的事件在预先指定的时间内没有发生,需要调用本函数将该任务从等待列表中删除,并把它置为就绪状态;
void OSEventTO (OS_EVENT *pevent)
{
if ((pevent->OSEventTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0)
{
pevent->OSEventGrp &= ~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY;
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;
}
本文介绍了实时操作系统中任务间的同步机制与通信方式,包括信号量、消息邮箱和消息队列等概念及其应用。探讨了任务间如何通过这些机制实现资源访问的同步与数据的传递。
868

被折叠的 条评论
为什么被折叠?



