本文主要介绍Infineon Aurix™ TC3xx系列芯片内核上下文切换逻辑及CSA机制,并通过代码实例进行演示。
01 简 介
计算机系统的运作机制决定了,开发人员在构建软件时必然采用分而治之的策略,通过将功能分解为相互协作的模块单元,并运用信息隐藏和层次化组织等工程方法,这自然导致了程序执行时频繁的例程跳转和上下文切换,从而形成树状调用结构。
在计算机系统中,上下文切换是核心的运行机制之一,其具体表现形式因系统类型而异:
- 单任务环境下,当主程序调用子程序时,必须先将当前寄存器状态、程序计数器等关键信息压栈保存,待子程序执行完毕后再恢复现场,确保主程序继续正确执行。
- 多任务环境则更为复杂,操作系统需要在不同进程或线程之间快速切换,每次切换都涉及完整的执行环境迁移,包括内存空间、寄存器组、内核状态等,这是实现并发处理的基础。
- 中断与异常处理同样依赖这一机制——无论硬件中断还是软件异常,处理器都会自动保存当前执行上下文,这一过程通常被称为保护现场。
从本质上看,这些操作均属于上下文切换的范畴。尽管终端用户无需感知这一底层行为,但对系统开发者而言,深入理解上下文切换的细节却是构建高效操作系统的关键所在。
下面通过一个简单程序,说明在单任务系统中上下文切换的流程。
如上图是一个简单的函数调用,调用关系为FuncA -> FuncB -> FuncC。
当程序执行到FuncB中即将调用FuncC时(PC指向14行),因为是FuncA调用的FuncB,所以当前返回地址为上面代码第9行,并且稍后调用FuncC后返回地址寄存器会被覆盖为第15行,因此在调用执行FuncC之前,程序需要先保存返回地址寄存器(一般存在栈中)。
另外此时的通用寄存器用于FuncB中的一些数据计算,并且其中的数据在调用完FuncC之后还需要继续使用,而FuncC在执行过程中肯定需要使用寄存器,因此需要先把通用寄存器中的值保存下来,这样FuncC才能使用通用寄存器。
除此之外,FuncC中可能还会声明局部变量,因此它会移动栈指针,这就需要把栈指针寄存器的值也保存下来,FuncC执行完之后,释放占用的栈空间。
一般情况下芯片硬件仅保存部分上下文,而由软件负责保存剩余所需的上下文。而在TriCore™架构中,上下文的操作直接通过硬件操作,极大提高了系统调度的开销,以及异常处理时的鲁棒性。
02 TriCore™上下文机制
2.1 结构组成
在典型的嵌入式架构(如ARM)中,内核通常采用栈结构来管理执行上下文。具体实现方式是:当发生函数调用或中断时,处理器会自动将程序计数器(PC)、链接寄存器(LR)以及通用寄存器等关键状态压入当前栈帧;待执行流程返回时,再通过出栈操作恢复原先的寄存器状态,从而构建了一套完整的上下文保存与恢复体系。
TriCore™架构采用了一种独特的上下文管理机制——通过专用的上下文保存区(CSA)来存储任务状态。与传统的栈存储方式不同,虽然CSA同样位于RAM空间,但其通过以下设计实现了性能优化:
- 硬件级上下文管理:CSA由处理器硬件自动维护,消除了软件保存/恢复上下文的开销
- 内存分区隔离:将上下文存储区与运行栈物理分离,避免内存访问冲突
- 实时性增强:专用的切换机制显著减少了任务切换延迟
接下来我们将详细解析TriCore™上下文的具体组成结构。
如图所示,TriCore™架构将执行上下文划分为Upper Context和Lower Context两部分,其寄存器分配具有以下特征:
- Upper Context
- 地址寄存器:A[10]~A[15]
- 数据寄存器:D[8]~D[15]
- 核心状态寄存器:程序状态字(PSW)
- Lower Context
- 地址寄存器:A[2]~A[7]
- 数据寄存器:D[0]~D[7]
- 特殊设计:返回地址寄存器A[11]在上下两部分均有存储
全局寄存器(A[0]、A[1]、A[8]、A[9])由于其特殊用途,不参与上下文保存机制。这种分层设计通过硬件优化实现了高效的任务状态管理。
之所以分成两部分上下文,是因为程序在调用过程中,有时候子程序不需要那么多的寄存器,如果所有的寄存器都进行保存则浪费系统开销。TriCore™架构中,Upper上下文是在中断、Trap发生时,由硬件自动保存的,保存后子程序可任意使用Upper上下文的寄存器。如果Upper上下文的这些寄存器不够使用,则软件再保存Lower上下文,以供子中断、Trap程序使用。
上下文的PCXI(CPUx Previous Context Information),称为链接字(Link Word)。因为CSA是链表结构,PCXI的功能之一是作为链表指针,另外还用来指示系统保存上下文时的状态,比如中断优先级、中断使能。
高、Lower上下文都是一段64字节的数据,存储在RAM中,地址由用户分配(需64字节对齐),被分配的内存称为上下文存储区(Context Save Area,CSA)。
2.2 链接字PCXI
PCXI是上下文中第一个4字节的内容,其作为链接字,能够充当链表指针的作用,将多层调用的上下文进行连接,以此来将程序的调用栈展示出来。
TriCore™架构采用了一种基于PCXI(Previous Context Information)的智能调用栈管理机制。作为每个上下文块的首4字节内容,PCXI不仅记录了系统状态信息,更重要的是充当了链表指针的角色,将分散存储的上下文块有机连接起来。
在运行时,每个处理器核的PCXI寄存器始终指向当前最新的上下文块,也就是调用栈的顶端。而该上下文块中的PCXI字段又会指向其父级上下文,如此层层递进,自然形成了一条完整的调用链。这种设计使得硬件能够高效地管理函数调用和中断嵌套,确保每次上下文切换都能快速完成。
对于开发者而言,这一机制带来了显著的调试便利。调试器通过解析PCXI链表,可以清晰地重建出程序的完整调用栈,准确展示从当前执行点到初始调用的完整路径。这不仅帮助开发者快速定位问题,还能实时观察程序执行流程,大大提高了调试效率。正是这种硬件级的调用栈管理,使得TriCore™在保证实时性的同时,还提供了强大的调试支持能力。
为了更好地理解TriCore™的上下文切换机制,我们可以通过一个具体的调用过程来说明:
假设程序从 main() 开始执行,当它第一次调用 FuncA() 时,处理器会自动将当前的寄存器状态(Upper Context)保存到预先分配的 CSA1 内存区域中。随后,如果 FuncA() 内部又调用了 FuncB(),系统会再次保存上下文至 CSA2。此时,内存中的上下文链表关系如下:
- CPU的PCXI寄存器 指向最新的上下文块 CSA2(代表当前执行的是FuncB)
- CSA2的PCXI字段 则回指到上一级上下文 CSA1(即FuncA的现场)
- CSA1的PCXI字段 可能进一步指向更早的上下文(如main函数的现场)
当 FuncB() 执行完毕准备返回时,处理器会依据PCXI的指引,从 CSA2 恢复之前保存的寄存器状态,使程序流回到 FuncA() 的断点处,同时将PCXI寄存器的值更新为指向 CSA1。类似地,当 FuncA() 返回时,系统再次从 CSA1 加载上下文,程序回到 main() 继续执行,完成整个调用链的回溯。
这一过程完全由硬件自动管理,不仅保证了函数调用的正确嵌套,还通过链式存储结构实现了高效的上下文切换,特别适合实时性要求高的嵌入式场景。调试器也正是利用这一特性,通过遍历PCXI链表直观地展示出完整的函数调用栈。
下面我们介绍PCXI的内容。
如图是PCXI的结构,也就是CPU的PCXI寄存器的结构,主要包括以下内容:
- RES:预留;
- PCPN:上一个CPU中断优先级(Previous CPU Priority Number),在之前关于中断的文章中我们提到过,中断的处理流程的一个步骤,是将ICR.CCPN,也就是当前的CPU中断优先级位,写入到PCPN,也就是本寄存器位中,用于中断环境的保存,中断执行完之后再进行恢复;
- PIE:先前中断使能位(Previous Interrupt Enable),同PCPN,在中断处理流程中将CPU中断使能状态位ICR.IE,保存到PIE中,用于中断环境保存,中断执行完之后再进行恢复;
- UL:Upper、Lower上下文标签(Upper or Lower Context Tag),1表示Upper上下文,0表示Lower上下文;
- PCXO、PCXS:PCXI内存地址偏移,与PCXI Segment编号,一起组成链表指针,指向最后一个被保存的上下文段,也就是调用栈的栈顶。地址计算关系为((PCXS<<12) | (PCXO<<6))。如下为地址计算示意图:
TriCore™架构通过FCX和LCX寄存器构建了一套高效的动态内存管理机制,专门用于控制上下文存储区(CSA)的分配与安全保护。FCX寄存器作为空闲CSA链表的头指针,始终指向第一个可用的64字节存储块。每当发生函数调用或中断需要保存上下文时,硬件会自动从FCX指向的CSA块开始分配,并将该CSA的PCXI字段更新为新的链表头,形成一个后进先出的栈式结构,确保分配操作能在恒定时间内完成,从而满足实时系统的严格要求。
与此同时,LCX寄存器作为安全边界的守护者,记录着CSA内存区域的物理结束地址。在每次上下文分配时,硬件都会自动检查FCX是否越过了LCX设定的边界。一旦检测到FCX值大于等于LCX值,就会立即触发CSA溢出异常,有效防止因上下文嵌套过深导致的内存越界访问。这种硬件级别的安全检查机制,为系统提供了可靠的内存保护。
当CSA资源耗尽时,系统会触发异常,开发者可以根据具体需求采取不同的处理策略:或者扩展CSA内存池以增加容量,或者终止非关键任务来释放已占用的CSA资源。这种设计不仅保障了实时性能,通过硬件自动化的CSA分配比传统软件管理方式快10-20个时钟周期,还为调试和维护提供了便利。
在TriCore™架构中,上下文管理通过两个相互关联的链表实现高效运作,这种机制在单任务系统中表现得尤为清晰。PCXI寄存器与FCX寄存器分别作为两个链表的控制核心,协同管理着CSA内存区域的使用状态。
空闲链表(由FCX寄存器引导)和任务链表(由PCXI寄存器引导)构成了动态平衡的内存管理系统。每当程序发生函数调用或中断需要保存上下文时,硬件会自动从空闲链表的头部取出一个CSA块(比如图中的CSA3),将其插入任务链表的顶端,并更新PCXI指向这个新加入的块(此时CSA3成为新的栈顶)。同时,FCX会指向空闲链表中的下一个可用CSA块,为后续操作做好准备。
相反,当函数返回或中断处理完成需要恢复上下文时,系统会从任务链表的头部(即PCXI当前指向的CSA块,如图中的CSA2)弹出最顶层的上下文,完成寄存器状态的恢复后,将该CSA块归还到空闲链表的头部。FCX随即指向这个刚刚释放的块(CSA2),而PCXI则回指到前一个上下文块(CSA1),确保调用栈的正确回溯。
整个过程完全由硬件自动完成,不仅实现了上下文切换的极速响应(通常在几个时钟周期内完成),还通过链表结构自然维护了函数调用的层级关系。调试器正是利用这种链式结构,通过遍历任务链表就能完整重建程序的调用栈,为开发者提供了清晰的程序执行轨迹。这种精妙的设计使得TriCore™在保持高性能的同时,还能确保系统运行的可靠性和可调试性。
在TriCore™多任务系统中,上下文管理机制展现出更丰富的层次结构。由于每个任务都是独立的执行线程,系统需要为它们维护各自独立的任务链表——这些链表通过操作系统的任务控制块(TCB)进行组织,在任务切换时操作系统负责更新当前活跃的任务链表指针。
值得注意的是,虽然任务链表实现了多实例化,但整个系统仍然共享唯一的全局空闲链表,这个关键设计既保证了内存资源的统一管理,又避免了不必要的内存碎片。
2.3 上下文存储区Context Save Area (CSA)
在TriCore™工程的实际开发中,CSA区域作为关键的运行时内存资源,其分配策略会直接在链接脚本(linker script)中体现。开发者可以在项目的链接配置文件中看到类似这样的显式定义:
一条CSA的长度是64字节,由于该切换是硬件自动操作的,因此该段区域必须64字节对齐。
并且在系统初始化阶段,会初始化PCXI寄存器和FCX、LCX寄存器,并初始化CSA区域的链表结构。以下为Infineon官方MCAL代码提供的示例。
CIFX_SSW_INLINE void Ifx_Ssw_initCSA(unsigned int *csaBegin, unsigned int *csaEnd){ unsigned int k; unsigned int nxt_cxi_val = 0U; unsigned int *prvCsa = 0U; unsigned int *nxtCsa = csaBegin; unsigned int numOfCsa = (((unsigned int)csaEnd - (unsigned int)csaBegin) / 64U); for (k = 0U; k < numOfCsa; k++) { nxt_cxi_val = ((unsigned int)((unsigned int)nxtCsa & ((unsigned int)0XFU << 28U)) >> 12U) | \ ((unsigned int)((unsigned int)nxtCsa & ((unsigned int)0XFFFFU << 6U)) >> 6U); if (k == 0U) { Ifx_Ssw_MTCR(CPU_FCX, nxt_cxi_val); /* store the new pcxi value to LCX */ } else { *prvCsa = nxt_cxi_val; } if (k == (numOfCsa - 3U)) { Ifx_Ssw_MTCR(CPU_LCX, nxt_cxi_val); /* Last but 2 context save area is pointed in LCX to know if there is CSA depletion */ } prvCsa = (unsigned int *)nxtCsa; nxtCsa += IFX_SSW_CSA_SIZE; /* next CSA */ } *prvCsa = 0U; /* Store null pointer in last CSA (= very first time!) */ Ifx_Ssw_DSYNC();}
我们可以调试查看实例,我们从编译后的map文件可以看到,分配的CSA地址为0x70039c00~0x7003bc00,一共8k空间。
然后我们将断点打在CSA初始化代码之后,查看寄存器:
通过前文提到的PCXI地址转换关系可知FCX指向0x70039C00,也就是指向了CSA的第一个段。LCX指向0x7003BB40,距离CSA的末尾地址还有192字节,也就是预留了3个CSA段的安全间隙。FCX为0表示当前还没有进行过上下文保存。
然后我们再打开内存中的CSA区域查看数据内容:
我们可以看到,系统的FCX寄存器指向了第一个CSA段,CSA段中的PCXI依次指向下一个CSA段,形成一个空闲链表。
2.4 切换流程介绍
介绍完了上下文内容以及上下文的内存结构,我们接下来介绍下在哪些场景下,CSA中的上下文会进行保存和恢复。
上下文的保存和恢复是由一个事件或指令来触发的,事件包括中断、异常或CALL调用。下表列出了具体的操作场景:
首先我们看前三个,中断、异常和函数调用,这三种场景下,Upper上下文是会自动保存的。对于中断和异常,编译器会在返回处添加RFE指令,用于恢复Upper上下文,归还CSA段,并且加载返回地址到PC中,完成调用返回。对于函数调用也会添加一条RET指令,同样也执行上述操作。
然后我们看BISR和SVLCX指令,这两条指令会保存Lower上下文,是由编译器根据寄存器的使用情况来定的,用户也可通过内联汇编来保存Lower上下文。一般当Upper上下文包含的寄存器不够用时,才会使用Lower上下文。比如中断程序较长,或调用子程序较长时。恢复的话则需要使用RSLCX指令,将上下文中的数据恢复到指定寄存器中。
最后我们看STLCX和STUCX,这两条指令使用较少,它们的功能是仅将通用寄存器以CSA的格式,保存到指定的内存中(非CSA区域),并在LDLCX和LDUCX时将数据恢复到通用寄存器中。其他寄存器如PCXI、FCX等均不受影响。也就是说该指令仅做全局寄存器的暂存和恢复,不做任何系统状态切换。
2.5 硬件上下文小结
我们来回看下TriCore™上下文的机制,以便我们理解下面的实例。
TriCore™的上下文为64字节存储段,包括通用寄存器、PSW等内容,包括Upper上下文和Lower上下文两类。
上下文段存储在RAM中称为CSA的内存区域中,以链表的形式体现。有空闲链表和任务链表,任务链表也可以表征系统的调用栈。当发生中断、异常或调用时,从空闲链表中取一个链接到任务链表的头,返回时将任务链表的头去除归还到空闲链表。
CPU对链表的句柄包括PCXI寄存器、FCX寄存器和LCX寄存器。PCXI指向最后一个保存的上下文段,也就是任务链表的头;FCX指向下一个可用的CSA段,也就是空闲可用链表的头;LCX表示CSA区域的使用限制,在保存时如果FCX指向LCX指向的区域,则发生异常。
上下文切换的操作包括中断、异常和函数调用时的硬件自动保存操作,自动操作的对象为Upper上下文,编译器或用户可通过指令操作Lower上下文。
另外还有一点需要注意的是CSA的内存大小分配,我们都知道嵌入式的内存资源是有限的,因此CSA不能分配太大,但是分配太小如果使用超出范围会产生异常导致系统崩溃。在分配的过程中根据实际情况来定,已知一层函数调用使用一个CSA段64字节,可分析系统的最大调用深度,再加上安全预留空间。如果是多任务操作系统,因为每个线程的调用栈是独立的,所以所有任务的CSA段资源消耗是累加的,分配时也需要注意综合考虑(inline函数和较短的函数编译器会进行优化,一般调用过程不占CSA)。一般情况下每个核分配32k左右CSA空间。
03 TC4 TriCore™ 1.8新特性
本文介绍的TriCore™内核版本为TriCore™ 1.6.2,英飞凌在2024年发布了新一代的Aurix™ TC4xx系列芯片,相对于TC3xx系列有较大升级,其使用TriCore™ 1.8的内核架构。在上下文保存上也做了升级。
我们知道,在PSW中有两个位域PRS和PRS2,共同组成了PSW.PRS(Protection Register Set),表示当前内存保护设置,即当前处于哪个内存保护权限集合。
当进行Upper上下文中的保存时,PSW会被存到CSA中;当进行上下文恢复时,CSA中保存的PSW则会被复制到PSW寄存器中,从而恢复内存保护设置。
在TriCore™ 1.8架构中,新增了Previous protection register set register (PPRS),表征上下文切换之前的内存保护设置。
当进行上文保存时,不再是直接将PSW保存到指定的位域中,而是将PPRS的内存保护集状态保存到上下文中(同样还是上下文中PSW.PRS的位域),而将当前PSW的PRS内容保存到了PPRS中。相当于多了一层上下文保护链,由PPRS寄存器进行中转。
这样的好处在于在运行过程中,无需通过PCXI连接的CSA内存,即可访问上下文中保存的PRS,进行某些内存保护判定。
04 示 例
为了更好地理解TriCore™架构的上下文管理机制,我们可以通过三种典型场景来分析其运作原理。第一个是单线程系统中的上下文切换,第二个是中断程序中的上下文切换,第三个多任务系统中的上下文切换。
4.1 单线程系统
单任务系统也就是单线程系统,除了中断以外系统的调用栈是单线的。下面我们通过一段函数调用来看CSA的使用过程。
4.1.1 启动阶段
首先我们让函数执行到main中,此时没有调用,也没有上下文保存,PCXI指向空,FCX指向CSA的第一个段CSA1。
在内存中第一个段为0x70039C00,它是空闲链表的头,指向下一个空闲CSA:
为了方便理解,我们将CSA按段画成示意图,用下图表示在没有上下文保存发生时的CSA状态。
4.1.2 第一层调用
然后我们向下执行代码,调用EcuM_Init。CALL指令表示跳转,同时自动保存Upper上下文。
此时执行了一次CALL指令,因此寄存器发生了变化:
我们通过换算可以看出,PCXI指向了CSA1,FCX指向了CSA2,因此CSA2成了空闲链表的头。并且CPU的PCXI中的UL位为1,表示其指向的上下文是一个Upper上下文(CALL指令调用自动保存Upper上下文)。
然后从内存中我们可以看到CSA第一个段CSA1的PCXI指向了0,因为任务链表仅有一个元素,所有它指向空;CSA2仍然指向CSA3,此时的CSA内容如下图所示:
4.1.3 第二层调用
我们再继续往下,让EcuM_Init调用Mcu_Init。
然后观察此时的寄存器,发现此时PCXI已经向下偏移了一个CSA段,指向了CSA2,同时FCX也指向了CSA3。
然后我们到内存中,发现此时任务链表的头CSA2,已经指向了CSA1,CSA3没有被修改,继续指向CSA4。
同样我们画出CSA示意图:
在这个阶段程序的上下文链表的形式得以展示出来,我们可以看到,随着调用栈的逐级增多,任务链表逐渐加长。
我们还可以观察,CSA2中保存的A11返回地址寄存器0x800268FE,它表示上一层调用EcuM_Init的返回地址,而Mcu_Init的返回地址在当前的A11返回地址寄存器中。其他的通用寄存器也是一一对应的。
4.1.4 第二层调用返回
首先我们将断点打在第二层调用Mcu_Init的最后一行,我们可以清楚地看到这里有一个RET指令,这是编译器添加的与CALL成对的指令。它的执行会恢复Upper上下文,并根据当前返回地址返回到上一层调用。
我们执行该指令,然后观察寄存器。
发现此时PCXI向前回退了一个CSA段,指向了CSA1,同时FCX也会退了一个段指向了CSA2。
我们观察内存发现CSA2的PCXI(70E72)指向CSA3,而CSA1指向空,同样我们画出示意图:
此时其实就是回到了第二层调用之前的状态,也就是归还了第二层调用所使用的CSA。
4.1.5 第一层调用返回
同样的,在执行到EcuM_Init的最后时,也会执行RET指令,恢复上下文并返回到之前调用位置,我们直接画出示意图,也就回到了最初的状态。
以上我们就完成了单任务系统调用过程中的上下文切换流程,对于多任务系统也是类似的,只是在任务切换过程中操作系统需要把对应的三个系统寄存器都切换到对应Task的状态即可。
另外对于CSA在切换时的内部硬件逻辑,因为其与软件没有太大相关性,这里没有介绍,感兴趣的读者可以自行阅读内核手册。
4.2 中断程序中的上下文
下面我们介绍中断服务程序中的上下文切换。
4.2.1 中断入口
因为中断是在常规任务执行时进入的,所以首先我们需要在中断的入口处打一个断点,这里我选择了ADC3的中断。可以看到CPU进行了一层子函数调用,然后被ADC中断打断。
然后我们观察CSA相关寄存器:
我们可以看到PCXI指向了CSA2,FCX指向了CSA3。这里我们直接画出CSA示意图,因为存在普通程序调用栈和中断程序调用栈,我们加一个底色进行区分:
从图上我们可以看出,main经过了一层调用,所以使用了CSA1,随后中断发生后由硬件保存了CSA2。
4.2.2 保存Lower上下文
由于中断的服务程序比较长,所以编译器在这里使用SVLCX指令保存了Lower上下文,这里我们介绍下这个流程。
我们执行该指令然后观察CSA相关寄存器:
同样的我们发现PCXI和FCX都发生了偏移。这里我们注意到PCXI的PCPN位为120,这是因为进入中断后,当前中断优先级为120,所以在进行上下文保存时会保存到PCPN位域中。同时SVLCX保存的是Lower上下文,所以PCXI的UL位域为0。同样我们也画出示意图:
中间还有一步CALL来调用中断的服务程序,这和普通程序流程一样,这里不做描述。
4.2.3 恢复Lower上下文
在执行完中断的服务程序之后,编译器这里添加了一条和SVLCX成对的RSLCX指令来进行Lower上下文恢复。
执行之后CSA回到了SVLCX执行之前的状态:
4.2.4 退出中断程序
执行完这些之后,程序会在最后执行RFE指令时退出中断程序,恢复硬件自动保存的Upper上下文,返回到中断前的地址,并切换相关的系统状态。到这里想必你已经很熟悉CSA的流程了,我们直接看示意图。
此时中断的处理完全结束,系统继续处理常规程序。
4.3 多任务系统中的上下文
在多任务系统中,一个核的所有线程共用一块CSA区域,但是每个线程之间对于CSA的使用是相互隔离的,他们从相同的空闲池获取CSA,返回的时候同样也退还到该空闲池。
这里不进行演示,读者感兴趣可自行调试相关操作系统代码。
05 小 结
本文详细介绍了TriCore™内核的上下文机制,以及CSA的存储和实现逻辑。并通过实例向读者展示了TriCore™内核在程序调用和中断等过程中的上下文处理流程,能够使读者对TriCore™的内核有更深刻的认知,并且能够帮助读者在调试过程中更好地处理一些系统调用方面的内核问题。
END
作者:林Nova
来源:汽车电子与软件
推荐阅读:
更多汽车电子干货请关注汽车电子与软件专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。