Autosar Os Task 调度
Tasks
前面有基础的概念文章,需要的请到主页点击历史文章进行查看,本文深入说Task中的一个点 调度 。
之前有关的文章
《Autosar Os MCU 多核 启动》
《Autosar Os 任务调度》
1. 任务的基础
调度,如果没有逻辑上面的需求与设计,那么一般可以参考下面两种方式来制定。
- 运行时间较短,也就是很快结束的任务,优先级越高
- 需要运行频率很快的任务,优先级越高
在Autosar os 里面有三种调度方式。
- 允许抢占的方式
- 非抢占的方式
- 合作式调度
2. 中断
我们这里调度用使用schedule table 来说明。说到调度就不得不说中断,为什么说中断呢,因为每一次激活task (schedule table 激活task) 都是一个 os 的二类中断来激活的。
在Autosar Os 中 中断分为两类。所以 任务,二类中断,一类中断的优先级可以按照下面来分布。
说任务呢,怎么扯到这了。因为我们一般把OS Level 的那个优先级的二类中断化作 OS 的中断,来驱动schedule table。
所以中断这里怎么到任务呢。还记得上一篇 Autosar 多核启动里提到的 OS 的中断向量表吗?
我们来分析一下源码。
Os_InterruptVectorTable0:\n\
nop\n\
.align 5 # Slot 1\n\
bisr 101\n\
j Os_CrossCoreISR0\n\
.align 5 # Slot 2\n\
bisr 255\n\
j DefaultInterruptHandler\n\
.align 5 # Slot 3\n\
mfcr %d15, 0xFE00\n\
bisr 101\n\
mov %d5, %d15\n\
mov %d4,0\n\
j Os_ISRWrapper0\n\
在OS 的中断向量表类似下面这样。每个core都会有自己的table. 这里拿core0 举例。
Slot 1,2,3,4,5,6 指的是 对应的优先级的序号。
j 后面指的是 os 处理函数。也就是说 只要不是DefaultInterruptHandler, 都是被os 配置了的。
然后Os_ISRWrapper0 这里面会执行OS 对这个中断的处理,随后调用 真正的中断服务函数。如下图。
所以:
- 配置一个定时器中断
- 中断服务函数是os 的驱动
- os的驱动这个服务函数 来驱动schedule table
- schedule table 来active 对应的tasks
这里面涉及到的 中断服务列表全局变量。
CONST(Os_ISRType, OS_CONST_FAST) Os_const_isrs[];
运行至此Os 的“counter” 就会跟着 定时器来跑起来了。
FUNC(StatusType, OS_CODE) Os_IncrementCounter(CounterType CounterID)
上面说到的wrapper 里面 os 具体执行了什么,我们后面专门说OS 的中断会说,这里只说 标题写的task.
注意,这里Os counter 不是字面 仅仅一个counter的意思,是一个很大的结构体。
FUNC(StatusType, OS_CODE) Os_IncrementCounter(CounterType CounterID) {
StatusType api_retval = E_OK;
const Os_CoreConfiguration *os_current_core_const = &Os_const_coreconfiguration[OS_MFCR(0xfe1c)];
Os_ControlledCoreType *os_current_controlled_core = os_current_core_const->controlled; /* [$UKS 238] */
/* Error if the counter is not valid */
if (!(Os_IsCounterValid(CounterID))) { api_retval = E_OS_ID; goto api_error_exit; } /* [$UKS 241] */
if (os_current_core_const != CounterID->core) { api_retval = E_OS_CORE; goto api_error_exit; } /* [$UKS 1555] */
if ((0U == (CounterID->access & Os_AppAccess))) { api_retval = E_OS_ACCESS; goto api_error_exit; } /* [$UKS 853] */
api_retval += CounterID->advincr(); /* [$UKS 240] [$UKS 239] */
goto api_exit;
api_error_exit:
Os_ErrorLog(api_retval, OSServiceId_IncrementCounter, (Os_BiggestType)CounterID, (Os_BiggestType)0, (Os_BiggestType)0); /* [MISRA 2012 Rule 11.4/11.6] */ /*lint !e9078 !e923 !e9030 !e9087 !e931 !e826 !e571 */
api_exit:
return api_retval;
}
这里能看出来最终函数会运行到
CounterID->advincr();
那么这个是什么呢?是下面的函数。这里
UNC(StatusType, OS_CODE) Os_IncrementCounter_Os_TickCounter(void) {
.
.
.
.
.
.
if ((current_std->state == SCHEDULETABLE_RUNNING) && (now == current_std->match)) {
(void)Os_PerformEP(current_stc, current_std, (TickType)(65536U), os_current_core_const, os_current_controlled_core);
}
current_std++;
current_stc++;
} }
if ((0U == (previous_imask & 0xFFU) ) && (Os_ControlledCoreInfo[0U].ReadyTasks.p0 > Os_ControlledCoreInfo[0U].RunningTPMask.t0) /*lint !e931 PC-lint thinks there are side effects here */) {
Os_Dispatch();
}
}
这里可以看出 优先级大于的时候就会走到调度函数。
Os_Dispatch();
终于形成回环了,看最前面的几张截图,通过优先级的调度。我们继续分析调度函数。
FUNC(void, OS_CODE_CORE0) Os_Dispatch0(void)
3. Os_Dispatch 调度
到这里我们做个总结。
下面我们继续分析调度函数。
首先通过前面的一张图。
在抢占的时候,会有一个上下文切换,如果是抢占,这里对应的一个栈空间的递增,所以我们先对栈空间进行判断。
spnow = Os_GetSP();
FUNC(Os_StackValueType, OS_CODE) Os_GetSP(void) {
Os_StackValueType ret;
uint32 cx = OS_MFCR(0xFE38) + 2U; /* FCX + 1 is stack position, then add return */
ret.ctx = ((cx & 0xFFFF) << 6) + ((cx & 0xF0000U) << 12); /* Convert to address */
__asm__ volatile ("mov.d %0, %%a10": "=d" (ret.sp));
return ret;
}
通过CPU 寄存器 0xFE38 来获取当前的栈指针。
随后进行判断栈空间是否满足。如果有问题,就出现我们常见的 trap了。
Os_Cbk_StackOverrunHook(spoverrun, OS_BUDGET);
一切都没问题,继续往下走,
对当前任务的栈空间进行准备使用。
然后开启即将运行的任务,在运行任务之前 我们在配置OS 的时候,会有配置一些Hook 。
运行任务,和退出任务时 都有对应的Hook. 这些Hook 就是在这里使用的。
.
.
.
PreTaskHook(); /* [$UKS 178] */
Os_Cbk_TaskStart(Os_RunningTask); /* [$UKS 1802] [$UKS 1803] [$UKS 1804] [$UKS 1805] */
OS_MTCR(0xFE2C, 0x8000U); OS_SEQ_POINT(); /* [$UKS 93] Allow interrupts precedence */
Os_RunningTask->entry_function();
.
.
.
Os_Cbk_TaskEnd(Os_RunningTask); /* [$UKS 1806] [$UKS 1807] [$UKS 1808] [$UKS 1809]*/
{
PostTaskHook(); /* [$UKS 177] */
}
.
.
.
所以我们很多的操作都可以在下面函数进行操作。任务的监控,时间的分析等等。
Os_Cbk_TaskStart(Os_RunningTask);
Os_Cbk_TaskEnd(Os_RunningTask);
真正的任务运行就到了
Os_RunningTask->entry_function();
到这里 一个任务的调度 终于调度起来了。
继续总结一下本文写道的调度的约束。并不是所有情况都涉及到的。涉及到下面。
- core 0 的任务
- 抢占式 调度
- 不涉及一类中断
- schedule table 触发
附:
OS 用到的大部分用到的全局变量配置结构体
CONST(Os_CoreConfiguration, OS_CONST_FAST) Os_const_coreconfiguration[2U] = {
{
&Os_ControlledCoreInfo[0U],
&Os_AnyCoreInfo[0U],
&Os_CoreState[0U],
Os_Dispatch0,
0U
},
{
&Os_ControlledCoreInfo[1U],
&Os_AnyCoreInfo[1U],
&Os_CoreState[1U],
Os_Dispatch1,
1U
}
}
看一下具体指的是什么。后面函数里面会用到。先举个例子说一下,后面用上的继续说。
这里面包含
- controlled
- any
- state
- dispatch
- core_id
typedef struct Os_CoreConfiguration_s {
CONSTP2VAR(Os_ControlledCoreType, OS_VAR, OS_CONST) controlled;
CONSTP2VAR(Os_AnyCoreType, OS_VAR, OS_CONST) any;
CONSTP2VAR(OS_VOLATILE Os_CoreStateType, OS_VAR, OS_CONST) state;
Os_VoidVoidFunctionType dispatch;
CoreIdType core_id;
} Os_CoreConfiguration;
controlled;
typedef struct Os_ControlledCoreType_s {
OsTrapInfoType TrapInfo;//
Os_ErrorInformationType ErrorInformation;
OS_VOLATILE Os_Lockable lock_taskaccess;
OS_VOLATILE Os_psetType ReadyTasks ;
OS_VOLATILE ISRType RunningISR;
OS_VOLATILE TaskType RunningTask;
OS_VOLATILE Os_tpmaskType RunningTPMask ;
OS_VOLATILE SpinlockIdType CurrentSpinlock;
Os_MeterInfoRefType CurrentMeteredObject;
Os_MeterInfoType IdleMeter;
uint8 AppAccess;
OS_VOLATILE ApplicationType AppOverride;
Os_StackSizeType GetStackValueAdjust;
boolean InErrorHook;
OS_VOLATILE TaskType ChainTaskRef;
Os_StackSizeType GetStackUsageAdjust;
boolean InProtectionHook;
boolean CoreIsActive;
boolean InShutdownHook;
} Os_ControlledCoreType;
- trapInfo
trap 信息,在前一个文章说到过,在启动os 之前 os的中断和trap 都会进行初始化。举个例子。程序里出现一个数除以0,则就会进入一个trap. 很多种错误都会进入trap.
trap 有以下几种分类,我这边经常遇到的trap 指令,上下文,内存访问。
- MMU (Memory Management Unit)
- Internal Protection
- Instruction Error
- Context Management
- System Bus and Peripherals
- Assertion Trap•
- System Call
- Non-Maskable Interrupt (NMI)
文章转载自公众号:汽车与基础软件