打印
[AURIX™]

AUTOSAR OS模块详解之Counter

[复制链接]
51|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2024-11-28 10:15 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
1 简介
在操作系统中,“Tick” 是一个重要的概念,通常指的是系统时钟的一个周期性中断。Tick 是操作系统内核中用于时间管理的基本单位。它代表了系统时钟每次中断的时间间隔,通常以毫秒为单位。每当系统时钟产生一个 tick 中断时,操作系统会执行一些特定的任务,例如更新系统时间、调度任务等。

Tick 在操作系统中有几个主要作用:

时间管理:Tick 用于跟踪系统的运行时间,操作系统可以通过 tick 来维护系统时间和日期。

任务调度:操作系统使用 tick 来决定何时切换任务。每当发生 tick 中断时,调度器会检查当前运行的任务是否需要被挂起,并选择下一个要运行的任务。周期任务就是由此实现的。

定时器功能:Tick 还用于实现定时器功能,允许程序在特定的时间间隔后执行某些操作。

在大多数操作系统中,tick 是通过硬件定时器实现的。硬件定时器会在预定的时间间隔内产生中断信号,操作系统内核会响应这些中断并执行相应的处理程序。

Tick 的频率(即每秒产生的 tick 数量)对系统性能有重要影响。常见的 tick 频率有100 Hz,适用于大多数实时操作系统;1000 Hz,适用于需要更高时间分辨率的系统。

Tick提供了精确的时间管理和任务调度,允许操作系统在多任务环境中有效地管理资源。过高的 tick 频率可能导致系统开销增加,因为每个 tick 都需要进行上下文切换和中断处理。过低的 tick 频率可能导致任务调度不够及时,影响系统的实时性。

Autosar OS中,Counter就是OS的Tick来源,一般由硬件Timer进行驱动,继而由Counter驱动Alarm或者调度表。如下图是多核系统中OS运行关系示意图。



从图中我们可以看到,由硬件Timer通过中断驱动Counter,Counter中根据周期触发Alarm或者ScheduleTable。

2 功能介绍
2.1 Timer
Counter的触发依赖于硬件定时器(Timer),由Timer产生中断来执行对应的Counter程序,如果把操作系统比作一辆汽车,那Timer就是燃油,Counter就是发动机。

Timer有三种类型:

Periodical Free Running Timer(PFRT):周期自由运行定时器,该定时器为自由运行的的硬件定时器,每次产生中断后需要软件重新装在定时值,具有一定的累计误差,如Aurix TC3XX中的STM;
Periodical Interrupt Timer(PIT):周期中断定时器,该类定时器由硬件以固定周期触发,硬件定时器自动重载;
High Resolution Timer (HRT):高分辨率定时器,该定时器的中断触发频率一般远高于Os Tick所需值,在中断中判断该Counter是只进行累加,还是执行相关的逻辑,得益于其高频率的中断实现高分辨率Tick。
在Vector Microsar中,当配置硬件定时器为STM时,则默认使用PFRT模式,我们在STM原理那章中介绍过,STM是一个启动不停表的定时器,中断是通过额外的比较寄存器实现的,需要软件每次计算重载值。因此在每次中断中进行比较寄存器重载的时候,会出现累计误差。感兴趣的朋友可以去翻阅那篇STM详解。

当配置硬件定时器为GPT时,则默认使用PIT模式,因为Aurix TC3XX芯片中的GPT是硬件自动重载的定时器,使得OS Tick的中断在时间轴上保证绝对均匀。另外GTM定时器也可以作为OS时钟使用,但是Vector没有在OS中实现这一特性,可能考虑到GTM广泛用于电机控制等外设。PFRT和PIT的Tick时间轴如下图所示。



HRT则是通过高频的中断,实现一些高精度需求的Tick值,而在实际使用过程中,我们Task周期的最大公约数一般为1ms,部分系统为0.5ms,因此PIT或者PFRT就满足我们的要求,而高频中断对于系统的负载有所增加。

关于Aurix TC3XX GPT硬件,我们在Aurix TC3XX系列中进行专题介绍。

2.2 Counter配置
我们先来看Vector OS界面中的Counter配置,我们打开OS Configuration界面,展开Counters即可看到Counter的配置,这里使用TC387 4核CPU,每个核都分配了一个硬件Counter。



OsCounterMaxAllowedValue

该参数定义了Counter累加的最大值,当OS Tick达到该值后会归零,该参数没有什么倍频限制,OS Tick的数据类型是uint32,不超过该上限即可。

OsCounterMinCycle

该参数定义了Counter的分辨率,一般设置为1,每进一次Timer中断处理一次Counter的业务。

OsCounterTicksPerBase

Counter对应的硬件时钟的tick数,与下面的OsSecondsPerTick相对应。比如当前GPT的硬件时钟频率为12.5MHz,OsSecondsPerTick为1ms,则实现1ms的OS Tick需要的硬件时钟tick为12500。

OsCounterType

Counter的类型有硬件和软件,如果不想给每个核配独立的硬件定时器,则没有配置定时器的核可以使用软件Counter。

OsSecondsPerTick

该参数与OsCounterMinCycle共同组成Counter的分辨率,计算关系为: OsCounterMinCycle*OsSecondsPerTick的值==所有需要该Counter触发的Alarm的周期的最大公约数,一般系统的最小Tick为1ms;如果有2.5ms等半秒的需求,则Tick需要配置为0.5ms,硬件时钟频率也对应调整。

OsDriverHardwareTimerChannelRef

该Counter对应的硬件Timer类型,支持Aurix TC3XX的GPT和STM。

OsDriverHighResolution

是否设置为HRT时钟,GPT模式下不支持的。

OsDriverIsrRef

该Counter对应的驱动中断引用,硬件Timer会触发该中断,然后该中断执行本Counter的业务逻辑。如果该Timer在OS中使用,注意在MCAL中不要再使用该资源。关于中断的配置,我们在中断的章节会详细描述。

TimeConstants

定义Tick的常量,用户可以借OS Counter用来计数使用。

Counter Accessing Application

访问权限设置,表征哪些OS-Application可以访问。

关于OS Counter的配置就这么多,配置项还是相对比较简单,且容易理解的。OS Counter的参数设计,符合实际的系统调度需求即可。

另外对于Counter的实现,需要OS工程师了解所使用芯片的硬件Timer原理,以及其背后的时钟分频设置。关于TC3XX的GPT和STM我们在芯片系列文章中会进行介绍,这里就不加以赘述了。

2.3 代码分析
此处以GPT为驱动的PIT为例,进行代码解析。

在Vector Microsar OS中,Timer、Counter的相关文件主要是Os_Counter.c、Os_Timer.c、Os_TimerInt.h,以及与驱动相关的如Os_Hal_Timer_GPT.h,然后以及Os_Counter_Lcfg.c等配置生成代码。

2.3.1 主要数据结构
Counter中的首要数据类型是Os_TimerPitConfigType(如果是PFRT,则对应的类型为Os_TimerPfrtConfigType),该类型在Os_Counter_Lcfg.c中定义,包括Counter中的相关配置值,和其他动态数据包括Queue、状态等指针。

/*! Counter configuration data: SystemTimer */
CONST(Os_TimerPitConfigType, OS_CONST) OsCfg_Counter_SystemTimer =
{
  /* .SwCounter = */
  {
      /* .Counter = */
      {
        /* .Characteristics       = */
        {
          /* .MaxAllowedValue      = */ OSMAXALLOWEDVALUE_SystemTimer,
          /* .MaxCountingValue     = */ OS_TIMERPIT_GETMAXCOUNTINGVALUE(OSMAXALLOWEDVALUE_SystemTimer),
          /* .MaxDifferentialValue = */ OS_TIMERPIT_GETMAXDIFFERENTIALVALUE(OSMAXALLOWEDVALUE_SystemTimer),
          /* .MinCycle             = */ OSMINCYCLE_SystemTimer,
          /* .TicksPerBase         = */ OSTICKSPERBASE_SystemTimer
        },
        /* .JobQueue              = */
        {
          /* .Queue     = */ OsCfg_Counter_SystemTimer_JobQueueNodes_Dyn,
          /* .Dyn       = */ &OsCfg_Counter_SystemTimer_JobQueue_Dyn,
          /* .QueueSize = */ (Os_PriorityQueueNodeIdxType)OS_CFG_NUM_COUNTER_SYSTEMTIMER_JOBS
        },
        /* .DriverType            = */ OS_TIMERTYPE_PERIODIC_TICK,
        /* .Core                  = */ &OsCfg_Core_OsCore0,
        /* .OwnerApplication      = */ &OsCfg_App_SystemApplication_OsCore0,
        /* .AccessingApplications = */ (OS_APPID2MASK(OsApplication_NonTrusted_Core0) | OS_APPID2MASK(OsApplication_NonTrusted_Core1) | OS_APPID2MASK(OsApplication_NonTrusted_Core2) | OS_APPID2MASK(OsApplication_NonTrusted_Core3) | OS_APPID2MASK(OsApplication_Trusted_Core0) | OS_APPID2MASK(OsApplication_Trusted_Core1) | OS_APPID2MASK(OsApplication_Trusted_Core2) | OS_APPID2MASK(OsApplication_Trusted_Core3) | OS_APPID2MASK(SystemApplication_OsCore0) | OS_APPID2MASK(SystemApplication_OsCore1) | OS_APPID2MASK(SystemApplication_OsCore2) | OS_APPID2MASK(SystemApplication_OsCore3))  /* PRQA S 0410 */ /* MD_MSR_Dir1.1 */
      },
      /* .Dyn     = */ &OsCfg_Counter_SystemTimer_Dyn
  },
  /* .HwConfig  = */ &OsCfg_Hal_TimerPit_SystemTimer
};




配置生成命名为OsCfg_Counter_xxx,比如前面我们Counter命名为SystemTimer,则生成的数据结构为OsCfg_Counter_SystemTimer。

成员变量->SwCounter.Counter.Characteristics定义了我们前面配置中的相关参数值。

成员变量->SwCounter.Counter.JobQueue指向的OsCfg_Counter_SystemTimer_JobQueue_Dyn存储了该Counter的任务队列,我们在运行时监控可以观测到其为引用该Counter的Alarm(此处未配置ScheduleTable,因此任务队列中仅有仅Alarm),对应数据类型为Os_AlarmSetEventConfigType或Os_AlarmActivateTaskConfigType。

其中的成员变量->SwCounter.Dyn指向的OsCfg_Counter_SystemTimer_Dyn中,包含了该Counter的相关动态值如Counter Value等。

2.3.2 Os初始化
作为第一篇Os内部模块分解,这里先简要介绍下Os的启动调用流程,首先在main中会调用Os_Init进行Os相关数据的初始化中。在Os_Api_Init中执行如下内容等:

执行中断的初始化(暂不使能),包括Os_SystemInterruptHandlingInit、Os_CoreInterruptHandlingInit()等;
执行所有栈的初始化(Vector Os每个中断和任务具有独立的栈空间),包括Os_StackInit();
SpinLock的初始化等;
在Os_Init初始化过程中,一般流程都是由主核执行,对于从核来说,经过该函数时一般无实际动作。调用栈如下图所示。



随后main将线程交给了EcuM,在EcuM的初始化最后一步,调用StartOs进行Os的启动,其调用栈如下图所示。



Os接收main后会在Os_Hook中将线程切到InitHook线程,在其中执行Os_CoreInit,Os_CoreInit中包含了Os层面各个资源的初始化,下面列出一些重点内容:

Os_ResourceInit:初始化OsResource;
Os_IocInit:初始化IOC;
Os_AppInit:初始化各个OsApplication,其中还包括Task、Isr、Alarm、Counter等元素初始化;
Os_XSigInit:初始化XSignal;
Os_TpStart:启动时间保护;
Os_BarrierSynchronizeInternal:进行多核同步;
Os_HookCallCallback:调用Startup Hook函数;
Os_AppStart:启动各个App;
线程切到InitHook时,其返回地址为Os_HookWrapperOs_CoreInitHook,进而调用Os_HookReturn,在其中进入任务调度,完成Os的初始化。

2.3.3 Counter初始化
我们回到Os_CoreInit,在Os_CoreInit中进行Os_AppInit,这就进入了我们本章节的Counter的初始化,调用栈如下图所示。



首先在Os_CoreInit过程中会调用Os_TimerPitInit进行相关的软件变量初始化,如Counter初值等;然后在Os_Hal_TimerPitInit(Os_Hal_Timer_GPT.h),进行GPT硬件Timer的硬件配置,包括Timer中断的优先级及中断源配置。

Counter中的Job是在启动时添加的,比如SetRelAlarm,下图为其过程调用关系图。



在EcuM启动第二阶段,会进行RTE相关的启动,其中进行Alarm的设置。在SetRelAlarm的调用中,会将Alarm作为Job添加到引用的Counter的Job Queue中,也就是将自身的Os_AlarmActivateTaskConfigType数据类型添加到->SwCounter.Counter.JobQueue中。

在Os_Trap这级调用中,如果使用了内存保护,则需要通过syscall触发Trap进入系统调用,从而进入Os内核模式,执行相应的逻辑,这里不展开说明了。

2.3.4 Counter运行阶段
运行阶段,由硬件Timer中断进入Counter相关逻辑,其调用关系图如下图所示。



Timer的中断入口函数定义在Os_Timer.c中,其中会调用Os_TimerSwIncrement进行Counter的累加。

FUNC(void, OS_CODE) Os_TimerSwIncrement
(
  P2CONST(Os_TimerSwConfigType, AUTOMATIC, OS_CONST) Timer
)
{
  /* #10 Perform assertions. */
  Os_Assert((Os_StdReturnType)(Timer->Dyn->Value <= Timer->Counter.Characteristics.MaxCountingValue));

  /* #20 If the counter reached its maximum value, reset the counter to zero. */
  if(OS_UNLIKELY(Timer->Dyn->Value == Timer->Counter.Characteristics.MaxCountingValue))
  {
    Timer->Dyn->Value = 0;                                                                             
  }
  /* #30 Otherwise, increment the counter. */
  else
  {
    (Timer->Dyn->Value)++;                                                                             
  }

  /* #40 If the compare value (time stamp of the next job) has been reached: */
  if(OS_UNLIKELY(Timer->Dyn->Value == Timer->Dyn->Compare))
  {
    /* #50 Work of expired jobs. */
    Os_CounterWorkJobs(&(Timer->Counter));                                                            
  }
}



我们可以看到Os_TimerSwIncrement中首先对Counter的Tick进行累加,然后根据Counter的比较值Compare,该值定义了下一个需要激活Alarm的未来Tick值,然后根据比较判断是否执行Job。如果我们的最小周期Alarm为1ms,那每次都会进入Os_CounterWorkJobs进行工作,否则需要按需执行,Compare的值是按需求更新的。

如果有需要激活的Alarm,则会调用Os_CounterWorkJobs。

FUNC(void, OS_CODE) Os_CounterWorkJobs
(
  P2CONST(Os_CounterConfigType, AUTOMATIC, OS_CONST) Counter
)
{
  P2CONST(Os_JobConfigType, AUTOMATIC, OS_CONST) job;
  P2CONST(Os_PriorityQueueConfigType, AUTOMATIC, OS_CONST) jobQueue = &(Counter->JobQueue);
  Os_IntStateType interruptState;
  uint8 jobCounter = 0u;

  /* #10 Suspend interrupts */
  Os_IntSuspend(&interruptState);                                                            

  job = Os_PriorityQueueTopGet(jobQueue);                                                     

  /* #20 Repeat until queue is empty OR there are no expired jobs in the queue: */
  while(OS_LIKELY(job != NULL_PTR))
  {
    /* #30 Get counter's current value. (We have a continuously changing FRT here.) */
    Os_TickType now = Os_CounterGetPhysicalValue(Counter);                                    

    /* #40 If the high prio job is expired (likely): */
    if(OS_LIKELY(Os_CounterIsFutureValue(Counter, job->Dyn->ExpirationTimestamp, now) == 0u))
    {
      /* #50 Dequeue the job. */
      Os_PriorityQueueDeleteTop(jobQueue);                                                   

      /* #60 Work the job off. */
      Os_JobDo(job);                                                                          

      /* #70 Increment the local job counter. */
      jobCounter = jobCounter + 1u;

      /* #80 If the maximum number of job executions per interrupt lock is reached: */
      if (jobCounter >= MAX_JOB_EXECS_PER_LOCK)
      {
        jobCounter = 0u;

        /* #90 Open interrupts for a short time to allow interrupts of higher priority. */
        Os_IntResume(&interruptState);                                                        
        Os_IntSuspend(&interruptState);                                                      
      }
    }
    else
    {
      break;
    }

    /* #100 Get high prio job from queue. */
    job = Os_PriorityQueueTopGet(jobQueue);                                                   
  }

  /* #110 If the queue still contains jobs: */
  if(job != NULL_PTR)
  {
    /* #120 Set compare value to expiration time of next job. */
    Os_CounterSetCompareValue(Counter, job->Dyn->ExpirationTimestamp);                        
  }
  /* #130 else */
  else
  {
    /* #140 Set compare value far into the future. */
    Os_TickType now = Os_CounterGetPhysicalValue(Counter);                                    
    Os_TickType expirationTimestamp;

    expirationTimestamp = Os_TimerAdd(
        Counter->Characteristics.MaxAllowedValue,
        Counter->Characteristics.MaxCountingValue,
        now,
        Counter->Characteristics.MaxAllowedValue);
    Os_CounterSetCompareValue(Counter, expirationTimestamp);                                 
  }

  /* #150 Resume all interrupts. */
  Os_IntResume(&interruptState);                                                              
}                                                                                             



我们可以看到,在Os_CounterWorkJobs中会首先使用Os_PriorityQueueTopGet从队列中取出Job,然后判断该Job是否到期。比如我们的Tick是1ms,某个Alarm定义周期10ms,那每10次Tick会调用Os_JobDo执行该Job的Callback函数。

在Os_JobDo(job)中,执行对应的Callback,对于Alarm Callback是Os_AlarmActionActivateTask或Os_AlarmActionSetEvent。

如果到期Job会被从队列中取出来,注意在Callback中,循环Job如周期Alarm会重新添加到队列,只不过会根据周期添加到队列末尾。

最后会进行Counter的Compare值的重载,由于Counter每次不一定执行完所有的Job,因此重载有所区别。

3 小结
本文对AUTOSAR OS中的Counter进行了介绍,对其内部元素和原理进行了详细说明,并对基于Aurix TC3XX芯片的Vector Microsar工具配置及代码进行了解读。下一篇我们将继续介绍Alarm模块,解析Alarm的机制,以及更深入地探讨Counter激活Alarm之间的关联交互。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_44000419/article/details/144176191

使用特权

评论回复
沙发
申小林一号| | 2024-12-2 15:35 | 只看该作者
非常不错的分享,讲解很详细

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1968

主题

15727

帖子

12

粉丝