FreeRTOS任务创建、启动调度器、任务切换过程分析——基于Tricore1.6

【任务创建】

1.创建任务控制块TCB

2.为任务申请堆栈空间并将起始地址存到任务控制块中(pxNewTCB->pxStack = pxStack)

3.调用prvInitialiseNewTask()函数

  1)   通过NewTCB.pxStack计算栈顶指针pxTopOfStack并将其对齐;

  2)   记录任务名和优先级等信息(pxNewTCB->pcTaskName、pxNewTCB->uxPriority...)

  3)   调用pxPortInitialiseStack()函数初始化任务堆栈(准确来说是CSA)

    从CPU_FCX寄存器中获取空闲CSA的地址并赋给低级上下文指针pulLowerCSA

    根据获取到的CSA的链接字找到下一个空闲CSA的地址并赋给高级上下文指针pulUpperCSA

    更新CPU_FCX的值,使其指向最新的空闲CSA

    对高级上下文CSA进行设置:

      清空CSA,即将整个高级上下文CSA的内容都设置为0

      将A[10](SP)对应的字设置为之前申请的任务堆栈的栈顶指针pxTopOfStack

      将PSW对应的字设置为portSYSTEM_PROGRAM_STATUS_WORD

    对低级上下文CSA进行设置:

      清空CSA,即整个低级上下文CSA的内容都设置为0

      将A[4]对应的字设置为任务参数pvParameters

      将A[11](RA)对应的字设置为任务函数地址pxCode

    设置低级上下文CSA中的链接字PCXI为高级上下文CSA的地址

    将pxTopOfStack的值设置为低级上下文CSA的地址并将其返回

  4)   将低级上下文CSA的地址存储到TCB中pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );

  5)   返回任务控制块句柄用于任务删除等操作

4.将创建好的任务添加到任务就绪列表

 

【启动调度器】

1.设置STM,开启并安装STM中断

2.安装syscall(0)的 trap handler

3.安装通用寄存器GPSR00的中断,用于软件中断

4.初始化SYSCON寄存器和PSW寄存器

5.从当前任务控制块pxCurrentTCB中读取低级上下文CSA的地址并赋给PCXI:_mtcr( CPU_PCXI, *pxCurrentTCB );

6.执行_rslcx指令,该指令从PCX指向的低级上下文CSA中恢复低级上下文(指令执行完后PCX的值更新为该CSA中存储的链接字Link Word,即高级上下文CSA的地址,然后该CSA就被释放到FCX列表中)

7.执行rfe指令,返回到A[11](RA)存储的返回地址,与此同时恢复高级上下文(二者并行执行)。创建任务时初始化CSA中A[11]对应的值为任务函数的地址,因此执行rfe后就进入了任务函数,也就是启动了第一个任务。

 

【任务切换】

任务的切换有三种方式:

(1)任务中调用任务切

  切换函数:portYIELD()/portYIELD_WITHIN_API()/taskYEILD()

  处理器资源:Trap_class6_TIN0

  触发方式:_syscall(0)

(2)普通中断中调用任务切换

  切换函数:portYIELD_FROM_ISR()/taskYEILD_FROM_ISR()

  处理器资源:GPSRx0

  触发方式:GPSRx0.B.SETR = 1或INT_SRB0.B.TRIG0 = 1

(3)systick中断中的任务切换

  切换函数:在StmISR中进行

  处理器资源:SysTimerx

  触发方式:SysTimerx  COMP0计数器溢出触发中断

以上三种任务切换方式分别对应port.c中的三个中断或陷阱处理函数:prvTrapYield()、prvInterruptYield()、prvSystemTickHandler()。这三个处理函数都执行的操作:

  _disable();                                          //关中断

  _dsync();

  xUpperCSA = _mfcr( CPU_PCXI );                 //获取PCXI寄存器的值

  pxUpperCSA = portCSA_TO_ADDRESS( xUpperCSA ); //将PCXI中PCXS和PCXO字段转换成PCX指向的CSA地址

  *pxCurrentTCB = pxUpperCSA[ 0 ];               //将PCX当前指向的CSA中的Link Word即下一个CSA的地址存储到当前任务TCB.TopOfStack

  vTaskSwitchContext();                             //寻找新的任务,更新pxCurrentTCB的值

  pxUpperCSA[ 0 ] = *pxCurrentTCB;               //将PCX当前指向的CSA的Link Word设置为新任务的CSA地址(低级上下文地址)

  SRC_GPSR00.B.SRR = 0;                  

  _isync();

看到这里会产生很多疑问:

(1)   当前任务的低级上下文为什么没有保存?

(2)   为什么存放PCXI值得变量叫做UpperCSA,而不是LowerCSA?

(3)   为什么存储到TCB.TopOfStack的是pxUpperCSA[ 0 ]即当前CSA的Link Word而不是pxUpperCSA的值

(4)   新任务的上下文为什么没有恢复?

(5)   怎么切换到新任务并运行?

经过对中断及陷阱代码的分析,得出如下答案:

(1)当前任务的低级上下文并不是没有保存,只是没有写在中断或陷阱处理函数中,低级上下文的保存操作是在中断或陷阱入口程序中完成的,如下图(入口程序指的是中断或陷阱向量(表)中存储的一段用于向处理函数跳转的代码,详见博文《Tricore系列MCU中断系统的原理》)。在中断入口程序中用bisr指令完成低级上下文的保存,而在陷阱入口程序中用svlcx指令完成低级上下文的保存。

cint_tc29x.c中定义的中断和陷阱入口:

(2)PCX此时指向的确实是一个高级上下文的CSA,因为保存完当前任务的低级上下文后,紧接着用call指令调用了该处理函数,从而发生了高级上下文的自动保存,因此PCX此时指向进入处理函数前的高级上下文CSA。

(3)因为PCX当前指向的是一个高级上下文CSA,后面链接着当前任务的低级上下文CSA和高级上下文CSA,结构为PCX->BeforeHandlerUpperCSA->TaskLowerCSA->TaskUpperCSA,因此TCB.TopOfStack=pxUpperCSA[ 0 ]是将TaskLowerCSA的地址存放到TCB中。上下文保存前后PCX、FCX及CSA列表的状态变化如下图所示。

 

 

(4)pxUpperCSA[ 0 ] = *pxCurrentTCB 所做的是将新任务的LowerCSA和UpperCSA链接到BeforeHandlerUpperCSA后面,即PCX->BeforeHandlerUpperCSA->NewTaskLowerCSA->NewTaskUpperCSA,当该中断或陷阱处理函数执行完成后返回时,会自动恢复高级上下文并释放BeforeHandlerUpperCSA到FCX所指向的列表中,此时PCX就指向了新任务的CSA,即PCX->NewTaskLowerCSA->NewTaskUpperCSA,具体过程如下图所示。从处理函数返回后会执行rslcxrfe指令,分别恢复新任务的低级上下文和高级上下文

 

 

(5)执行rfe指令时会从A[11](RA)寄存器获取返回地址,而此时A[11]的值为新任务的任务函数地址,于是便跳转到了新任务函数。(高级上下文的恢复与向返回地址的跳转是并行的)

 

 

以上便是FreeRTOS创建任务、启动调度器、切换任务的整个过程的原理及实现,都是与移植相关的,都涉及到内核架构方面的知识,不容易看懂。

----------------------------------------------------------------------------------------------------------------------

最后补充一点:在使用iLLD时,中断和陷阱入口程序的定义与cint_tc29x.c不同,因此任务切换的过程也可能有所不同。

陷阱的入口程序的定义与之前所述的基本相同,其定义如下图所示:

 

这里使用了ji指令进行了直接跳转,因此不自动保存高级上下文,而在钩子函数(内联函数,不属于函数调用,不发生上下文自动保存)中调用了陷阱处理函数prvTrapYield(),因此发生一次高级上下文的自动保存。从prvTrapYield()函数中返回时恢复了该高级上下文,接着执行rslcx和rfe指令恢复新任务的上下文并跳转到任务函数中继续执行。

但中断入口程序的定义有所不同,我们首先从软件管理的中断开始分析,入口程序的定义如下:

将宏IFX_INTERRUPT_INTERNAL展开后得到:

这里在入口处保存了低级上下文,然后使用ji指令直接跳转到了IfxCpu_Irq_intVecTable()函数,该函数中调用了相应的中断处理程序(prvSystemTickHandler或prvInterruptYield),因此产生了一次高级上下文的保存,进入任务切换函数后的处理过程不再赘述。到这里有一个问题:为什么入口程序中没有rslcx和rfe指令?但实际上FreeRTOS能够正常运行。于是开始对程序进行调试,调试的过程中发现编译器自动在IfxCpu_Irq_intVecTable()函数的末尾添加了这两条指令,如图所示:

 

 为什么编译器会自动添加这两条指令呢?通过HighTec编译器的用户手册发现了原因:

这样的话一切就说的通了!

接下来我们再对硬件管理的中断进行分析。硬件管理方式中,中断向量的内容即中断入口程序和软件管理方式是一样的,如下图所示。但不同于软件管理中断的单一中断向量,硬件管理方式下每个中断对应一个中断向量。中断发生时先自动保存高级上下文,然后进入中断入口程序,入口程序首先保存低级上下文,然后直接跳转到中断处理函数,而不像软件管理方式间接通过函数IfxCpu_Irq_intVecTable()进行相应中断处理函数的调用。但是这也导致使用硬件中断时,任务切换函数需要修改,否则会发生上下文保存与恢复上的错误,因为硬件中断是使用ji指令直接跳转到中断处理函数prvSystemTickHandler或prvInterruptYield中的,并没有发生高级上下文的保存,因此CSA、PCX和FCX的状态为PCX->TaskLowerCSA->TaskUpperCSA,与前文中的状态不一致。

 

posted @ 2020-03-05 19:47  凉风SK  阅读(789)  评论(0编辑  收藏  举报