AUTOSAR OS模块详解(四) Task&Event
本文主要介绍AUTOSAR OS的Task及Event机制,并对基于英飞凌Aurix TC3XX系列芯片的Vector Microsar代码和配置进行部分讲解。
文章目录
1 简介
上文我们提到,Counter激活Alarm之后,Alarm会执行相应的Job,如果Job是ActivateTask,那么其会激活一个任务,如果Job是SetEvent类型,那么其会调用SetEvent进行周期Event的设置,以期Task执行特定的Rbl。这篇我们就来介绍Autosar Os中Event的作用机制。Event和Task的关系比较紧密,而且Event包含于Task,因此将这两个模块放到一起讨论。
在一般操作系统中,都有线程的概念,线程是CPU运算资源分配的基本单位,也是程序执行的基本单位。与进程相比,线程更加轻量级,能够更有效地利用系统资源。我们将需要进行计算的具体代码业务放置到线程中,然后由操作系统执行该线程。
在Autosar Os中,没有明确指定线程的概念,取而代之的任务——Task。Task是可执行实体Runnable(简称Rbl)的分配分配容器,Rbl里面放置了我们的业务逻辑,分配到Task中,由操作系统进行调度执行。
而事件Event是Autosar Os中静态配置好的,分配到指定的任务中,该任务会等待并轮询所有Event,当某个Event成立时,就会执行相应的Rbl。
在调度层面,Autosar Os使用了基于优先级的调度策略;同时对于开启了时间保护的系统,具有时间片分片功能;另外对于一些资源、自旋锁的获取,也会调整任务的实际优先级,做到优先级反转。
2 功能介绍
2.1 Task介绍
在Autosar Os中,Task分为两类,一类是基础任务(Basici Task),一类是扩展任务(Extended Task),基础任务运行重新结束后需要再次激活才能执行下一轮运算;而扩展任务则是一个无线循环模型,在其中等待并轮询所有的Event,以完成相应的Rbl执行。
Autosar Os中的Task是可以抢占的,抢占的原则是使用优先级队列。高优先级任务能够抢占低优先级任务。任务的优先级以及Event等都是静态配置的。
2.1.1 基础任务Basic Task
基础任务包括三种状态:运行状态(Running)、挂起状态(Suspend)和就绪状态(Ready)。其状态的转化如下图所示。
-
当操作系统启动后,除了自激活的任务以外,都处于挂起状态。
-
当该任务被激活时(由ActivateTask接口进行激活),则处于就绪状态,等待操作系统进行调度。
-
当CPU出现空闲且没有更高优先级任务时,该任务被系统调度,进入运行状态;
-
如果在运行状态中被更高优先级任务抢占,则回到就绪状态;
-
当任务运行完毕之后便会主动结束,进入挂起状态,等待下一次的激活。
基础任务的模型比较简单,初始化之后处于挂起状态由对应的激活方激活之后进入就绪队列,执行之后主动结束任务。
TASK(AppTask)
{
Rbl1();
Rbl2();
...
(void)TerminateTask();
}
基础任务的激活一般情况下是由Alarm进行直接激活。
2.1.2 扩展任务Extended Task
扩展任务相对于基础任务多了一个等待状态(Waiting),因为扩展任务的激活源并不是统一的,当一个任务中的Rbl由各种不同的触发源,比如有些是周期执行,有些是收到信号之后执行,则该任务就需要配置为扩展任务,由事件进行驱动,而非激活。扩展任务的状态转化关系如下图所示。
-
初始化完成之后,扩展任务会进入挂起状态;
-
在RTE启动时会将所有的扩展任务进行激活,以进入就绪状态,但是此时还不会直接执行Rbl;
-
当系统空闲且没有高优先级任务时,该任务就会被调度,随后该扩展任务进入一个无限循环,循环的开头是WaitEvent,然后任务进入等待状态,等待指定事件;
-
当指定事件发生时,该任务会进入就绪状态,等待系统调度;
-
当系统空闲且没有高优先级任务时,该任务被调度,此时WaitEvent函数结束,进入Rbl的执行,但是只执行对应Event被置位的Rbl;
-
所有的Event都被轮询且激活的Rbl都执行完毕之后,回到循环的开头调用WaitEvent,进入等待状态,交出CPU。
扩展任务的任务模型相对于基础任务来说复杂一点,包含WaitEvent、GetEvent、ClearEvent以及事件的判断。
TASK(APPTASK)
{
EventMaskType ev;
for( ; ; )
{
(void)WaitEvent(Event0 | Event1 | Event2);
(void)GetEvent(APPTASK, ev);
(void)ClearEvent(Event0 | Event1 | Event2);
if((ev & Event0) != (EventMaskType)0)
{
Rbl0();
}
if((ev & Event1) != (EventMaskType)0)
{
Rbl1();
}
if((ev & Event2) != (EventMaskType)0)
{
Rbl2();
}
}
}
我们从代码中可以看出,这是一个无限循环的任务模型,当进入该任务时,调用WaitEvent进行事件等待,交出CPU切换到其他任务;当事件发生时,会调度回该任务,执行完WaitEvent,然后将事件通过GetEvent获取到本地,并通过ClearEvent清除全局事件。
在Rbl的执行中,只执行事件发生过的Rbl,而不是像基础任务一样全部执行。
2.1.3 调度机制
优先级调度机制
Autosar Os是按照任务优先级进行调度的。高优先级任务的激活可以抢占低优先级任务,低优先级则不可以抢占高优先级任务。在没有使能时间保护的场景下,默认关闭时间片分片机制,高优先级的任务如果持续占据CPU,低优先级的任务是无法被执行的。关于时间分片的轮询调度算法,后面在关于时间保护的文章里会介绍,这里仅介绍一般优先级调度。
每个核的调度器是独立运行的,在配置完成之后,工具会根据所有Task的优先级创建优先级队列,优先级相同的Task使用同一个队列,一个核有多少种优先级则有多少个队列。将这些队列按照配置优先级依次排序,得到主优先级(HomePriority)。主优先级数字越小代表优先级越高。
如上图中一共4个任务共三个主优先级0、1、2,相应的有三个队列,当任务被激活时会进入预先关联的队列中,调度器从高优先级到低优先级依次清空并执行队列中的Task,相同主优先级的队列中按照先进先出原则进行执行。
优先级调整机制
另外Autosar Os中存在天花板优先级以及优先级反转机制,当任务配置为不可抢占时,其实际运行过程中的优先级为天花板优先级。
如果该任务需要获取资源、SpinLock等,则取该资源和运行优先级的最高值。
调度场景
以下场景会发生调度:
- SetEvent:设置事件后扩展任务可能由等待切换到就绪,因此需要调度;
- TerminateTask:基础任务在完成执行之后释放CPU,需要进行调度;
- WaitEvent:扩展任务执行完本次之后切换到等待状态,释放CPU,需要进行调度;
- 中断服务例程结束:二类中断中可能存在各种任务状态切换,因此推出时需要判断以进行任务调度;
- ReleaseResource:Os释放资源之后原先因为资源阻塞的任务可能能够执行,需要进行调度。
2.2 Event介绍
Event是归属于Task下的一个元素,不同Task的Event之间没有关联。Vector代码中EventMaskType是一个64位的掩码数据类型,每个Event占据一个Bit,也就是一个Task最多配置64个Event。
Os_Event中主要就是四个接口函数SetEvent、WaitEvent、GetEvent和ClearEvent,WaitEvent将Task置于等待状态,并交出CPU计算资源,GetEvent和ClearEvent则是将全局状态位同步到局部变量之后清除全局的操作。SetEvent将Task从等待状态切换到就绪状态,以执行Task中Event关联的Rbl。下文中的代码分析会详细介绍其内部逻辑。
2.3 Task & Event配置
2.3.1 Developer工具
在工程实践中,需要将Rbl配置到Task中,除了Bsw和MCAL中Rbl是系统导入时就存在的,用户的Rbl是需要在Developer工具中创建的,并为其指定Event。下面给出工程自带的示例。
图中的SWC存在多个周期的Rbl,我们看到右侧Trigger则是该Rbl的实际触发周期,这里配置好了以后,系统会自动计算Alarm、Event等元素的配置,无需在Davinci工具中一一配置。
另外有很多非周期事件,由信号交互产生,包括内部信号和外部信号,这里选择相应的Trigger即可关联该Rbl到事件上。关于Developer工具的使用和SWC等的创建,这里不做过多介绍。
2.3.2 Task配置
前面我们提到过,OsApplication作为进程概念存在,是Os中很多元素的容器,因此我们配置Task时最好是直接在目标OsApplication下添加,不然后续工具还是提示分配到一个OsApplication中。
这里我们以Default_BSW_Async_Task_Core0为例,介绍Task配置。
OsTaskActivation
Task可激活的次数,Autosar Os中支持ECC2或BCC2类型Task,即任务的激活可以缓存,在未执行Task的情况下多次激活该Task,会依次执行完多次,典型的生产者消费者模型。如果激活次数大于这里配置的值,则会报对应的OsError,调用ErrorHook,但是系统能够继续正常运行。
OsTaskMemoryProtectionIdentifier
内存保护识别号,Vector Os中支持Task级别的内存保护,但是一般使用是以OsApplication为保护单位,因此这里无需配置。
OsTaskPriority
Task优先级,数字越高优先级越高,高优先级在一定情况下能够抢占低优先级。
OsTaskSchedule
定义了Task的可抢占性,如果配置为NON则即使优先级高的任务也不可被抢占,如果定义为FULL则根据优先级策略执行抢占式调度。
OsTaskStackSharing
基础任务是可以共享栈的,因为Task之间的抢占顺序是固定的,且任务挂起之后栈释放出来了。但是一般情况下配置为否,增强Task的独立性,以及系统鲁棒性。也方便问题排查。
OsTaskStackSize
栈的尺寸,根据工程情况分配。另外Vector Os有监控栈使用的功能,可根据使用量调整。
OsTaskType
基础任务或扩展任务类型,一般我们选择Auto,工具会根据分配的Rbl类型自行决定。
OsTaskUsesFpu
部分芯片如ARM等,其硬件浮点单元是独立的内核,有独立的寄存器,需要初始化,且任务切换时需要进行浮点上下文保护。TC3XX芯片这里无需配置,也不用进行切换。
OsTaskAutostart
Task的自启动,除了系统的默认初始化Task以外,其他Task均通过Rte启动等流程启动。
OsTaskAccessingApplication
定义了能够访问该Task的OsApplication,如果其他OsApplication中存在激活该Task的需求则需要配置。
然后我们展开该Task,可以看到该Task关联的Event和Resource,Event不用配置系统会分配。这里我们打开Mapped Function,这里配置Rbl到Task中的地方。
这里我们看到其中每个Rbl的特性,我们可以选择将哪些Rbl映射到该任务,配置其Offset,配置顺序等。
2.3.3 Event配置
Event仅存在一个Bit的掩码位,因此没有额外的配置,且由工具计算生成,用户无需关注。
2.4 代码分析
2.4.1 主要数据结构
Task数据结构
Task的主要数据结构为Os_TaskConfigType,我们到Os_Task_Lcfg.c文件中可以找到该数据,Task对应的变量名为OsCfg_Task_前缀加上Task的配置名称。
CONST(Os_TaskConfigType, OS_CONST) OsCfg_Task_Default_BSW_Async_Task_Core0 =
{
/* .Thread = */
{
/* .ContextConfig = */ &OsCfg_Hal_Context_Default_BSW_Async_Task_Core0,
/* .Context = */ &OsCfg_Hal_Context_Default_BSW_Async_Task_Core0_Dyn,
/* .Stack = */ &OsCfg_Stack_Default_BSW_Async_Task_Core0,
/* .Dyn = */ OS_TASK_CASTDYN_TASK_2_THREAD(OsCfg_Task_Default_BSW_Async_Task_Core0_Dyn),
/* .OwnerApplication = */ &OsCfg_App_OsApplication_NonTrusted_Core0,
/* .Core = */ &OsCfg_Core_OsCore0,
/* .IntApiState = */ &OsCfg_Core_OsCore0_Dyn.IntApiState,
/* .TimeProtConfig = */ NULL_PTR,
/* .MpAccessRightsInitial = */ &OsCfg_Mp_Default_BSW_Async_Task_Core0,
/* .AccessRights = */ &OsCfg_AccessCheck_NoAccess,
/* .Trace = */ NULL_PTR,
/* .FpuContext = */ NULL_PTR,
/* .InitialCallContext = */ OS_CALLCONTEXT_TASK,
/* .PreThreadHook = */ &Os_TaskCallPreTaskHook,
/* .InitDuringStartUp = */ TRUE,
/* .UsesFpu = */ FALSE
},
/* .HomePriority = */ (Os_TaskPrioType)4uL,
/* .TaskId = */ Default_BSW_Async_Task_Core0,
/* .RunningPriority = */ (Os_TaskPrioType)0uL,
/* .MaxActivations = */ (Os_ActivationCntType)1uL,
/* .AutostartModes = */ OS_APPMODE_NONE,
/* .AccessingApplications = */ (OS_APPID2MASK(OsApplication_NonTrusted_Core0) | ...
/* .NumSchEventsRoundRobin = */ 0uL,
/* .RoundRobinEnabled = */ FALSE,
/* .IsExtended = */ (boolean)TRUE,
/* .StackSharing = */ OS_TASKSCHEDULE_ALLOWED
};
该结构体是该Task的句柄,相当于线程控制块。其中ContextConfig指向的结构体为OsCfg_Hal_Context_Default_BSW_Async_Task_Core0,包含了栈信息等,其中Entry是该Task的入口函数,ReturnAddress是系统调度栈的栈底,非异常情况下不会执行,因为TerminateTask函数中就会调用任务切换。
CONST(Os_Hal_ContextConfigType, OS_CONST) OsCfg_Hal_Context_Default_BSW_Async_Task_Core0 =
{
/* .StackEndAddr = */ (uint32)(OS_STACK_GETHIGHADDRESS(OsCfg_Stack_Default_BSW_Async_Task_Core0_Dyn)+1),
/* .StackStartAddr = */ (uint32)OS_STACK_GETLOWADDRESS(OsCfg_Stack_Default_BSW_Async_Task_Core0_Dyn),
/* .ProgramStatus = */ (uint32)OS_HAL_PSW_IS_MASK | OS_HAL_PSW_CDE_MASK | OS_HAL_PSW_IO_SUPERVISOR | OS_HAL_PSW_S_MASK | OS_HAL_PSW_PRS_PS2,
/* .Entry = */ (uint32)&Os_Task_Default_BSW_Async_Task_Core0,
/* .ReturnAddress = */ (uint32)&Os_TrapTaskMissingTerminateTask,
/* .IntStatus = */ ((uint32)0<<OS_HAL_PCXI_PCPN_BIT_POSITION) | OS_HAL_PCXI_PIE_ENABLED
};
Context所指向的OsCfg_Hal_Context_Default_BSW_Async_Task_Core0_Dyn为该Task的上下文信息,包括返回地址、CSA等信息,如下图所示。
Dyn所指向的OsCfg_Task_Default_BSW_Async_Task_Core0_Dyn(代码中用宏取第一个元素的地址,其实就是结构体的地址)指示了该Task的各种动态状态量,包括运行状态,运行时优先级等。这里的Events成员就是该Task内部的Event状态。
另外值得一提的是HomePriority对应的就是其所在的优先级队列的优先级,而RunningPriority则是其实际运行过程中的优先级,比如一个不可抢占的任务,其HomePriority为4,但是其RunningPriority为0,为天花板优先级。
其他类型这里就不一一介绍了,读者可自行查阅代码。
调度器数据结构
Vector Os中调度器的数据结构是Os_SchedulerConfigType,在Os_Scheduler_Lcfg.c中,每个核有独立的结构体。
CONST(Os_SchedulerConfigType, OS_CONST) OsCfg_Scheduler_OsCore0 =
{
/* .BitArray = */
{
/* .Dyn = */ &OsCfg_Scheduler_OsCore0_BitArray_Dyn,
/* .Data = */ OsCfg_Scheduler_OsCore0_BitField_Dyn,
/* .Size = */ (uint16)OS_BITARRAY_SIZE(OS_CFG_NUM_TASKQUEUES),
/* .BitLength = */ (uint16)OS_BITARRAY_LENGTH(OS_CFG_NUM_TASKQUEUES),
},
/* .Dyn = */ &OsCfg_Scheduler_OsCore0_Dyn,
/* .TaskQueues = */ OsCfg_Scheduler_OsCore0_TaskQueues,
/* .NumberOfPriorities = */ OS_CFG_NUM_TASKQUEUES
};
在一个Core内,用户给每个Task中配置了不同的优先级,从Os的视角来说,有多少种优先级则有多少个优先级队列,相同的优先级放到同一个队列中,队列数量对应这里的OS_CFG_NUM_TASKQUEUES。每个队列是一个环形Buffer,先进先出。也就是说所有相同优先级的任务,先激活的先执行。
这里的BitArray.Dyn指向的OsCfg_Scheduler_OsCore0_BitArray_Dyn用于记录某个优先级是否存在激活任务;Dyn指向的OsCfg_Scheduler_OsCore0_Dyn用于记录当前调度器的状态,比如当前状态优先级等;TaskQueues指向的OsCfg_Scheduler_OsCore0_TaskQueues是前面提到的优先级队列,它是一个数组,数组中每一个元素是一个优先级队列,我们把它展开。
OS_LOCAL CONST(Os_DequeConfigType, OS_CONST) OsCfg_Scheduler_OsCore0_TaskQueues[OS_CFG_NUM_TASKQUEUES] =
{
/* [0] = */
{
/* .Dyn = */ &OsCfg_TaskQueue0_OsCore0_Dyn,
/* .Buffer = */ OsCfg_TaskQueueNodes0_OsCore0_Dyn,
/* .Size = */ OS_CFG_NUM_TASKQUEUE0_OSCORE0_SLOTS
},
...
其中Dyn指向的OsCfg_TaskQueue0_OsCore0_Dyn是环形队列的读写Index,用于组成生产者消费者模型;Buffer指向的OsCfg_TaskQueueNodes0_OsCore0_Dyn为指针数组,是队列的存储空间,Size则为该队列的最大长度,因为Autosar Os是静态配置的,这里是固定的,其值等于所有该优先级的Task的可激活次数的累加。运行时刻的值如下图。
2.4.2 Task初始化阶段
关于Os的初始化,在本系列Counter章节中已经介绍过,这里就不赘述了,与Counter相同,Task的初始化也是在Os_AppInit中进行的,其调用栈如下图所示。
在Task的初始化阶段,首先会通过设置状态将其设置为挂起;
在Os_EventInit中将所有Event状态位初始化为0;
在最后的Os_ThreadInit中操作的流程比较多,除了Service初始化、SpinLock初始化、内存保护初始化(Os_MpThreadInit)、时间保护初始化(Os_TpThreadInit)以外,还会初始化该Task的上下文(Os_Hal_ContextInit)。
在上下文初始化中,可以看到所有的上下文变量都被重新赋值。
另外值得注意的是Vector Os中对Task的CSA的分配使用,Vector代码中线程是相互独立的,一般是各自使用各自的栈,而CSA本身作为链表结构,也是比较容易做成独立结构的。Os_Hal_ContextInit中的两个函数Os_Hal_ContextIntInitializeReserved和Os_Hal_ContextIntInitialize为该Task申请了一个新的CSA链表,并将其调用栈底的LR设置为Os_TrapTaskMissingTerminateTask,将次栈低的LR设置为Task的入口地址。
如下图为该系统中的CSA关系示意图,每个任务都可对空闲链表进行使用和归还,相互之间不受干扰。关于TC3XX CSA的硬件机制,读者可阅读TriCore上下文机制那篇。
2.4.3 Task启动阶段
对于基础任务,它的激活是由Alarm或者实际运行过程中的某些场景进行激活的,而不是在初始化阶段进行启动的。而扩展任务则需要在启动阶段进行激活,正常情况下便不再进入挂起状态,而是在等待、运行、就绪三个状态切换。所以这里介绍扩展任务的启动。
与Alarm相同,扩展任务的的启动是在EcuM的StartupTwo阶段完成的,关于EcuM的启动时序,这里不做过多介绍,后续聊到EcuM模块时再做解读。
在Os_Api_ActivateTask函数中,会调用Os_TaskActivateLocalTask将Task的状态设置为就绪,并将其插入到调度器的队列中。
2.4.4 基础任务调度
激活
对于基础任务来说,其生命周期是从ActivateTask被激活,直到运行完毕然后执行完毕,其代码模型尾部跟随了一个TerminateTask函数,该函数会将Task状态重置,归还激活次数,并进行任务调度。
Task的激活一般是由Counter触发Alarm,Alarm在Job中激活,或者Rte在初始化中激活初始化任务。前面的流程在之前的文章里有介绍,本文主要着重于Task激活之后的行为。另外这里仅讨论本核内任务激活,跨核场景不讨论。
在Task激活的过程中,首先会消耗一次Task激活次数,然后将Task状态设置为就绪,然后将Task塞入对应的优先级队列中,并设置该队列的有效位以告知调度器该队列存在就绪任务。在激活的末尾会判断当前Task的优先级以确定是否需要调度,如果需要调度则会通过Os_TaskSwitch进行任务切换。
当该激活任务处于就绪队列的最高优先级时,Os将线程交给该任务,以执行任务内容。
终结
基础任务结束之后会通过TerminateTask进行终结,该函数是基础任务结束必须的函数。
从调用关系图中看到,在结束任务时会归还可激活次数,如果当前任务激活次数已经消耗完毕,则将任务置位挂起状态,并清除所有Event。然后通过Os_ThreadKill将该任务的上下文等信息全部重置回初始化状态,等待下一次激活。
Os_SchedulerRemoveCurrentTask中将该任务从优先级队列中移除,并从调度器的最高优先级队列中取出一个放到NextTask指针,以进行切换。
然后调度器需要切换到另这个NextTask中,通过Os_TaskResume函数,首先执行前一个Task的PostHook,然后通过Os_ThreadResetAndResume进行一系列的操作,包括栈检查、内存保护切换、时间保护切换等并调用PreTaskHook,最后在其中调用Os_Hal_ContextResetAndResume完成寄存器等上下文的切换,开始执行该任务。
至此,一个基础任务的一次调度周期结束了。
2.4.5 扩展任务调度
任务等待
前文提到,在正常运行过程中,扩展任务会在就绪、运行、等待三个状态之间进行切换。当任务运行完毕并清理掉Event之后,便会调用WaitEvent进入等待状态,我们来看它的调用时序图。
在调用WaitEvent之后,首先会通过Os_EventWaitSet设置需要等待的事件掩码;然后通过Os_EventWaitEventTriggered判断是否已经存在激活事件,如果存在则不进行任务调度。如果不存在则调用Os_TaskWait开始进行等待。
在Os_TaskWait中,首先会将该任务状态设置为等待,然后通过Os_SchedulerRemoveCurrentTask将当前任务从就绪队列中移除,并从调度队列中最高优先级队列中取出一个任务放到NextTask指针等待调度。最后调用Os_TaskSwitch进行任务切换。如果后续有事件被激活,回到当前任务时也是回到Os_TaskSwitch的后半截,然后回到该任务继续执行。
回到该任务之后就是调用GetEvent、ClearEvent获取全局事件到本地,并清除全局事件标志位,这里就不作介绍了。
任务激活
扩展任务的激活是通过设置事件,即Event进行的,当任务进入到等待状态后,就需要通过SetEvent指令设置一定的事件,来激活任务。
在Alarm文章中我们提到,Alarm的Job类型如果是激活事件,则会调用Os_EventSetLocal函数,而SetEvent内部也会调用该接口,因此这里就只介绍SetEvent的调用时序图。
在设置事件过程中,首先会调用Os_EventSetLocal进入实际Event内部执行逻辑,其中Os_EventTriggerSet函数会将Event成员变量Triggered中对应掩码的位置位,然后判断该事件是否是任务在等待的事件,如果是则调用Os_TaskLeaveWaitingState进行任务状态切换,将任务置位就绪并通过Os_SchedulerInsert将其添加到调度器的队列中等待调度。最后根据优先级等信息判断是否需要进行任务切换,如果需要则调用Os_TaskSwitch进行任务切换。
扩展任务正常情况下不进入挂起状态,而是在这三个状态中,通过无限循环模型不停切换。
2.4.6 任务切换
任务切换的场景其实有很多,比如基础任务结束、扩展任务进等待、中断结束等,其中中断结束之后会执行Os_IsrEpilogue函数,在其中进行线程调度,其最终执行的Os_ThreadCleanupAndResume函数和基础任务结束时的Os_ThreadResetAndResume内容相似。
在任务结束,事件设置等场景下,调度器会根据新增或者减少的就绪队列,更新调度信息,比如前面基础任务的Os_SchedulerRemoveCurrentTask,扩展任务的Os_SchedulerInsert等,以使最高运行优先级的任务能够被调度,然后就由Os_TaskSwitch进行切换,这里介绍下其中的逻辑,其调用时序图如图所示。
首先会调用Os_TaskCallPostTaskHook以执行用户的PostTaskHook函数,通常可以用来进行Os的负载测量等;然后是Os_SchedulerInternalSchedule将NextTask的指针切换到CurrentTask,以实现Task的切换;最后通过Os_ThreadSwitch进行线程的实际切换任务,其中包含栈检查、FPU切换、TimeProtection切换、MemoryProtection切换、PreTaskHook等,最终通过Os_Hal_ContextSwitch进行硬件寄存器层面的切换,其中切换了返回地址寄存器A11、用作入参的寄存器A4、上下文连接寄存器PCXI,并在Os_Hal_ContextIntGo中关闭了调用深度检查,并通过RFE指令弹出调用栈,以进行切换。
3 小结
本文介绍了Autosar Os中的Task定义及其调度机制,就其类型展开了介绍,并详细说明了扩展任务中Event机制的作用。然后通过对Vector Os实现机制进行了代码解析,介绍了各种场景下调度的逻辑。
如您有任何问题,欢迎关注公众号【TechLink汽车软件】与我们联系!