1. 概述
我之前介绍过如何用Keil编译NXP官方提供的AUTOSAR OS,想要了解的朋友可以去翻一下之前的文章,本文来简要介绍下AUTOSAR OS的代码实现。
我使用的单片机时S32K144,AUTOSAR的版本是4.0.3
2. 源码分析
(1)StartOS
StartOS()函数是AUTOSAR OS的入口,在main函数中做一些硬件和应用层的初始化,之后进入StartOS。
在这个函数中,会对各种对象(Object)进行初始化,如Application、Task、Alarm、ISR、Timer、Stack、Loc、ScheduleTable、Counter等。
初始化完成后,OS会启动AutoStart的任务,系统开始运行。
(2)对象初始化
上文所述的初始化,每种对象会对应一个配置结构体和一个控制结构体(下文称控制块),配置结构体由EB生成,固化在MCU的Flash中,控制块位于RAM中,在程序运行过程中实时记录和控制该对象的状态。在对象的初始化函数中,会把配置结构体中的对象属性赋给控制块,作为初始化状态。
在AUTOSAR OS 中,所有的对象都是程序编译时定义好了的,不支持动态创建任务等对象。
以Task为例,EB中定义了如下几个Task(官方测试例程):
EB生成的Task配置结构体如下:
const OSTSK OsTaskCfgTable[OSNTSKS] =
{
{
3U,
(OSTASKENTRY) &FuncTASKRCV1,
OSTASKRCV1STKTOS,
0U | OSTSKEXTENDED | OSTSKACTIVATE,
0U,
0U,
},
{
3U,
(OSTASKENTRY) &FuncTASKRCV2,
0U,
0U,
1U,
0U,
},
{
2U,
(OSTASKENTRY) &FuncTASKSND1,
0U,
0U,
2U,
1U,
},
{
2U,
(OSTASKENTRY) &FuncTASKSND2,
0U,
0U,
3U,
1U,
},
{
2U,
(OSTASKENTRY) &FuncTASKCNT,
0U,
0U | OSTSKNONPREEMPT,
4U,
1U,
},
{
4U,
(OSTASKENTRY) &FuncTASKSTOP,
0U,
0U | OSTSKNONPREEMPT,
5U,
2U,
},
};
在初始化代码中,定义了配置结构体指针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;
task_cb->appId = task_cfg->appId;
#endif
task_cb->entry = task_cfg->entry;
task_cb->taskId = task_cfg->taskId;
#if defined(OSINRES)
task_cb->runprio = (OSBYTE)task_cfg->runprio;
#endif
#if defined(OSRESOURCE) || defined(OSINRES)
OsPrioLink[task_cfg->taskId] = task_cb;
#endif
}
(3)任务调度
所涉及的变量:
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()。
(4)上下文切换
上下文切换发生在OSTaskForceDispatch()和OSInterruptDispatcher()等会执行任务调度的函数中,执行保存环境的函数是OSSetJmp(context),恢复环境的函数是OSLongJmp (OsRunning->pcontext),当然这只是两个主要的上下文切换函数,还有一些寄存器控制函数就不展开讲了。
这些函数与MCU底层架构强相关,而且对效率要求很高,所以都是用汇编编写的,这些函数基本都在Os_hw_core.c中。
|