我之前介绍过如何用Keil编译NXP官方提供的AUTOSAR OS,想要了解的朋友可以去翻一下之前的文章,本文来简要介绍下AUTOSAR OS的代码实现。
我使用的单片机时S32K144,AUTOSAR的版本是4.0.3
StartOS()函数是AUTOSAR OS的入口,在main函数中做一些硬件和应用层的初始化,之后进入StartOS。
在这个函数中,会对各种对象(Object)进行初始化,如Application、Task、Alarm、ISR、Timer、Stack、Loc、ScheduleTable、Counter等。
初始化完成后,OS会启动AutoStart的任务,系统开始运行。
上文所述的初始化,每种对象会对应一个配置结构体和一个控制结构体(下文称控制块),配置结构体由EB生成,固化在MCU的Flash中,控制块位于RAM中,在程序运行过程中实时记录和控制该对象的状态。在对象的初始化函数中,会把配置结构体中的对象属性赋给控制块,作为初始化状态。
在AUTOSAR OS 中,所有的对象都是程序编译时定义好了的,不支持动态创建任务等对象。
以Task为例,EB中定义了如下几个Task(官方测试例程):
/* Task Configuration table */
const OSTSK OsTaskCfgTable[OSNTSKS] =
{
{
3U, /* Application identification mask value */
(OSTASKENTRY) &FuncTASKRCV1, /* entry point of task */
OSTASKRCV1STKTOS, /* top of task stack */
0U | OSTSKEXTENDED | OSTSKACTIVATE, /* properties of task OSTSKACTIVATE, OSTSKEXTENDED, OSTSKNONPREMPT, OSTSKFLOATINGPOINT */
0U, /* task id (task number in the task table) */
0U, /* application identification value */
}, /* TASKRCV1 */
{
3U, /* Application identification mask value */
(OSTASKENTRY) &FuncTASKRCV2, /* entry point of task */
0U, /* top of task stack */
0U, /* properties of task OSTSKACTIVATE, OSTSKEXTENDED, OSTSKNONPREMPT, OSTSKFLOATINGPOINT */
1U, /* task id (task number in the task table) */
0U, /* application identification value */
}, /* TASKRCV2 */
{
2U, /* Application identification mask value */
(OSTASKENTRY) &FuncTASKSND1, /* entry point of task */
0U, /* top of task stack */
0U, /* properties of task OSTSKACTIVATE, OSTSKEXTENDED, OSTSKNONPREMPT, OSTSKFLOATINGPOINT */
2U, /* task id (task number in the task table) */
1U, /* application identification value */
}, /* TASKSND1 */
{
2U, /* Application identification mask value */
(OSTASKENTRY) &FuncTASKSND2, /* entry point of task */
0U, /* top of task stack */
0U, /* properties of task OSTSKACTIVATE, OSTSKEXTENDED, OSTSKNONPREMPT, OSTSKFLOATINGPOINT */
3U, /* task id (task number in the task table) */
1U, /* application identification value */
}, /* TASKSND2 */
{
2U, /* Application identification mask value */
(OSTASKENTRY) &FuncTASKCNT, /* entry point of task */
0U, /* top of task stack */
0U | OSTSKNONPREEMPT, /* properties of task OSTSKACTIVATE, OSTSKEXTENDED, OSTSKNONPREMPT, OSTSKFLOATINGPOINT */
4U, /* task id (task number in the task table) */
1U, /* application identification value */
}, /* TASKCNT */
{
4U, /* Application identification mask value */
(OSTASKENTRY) &FuncTASKSTOP, /* entry point of task */
0U, /* top of task stack */
0U | OSTSKNONPREEMPT, /* properties of task OSTSKACTIVATE, OSTSKEXTENDED, OSTSKNONPREMPT, OSTSKFLOATINGPOINT */
5U, /* task id (task number in the task table) */
2U, /* application identification value */
}, /* TASKSTOP */
};
在初始化代码中,定义了配置结构体指针task_cfg和控制块指针task_cb,截取的部分代码如下:
OSTSKCBPTR task_cb;
const OSTSK *task_cfg;
for (i = 0U; i < OSNTSKS; i++)
{
task_cb = &(OsTaskTable[i]);
task_cfg = &(OsTaskCfgTable[i]);
#if defined(OSAPPLICATION)
task_cb->appMask = task_cfg->appMask; /* copy application identification mask value */
task_cb->appId = task_cfg->appId; /* copy application identification value */
#endif
task_cb->entry = task_cfg->entry; /* entry point of task */
task_cb->taskId = task_cfg->taskId;
#if defined(OSINRES)
task_cb->runprio = (OSBYTE)task_cfg->runprio;
#endif
#if defined(OSRESOURCE) || defined(OSINRES)
/*
* @violates @ref Os_task_c_REF_17_4_1 MISRA 2004 Required Rule 17.4, pointer arithmetic other than array indexing used
*/
OsPrioLink[task_cfg->taskId] = task_cb;
#endif
}
OsRunning:任务控制块指针,指向当前运行的任务的控制块。
OsSchedulerVector1:就绪任务向量
OsSchedulerVectorMask1:就绪任务向量掩码
OsPrioLink:按优先级排序的Task表,成员是任务控制块指针,数量是Task数量+1
OsTaskTable:存储所有任务的任务控制块的表,成员是任务控制块,数量是Task数量+1
OsTaskCfgTable:存储所有任务配置信息的表
关于任务的初始化我们上面简单说了一下,总的来说就是把任务配置表中的初始化配置赋给任务控制块,同时会初始化OsPrioLink,按优先级将任务排序,以便于之后根据优先级来查找任务。
EB生成的任务配置表中,每个任务的配置按任务优先级数值从大到小排序,并从0开始为每个任务按顺序分配TaskID,因此优先级和TaskID在数值上的顺序是相反的,也就是说优先级越高的任务,优先级数值越大,排在配置表的前面,TaskID数值越小。
在OS运行过程中,TaskID起优先级的作用,无论是任务配置表还是控制块中,都没有任务优先级了,所以EB中的任务优先级只是为配置表中的排序提供依据,是仅用来比较大小关系的相对的数值,因此也不允许配置为相同的优先级。后文提到的代码中的优先级的概念,都指TaskID。
当一个任务转为就绪态时,调用以下接口函数:
OSTask2Ready:该函数设置相应的Task状态为Ready,例如Task优先级(TaskID)为3,那么就把OsSchedulerVector1的左数第4位设置为1,代码如下:
#define OSSETBITNUM2MASK(taskprio) ( OsSchedulerVector1 |= (OSDWORD)( OSDWORDMSB >> (taskprio) ) )
OS在计算最高优先级的时候,会计算OsSchedulerVector1最左侧的0的个数(clz汇编指令),例如上面说的第4位为1,则前面有3个0,即最高的优先级为3。用该优先级去OsPrioLink链表中调取相应的任务控制块。
如果有一个优先级为2的Task就绪,则会先查找到优先级为2的Task,因此TaskID越小,优先级越高。
查找最高优先级的函数接口为 OSGETMAXPRIOTASK。
OS内部的任务调度接口函数是OSDISPATCH,该函数内部又调用OSTaskForceDispatch函数,真正执行优先级查找和上下文切换。
OS中在以下函数中会执行任务调度:
此外还有中断任务调度函数:OSInterruptDispatcher()
开放给用户的任务调度接口函数是Schedule()。
上下文切换发生在OSTaskForceDispatch()和OSInterruptDispatcher()等会执行任务调度的函数中,执行保存环境的函数是OSSetJmp(context),恢复环境的函数是OSLongJmp (OsRunning->pcontext),当然这只是两个主要的上下文切换函数,还有一些寄存器控制函数就不展开讲了。
这些函数与MCU底层架构强相关,而且对效率要求很高,所以都是用汇编编写的,这些函数基本都在Os_hw_core.c中。
在EB中配置中断后,会生成一个中断配置表:
const OSISRCFGTYPE OsIsrCfg[OSNISR + 1] = /*Interrupts config table*/
{
{
OS_isr_ISR1, /* actual ISR function */
OSTRUSTEDISR2, /* ISR type */
116U,
4U, /* ISR PRIORITY */
2U, /* application identification value */
}, /* ISR1 */
{
OSISRSystemTimer, /* actual ISR function */
OSSYSINTERRUPT, /* ISR type */
115U, /* index in OsIsr */
2U, /* Interrupt priority */
OSINVALID_OSAPPLICATION, /* appId */
}, /* SysTimer */
{
OSISRSecondTimer, /* actual ISR function */
OSSYSINTERRUPT, /* ISR type */
15U, /* index in OsIsr */
4U, /* Interrupt priority */
OSINVALID_OSAPPLICATION, /* appId */
}, /* SecondTimer */
{
OSISRException, /* actual ISR function */
OSSYSINTERRUPT, /* ISR type */
OSISREXCEPTIONIDX, /* Index of interrupt */
OSISREXCEPTIONPRIO, /* Interrupt priority */
OSINVALID_OSAPPLICATION, /* appId */
}, /* Exception */
};
中断配置表中有中断入口函数、中断优先级等,在初始化函数OSInitializeISR (void)中,配置表中的配置被赋给OsIsrTable,OsIsrTable控制和记录OS运行过程中中断的状态,其定义如下:
/* ISR control structure */
struct tagISRTYPE
{
#if defined(OSTERMINATION)
OSBYTE isKilled; /* set when the ISR is killed via ProtectionHook or via TerminateApplication() */
#endif
#if defined(OSUSERESLINK)
OSRefResType resources; /* pointer to the resources */
#endif
OSVOIDFUNCVOID userISR; /* actual ISR function */
OSISRTYPE type; /* type of the ISR */
OSWORD index; /* Index of interrupt in the external interrupts table */
OSBYTE isrPrio; /* Interrupt priority */
#if defined(OSAPPLICATION)
OSAPPLICATIONTYPE appId; /* application identification value */
#endif
};
参数Os_ArmNvicNestingLevel用来记录中断嵌套等级。
Os_ArmSavedIRQCtx用来保存中断上下文环境,包括两个寄存器:XPSR和LR。其中LR(R14)是链接寄存器,它存储从子程序调用、函数调用和中断处理后返回的信息。XPSR是程序状态寄存器,包含三部分:
这三部分在XPSR寄存器中的位置如下图所示:
在初始化过程中OSPlatformIntcInit函数用来初始化中断上下文环境为空。
在启动文件startup_S32K144_OS.s中,定义中断向量表如下:
__Vectors DCD |Image$$ARM_LIB_STACK$$ZI$$Limit| ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD OSNmiException ;NMI Handler
DCD OSHardFaultException ;Hard Fault Handler
DCD OSReservedException ;MPU Fault Handler
DCD OSBusFaultException ;Bus Fault Handler
DCD OSUsageFaultException ;Usage Fault Handler
DCD OSReservedException ;Reserved
DCD OSReservedException ;Reserved
DCD OSReservedException ;Reserved
DCD OSReservedException ;Reserved
DCD OSSVCallException ;SVCall Handler
DCD OSDebugMonitorException ;Debug Monitor Handler
DCD OSReservedException ;Reserved
DCD OSPendSVException ;PendSV Handler
DCD OSInterruptDispatcher ;SysTick Handler
;External Interrupts
DCD OSInterruptDispatcher ;DMA channel 0 transfer complete
DCD OSInterruptDispatcher ;DMA channel 1 transfer complete
DCD OSInterruptDispatcher ;DMA channel 2 transfer complete
DCD OSInterruptDispatcher ;DMA channel 3 transfer complete
DCD OSInterruptDispatcher ;DMA channel 4 transfer complete
DCD OSInterruptDispatcher ;DMA channel 5 transfer complete
DCD OSInterruptDispatcher ;DMA channel 6 transfer complete
DCD OSInterruptDispatcher ;DMA channel 7 transfer complete
DCD OSInterruptDispatcher ;DMA channel 8 transfer complete
DCD OSInterruptDispatcher ;DMA channel 9 transfer complete
DCD OSInterruptDispatcher ;DMA channel 10 transfer complete
DCD OSInterruptDispatcher ;DMA channel 11 transfer complete
DCD OSInterruptDispatcher ;DMA channel 12 transfer complete
DCD OSInterruptDispatcher ;DMA channel 13 transfer complete
DCD OSInterruptDispatcher ;DMA channel 14 transfer complete
DCD OSInterruptDispatcher ;DMA channel 15 transfer complete
我们可以看到,SystemTick中断以及外部中断都指向了OSInterruptDispatcher()这个函数,OSInterruptDispatcher()是用汇编写的,调用了C函数OSInterruptDispatcher1(),在该函数中再对各个中断进行进一步判断和处理,并进入最终的中断处理函数。OSInterruptDispatcher1()是OS中断处理最重要的一个函数。