本文框架
1.概述
本系列RTA OS系列入门介绍,会详细介绍OS相关基础知识,如您对Autosar中的MCAL配置,通信,诊断,OS等Autosar全栈实战有更高需求,可以参见AutoSar 实战进阶系列专栏,快速链接:AutoSar实战进阶系列导读
在《RTA-OS系列介绍-Task》部分我们介绍了任务分为基础任务与扩展任务,两者的主要区别为,扩展任务多了waiting状态,那Waiting状态等待的是什么呢?其实就是我们今天要介绍的Events(事件),当系统中的Task或ISR设置事件后,等待的任务将转到Ready状态。当它成为最高优先级就绪任务时,RTA-OS将选择运行该Task。
在AUTOSAR操作系统中,事件用于向任务发送信号信息,主要用于为扩展任务提供多个同步点。本文将对什么是事件,如何配置事件以及如何在运行时使用它们。Events的使用场景大致如下图所示。
2. Events配置
正常在应用中可配置的Events的最大数量取决于硬件,而Events需要配置的内容包括:名字、至少一个Task使用及Event mask。
设置事件时,必须同时指定任务。因此,例如,如果为名为Task1的任务设置名为Event0的事件,则这对任务Task2的Event0没有影响。
2.1 定义等待任务
在使用中,当我们声明某个Task需要等待一个Event时,系统将默认该任务为扩展任务,等待事件的扩展任务通常会自动启动(等待的时间满足后),并且任务永远不会终止。当任务开始执行时,RTA-OS将清除它拥有的所有事件。
3. 如何使用Event
3.1 等待事件
任务的等待事件需要调用WaitEvent(EventMask) API,具体等待的EventMask需要关联到提前声明的内容。
WaitEvent()将事件作为其唯一参数。执行调用时,有两种可能:
1)事件暂未发生。这种情况下该Task会进入等待状态,RTA-OS会运行Ready状态中优先级最高的Task。
2)事件已经发生。在这种情况下,任务将保持在运行状态,并将在WaitEvent()调用之后的语句中继续执行。
3.1.1 等待单一事件
要等待单个事件,只需将事件掩码名称传递给API调用。下面示例显示了任务如何使用等待事件。
#include <Os.h>
TASK(ExtendedTask) {
...
WaitEvent(Event1); /* Task enters waiting state in API call if
Event1 has not happened */
/* When Event1 is set, ExtendedTask resumes here */
...
}
在AUTOSAR操作系统中,为处于挂起状态的任务设置事件是非法的。实际上,这意味着等待事件的任务结构通常是一个等待事件的有限循。
3.1.2 等待多个事件
因为AUTOSAR OS事件只是一个位掩码(Bit Mask),所以用户可以通过按位设置一组位掩码,同时等待多个事件。
当任务等待多个事件时,当等待的任何一个事件发生时,它将恢复。当从等待多个事件恢复时,将需要确定发生了哪些事件。
#include <Os.h>
TASK(ExtendedTask){
EventMaskType WhatHappened;
while(true){
WaitEvent(Event1|Event2|Event3);
GetEvent(Task1, &WhatHappened);
if( WhatHappened & Event1 ) {
/* Take action on Event1 */
...
} else if( WhatHappened & Event2 ) {
/* Take action on Event2 */
...
} else if( WhatHappened & Event3 ) {
/* Take action on Event3 */
...
}
}
}
在AUTOSAR-OS中,提供了GetEvent()的API,我们可以通过该API获知哪个事件已完成。
3.1.3 扩展任务的死锁
虽然AUTOSAR操作系统在关键部分的资源互斥中提供了免于死锁的自由,但在构建具有可能死锁的事件的系统时,仍不会受到保护。如果我们有相互设置和等待事件集的扩展任务,则两个(或更多)任务可能正在等待仅由其他正在等待的任务设置的事件。当然,即使存在死锁扩展任务,系统中的基本任务也不可能死锁。
下面的样例展示了扩展任务的死锁:
#include <Os.h>
TASK(Task1) {
while (1) {
WaitEvent(Ev1);
/* Never reach here - DEADLOCKED with Task2! */
SetEvent(Task2,Ev2);
}
}
TASK(Task2) {
while (1) {
WaitEvent(Ev2);
/* Never reach here - DEADLOCKED with Task1! */
SetEvent(Task1,Ev1);
}
}
OS配置不获取哪些任务或ISR设置了事件,只获取哪些任务可以等待事件。因此,RTA-OS不可能静态地确定扩展任务是否会死锁。采用下面的设计方法可能会避免类似问题:
•仅使用基本任务;
•分析代码,以表明在所有SetEvent()或WaitEvent()对的传递闭包上没有循环等待事件。
3.2 设置事件
通过SetEvent() API 来设置事件。
SetEvent()调用有两个参数,一个任务和一个事件掩码。对于指定的任务,SetEvent()调用设置事件掩码中指定的事件。该调用不会为共享事件的任何其他任务设置事件。
在调用SetEvent()时,可以按位或多个事件掩码来同时为任务设置多个事件。
无法为处于挂起状态的任务设置事件。因此,在设置事件之前,必须确保任务未挂起。您可以使用GetTaskState()API调用来实现这一点,但请注意,当为优先级高于调用方的任务调用此函数时,可能存在竞争条件。调用方可以在对API的调用和对结果的评估之间被抢占,并且被请求的任务的状态在中间时间内可能已经改变。
当扩展任务正在等待的任何一个事件被设置时,扩展任务将从等待状态移动到就绪状态。
如下任务显示了任务如何设置事件:
#include <Os.h>
TASK(Task1) {
TaskStateType TaskState;
/* Set a single event */
SetEvent(Task2, Event1);
/* Set multiple events */
SetEvent(Task3, Event1 | Event2 | Event3);
...
/* Checking for the suspended state */
GetTaskState(Task2,&TaskState);
if (TaskState != SUSPENDED) {
SetEvent(Task2, Event1);
}
...
TerminateTask();
}
多个任务可以同时等待同一个事件,然而从上面例子可以看出,事件没有广播机制,换句话说,不能通过调用一个API告诉所有等待的任务该事件已经发生。
此外,也可以通过Alarms及调度表来设置事件。
3.2.1通过Alarm设置事件
Alarm可用于定期激活不终止的扩展任务。每次Alarm到期时,都会设置该事件。等待事件的任务随后准备好运行。
3.2.2 通过带有到期点的调度表设置事件
调度表上的到期点可用于编程(a)非终止状态的扩展任务的定期激活。每次处理到期点时,都会设置事件。等待事件的任务随后准备好运行。
3.3 清除Events
可以通过Task或者ISRs来设置Event,但是Event只能被其owner清除。
#include <Os.h>
TASK(ExtendedTask){
EventMaskType WhatHappened;
...
while( WaitEvent(Event1|Event2|Event3)==E_OK ) {
GetEvent(Task1, & WhatHappened);
if(WhatHappened & Event1 ) {
ClearEvent(Event1);
/* Take action on Event1 */
...
} else if( WhatHappened & (Event2 | Event3 ) {
ClearEvent(Event2 | Event3);
/* Take action on Event2 or Event3*/
...
}
}
}
当某个任务等待某个事件,该事件发生,在后面时序再次对同一个事件调用WaitEvent()时,由于该事件仍处于Set状态,会立即返回。因此,在再次调用等待事件前需要将之前已发生事件清除。
清除事件时调用ClearEvent API,被清除后的状态必须与事件掩码关联起来。
当某个任务被挂起时,其所拥有的Event将被自动清除。
3.4 用基础任务模拟扩展任务
基础任务只能在任务执行的开始或结束时同步。
如还有其他同步节点需要时,可以通过event机制来实现。然而,扩展任务较基础任务占用资源更多,在资源限制的系统中,只能通过使用基础任务来进行 同步。
例如,如果任务构建为状态机(例如,使用C switch语句),则可以设置状态变量,发出TerminateTask()调用并等待重新激活。如下样例代码显示了如何实现这一点。
#include <Os.h>
/* Create a "State" variable that remains in scope between task
activations */
uint8 State;
TASK(Task1) {
switch (State) {
case 0:
/* Synchronization point 0. */
State = 1;
break;
case 1:
/* Synchronization point 1. */
State = 2;
break;
case 2:
/* Synchronization point 2. */
State = 0;
break;
}
TerminateTask();
}
4.本文总结
- Event是用于同步的实体,可用于扩展任务的等待内容;
- 同一个Event可被不同的Task引用;
- Event不具有广播机制,即无法将信息通知所有等待该Event中的任务;
- Tasks,ISRs及调度表都可以设置Events。
- 如果时效性在系统中很重要,则所有扩展任务(任何等待事件的任务)的优先级必须低于基本任务。