µC/OS-II通过uCOS_II.H 中定义的OS_EVENT数据结构来维护一个事件控制块的所有信息[程序清单L6.1],也就是本章开篇讲到的事件控制块ECB。该结构中除了包含了事件本身的定义,如用于信号量的计数器,用于指向邮箱的指针,以及指向消息队列的指针数组等,还定义了等待该事件的所有任务的列表。程序清单 L6.1是该数据结构的定义。
程序清单 L6.1 ECB数据结构 |
typedef struct { |
void *OSEventPtr; /* 指向消息或者消息队列的指针 */ |
INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; /* 等待任务列表 */ |
INT16U OSEventCnt; /* 计数器(当事件是信号量时) */ |
INT8U OSEventType; /* 时间类型 */ |
INT8U OSEventGrp; /* 等待任务所在的组 */ |
} OS_EVENT; |
.OSEventPtr指针,只有在所定义的事件是邮箱或者消息队列时才使用。当所定义的事件是邮箱时,它指向一个消息,而当所定义的事件是消息队列时,它指向一个数据结构,详见6.06节消息邮箱和6.07节消息队列。
.OSEventTbl[] 和 .OSEventGrp 很像前面讲到的OSRdyTbl[]和OSRdyGrp,只不过前两者包含的是等待某事件的任务,而后两者包含的是系统中处于就绪状态的任务。(见3.04节 就绪表)
.OSEventCnt 当事件是一个信号量时,.OSEventCnt是用于信号量的计数器,(见6.05节信号量)。
.OSEventType定义了事件的具体类型。它可以是信号量(OS_EVENT_SEM)、邮箱(OS_EVENT_TYPE_MBOX)或消息队列(OS_EVENT_TYPE_Q)中的一种。用户要根据该域的具体值来调用相应的系统函数,以保证对其进行的操作的正确性。
每个等待事件发生的任务都被加入到该事件事件控制块中的等待任务列表中,该列表包括.OSEventGrp和.OSEventTbl[]两个域。变量前面的[.]说明该变量是数据结构的一个域。在这里,所有的任务的优先级被分成8组(每组8个优先级),分别对应.OSEventGrp中的8位。当某组中有任务处于等待该事件的状态时,.OSEventGrp中对应的位就被置位。相应地,该任务在.OSEventTbl[]中的对应位也被置位。.OSEventTbl[]数组的大小由系统中任务的最低优先级决定,这个值由uCOS_II.H中的OS_LOWEST_PRIO常数定义。这样,在任务优先级比较少的情况下,减少µC/OS-II对系统RAM的占用量。
当一个事件发生后,该事件的等待事件列表中优先级最高的任务,也即在.OSEventTbl[]中,所有被置1的位中,优先级代码最小的任务得到该事件。图 F6.2给出了.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。
下面的代码将一个任务放到事件的等待任务列表中。
程序清单 L6.2——将一个任务插入到事件的等待任务列表中 |
pevent->OSEventGrp |= OSMapTbl[prio >> 3]; |
pevent->OSEventTbl[prio >> 3] |= OSMapTbl[prio & 0x07]; |
其中,prio是任务的优先级,pevent是指向事件控制块的指针。
从程序清单 L6.2可以看出,插入一个任务到等待任务列表中所花的时间是相同的,和表中现有多少个任务无关。从图 F6.2中可以看出该算法的原理:任务优先级的最低3位决定了该任务在相应的.OSEventTbl[]中的位置,紧接着的3位则决定了该任务优先级在.OSEventTbl[]中的字节索引。该算法中用到的查找表OSMapTbl[](定义在OS_CORE.C中)一般在ROM中实现。
表T6.1 OSMapTbl[]
|
|
Index |
Bit Mask (Binary) |
0 |
00000001 |
1 |
00000010 |
2 |
00000100 |
3 |
00001000 |
4 |
00010000 |
5 |
00100000 |
6 |
01000000 |
7 |
10000000 |
从等待任务列表中删除一个任务的算法则正好相反,如程序清单 L6.3所示。
程序清单 L6.3 从等待任务列表中删除一个任务 |
if ((pevent->OSEventTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) == 0) { |
pevent->OSEventGrp &= ~OSMapTbl[prio >> 3]; |
} |
该代码清除了任务在.OSEventTbl[]中的相应位,并且,如果其所在的组中不再有处于等待该事件的任务时(即.OSEventTbl[prio>>3]为0),将.OSEventGrp中的相应位也清除了。和上面的由任务优先级确定该任务在等待表中的位置的算法类似,从等待任务列表中查找处于等待状态的最高优先级任务的算法,也不是从.OSEventTbl[0]开始逐个查询,而是采用了查找另一个表OSUnMapTbl[256](见文件OS_CORE.C)。这里,用于索引的8位分别代表对应的8组中有任务处于等待状态,其中的最低位具有最高的优先级。用这个值索引,首先得到最高优先级任务所在的组的位置(0~7之间的一个数)。然后利用.OSEventTbl[]中对应字节再在OSUnMapTbl[]中查找,就可以得到最高优先级任务在组中的位置(也是0~7之间的一个数)。这样,最终就可以得到处于等待该事件状态的最高优先级任务了。程序清单 L6.4是该算法的具体实现代码。
程序清单 L6.4 在等待任务列表中查找最高优先级的任务 |
y = OSUnMapTbl[pevent->OSEventGrp]; |
x = OSUnMapTbl[pevent->OSEventTbl[y]]; |
prio = (y << 3) + x; |
举例来说,如果.OSEventGrp的值是01101000(二进制),而对应的OSUnMapTbl[.OSEventGrp]值为3,说明最高优先级任务所在的组是3。类似地,如果.OSEventTbl[3]的值是11100100(二进制),OSUnMapTbl[.OSEventTbl[3]]的值为2,则处于等待状态的任务的最高优先级是3×8+2=26。
在µC/OS-II中,事件控制块的总数由用户所需要的信号量、邮箱和消息队列的总数决定。该值由OS_CFG.H 中的#define OS_MAX_EVENTS定义。在调用OSInit()时(见3.11节,µC/OS-II的初始化),所有事件控制块被链接成一个单向链表——空闲事件控制块链表(图 F6.3)。每当建立一个信号量、邮箱或者消息队列时,就从该链表中取出一个空闲事件控制块,并对它进行初始化。因为信号量、邮箱和消息队列一旦建立就不能删除,所以事件控制块也不能放回到空闲事件控制块链表中。
对于事件控制块进行的一些通用操作包括:
Ÿ 初始化一个事件控制块
Ÿ 使一个任务进入就绪态
Ÿ 使一个任务进入等待该事件的状态
Ÿ 因为等待超时而使一个任务进入就绪态
为了避免代码重复和减短程代码长度,µC/OS-II将上面的操作用4个系统函数实现,它们是:OSEventWaitListInit(),OSEventTaskRdy(),OSEventWait()和OSEventTO()。