当前位置:首页 > 公众号精选 > 技术让梦想更伟大
[导读]关注、星标公众号,直达精彩内容ID:技术让梦想更伟大整理:李肖遥FreeRTOS任务相关的代码大约占总代码的一半左右,这些代码都在为一件事情而努力,即找到优先级最高的就绪任务,并使之获得CPU运行权。任务切换是这一过程的直接实施者,为了更快的找到优先级最高的就绪任务,任务切换的代...


关注、星标公众号,直达精彩内容

ID:技术让梦想更伟大

整理:李肖遥


FreeRTOS任务相关的代码大约占总代码的一半左右,这些代码都在为一件事情而努力,即找到优先级最高的就绪任务,并使之获得CPU运行权。

任务切换是这一过程的直接实施者,为了更快的找到优先级最高的就绪任务,任务切换的代码通常都是精心设计的,甚至会用到汇编指令或者与硬件相关的特性,比如Cortex-M3的CLZ指令。

因此任务切换的大部分代码是由硬件移植层提供的,不同的平台,实现发方法也可能不同。

这篇文章以Cortex-M3为例,讲述FreeRTOS任务切换的过程。

「FreeRTOS有两种方法触发任务切换:」

  • 执行系统调用,比如普通任务可以使用taskYIELD()强制任务切换,中断服务程序中使用portYIELD_FROM_ISR()强制任务切换;
  • 系统节拍时钟中断
对于Cortex-M3平台,这两种方法的实质是一样的,都会使能一个PendSV中断,在PendSV中断服务程序中,找到最高优先级的就绪任务,然后让这个任务获得CPU运行权,从而完成任务切换。

对于第一种任务切换方法,不管是使用taskYIELD()还是portYIELD_FROM_ISR(),最终都会执行宏portYIELD(),这个宏的定义如下:

#define portYIELD()      \
{        \
 /*产生PendSV中断*/                          \
 portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;  \
}
对于第二种任务切换方法,在系统节拍时钟中断服务函数中,首先会更新tick计数器的值、查看是否有任务解除阻塞,如果有任务解除阻塞的话,则使能PandSV中断,代码如下所示:

void xPortSysTickHandler( void )
{
 /* 设置中断掩码 */
 vPortRaiseBASEPRI();
 {
  /* 增加tick计数器值,并检查是否有任务解除阻塞 */
  if( xTaskIncrementTick() != pdFALSE )
  {
   /* 需要任务切换。产生PendSV中断 */
   portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
  }
 }
 vPortClearBASEPRIFromISR();
}
从上面的代码中可以看出,PendSV中断的产生是通过代码:

portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT
实现的,它向中断状态寄存器bit28位写入1,将PendSV中断设置为挂起状态;

等到优先级高于PendSV的中断执行完成后,PendSV中断服务程序将被执行,进行任务切换工作。

Cortex-M3架构下,PendSV中断服务程序源码如下所示,这篇文章重点分析这段代码。

__asm void xPortPendSVHandler( void )
{
 extern uxCriticalNesting;
 extern pxCurrentTCB;            /* 指向当前激活的任务 */
 extern vTaskSwitchContext;      
 
 PRESERVE8
 
 mrs r0, psp                   /* PSP内容存入R0 */    
 isb                           /* 指令同步隔离,清流水线 */
 
 ldr r3, =pxCurrentTCB     /* 当前激活的任务TCB指针存入R2 */
 ldr r2, [r3]
 
 stmdb r0!, {r4-r11}          /* 保存剩余的寄存器,异常处理程序执行前,硬件自动将xPSR、PC、LR、R12、R0-R3入栈 */
 str r0, [r2]       /* 将新的栈顶保存到任务TCB的第一个成员中 */
 
 stmdb sp!, {r3, r14}         /* 将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext,调用函数时,返回地址自动保存到R14中,所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护; R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护*/
 mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY   /* 进入临界区 */
 msr basepri, r0
 dsb                         /* 数据和指令同步隔离 */
 isb
 bl vTaskSwitchContext        /* 调用函数,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换 */
 mov r0, #0                   /* 退出临界区*/
 msr basepri, r0
 ldmia sp!, {r3, r14}         /* 恢复R3和R14*/
 
 ldr r1, [r3]
 ldr r0, [r1]       /* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/
 ldmia r0!, {r4-r11}      /* 出栈*/
 msr psp, r0
 isb
 bx r14                      /* 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回,然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。*/
 nop
}
为了便于理解上面的代码,我们先用流程图的方式将整个过程画出来,然后再逐句分析代码。因为图形可以简化程序,并且信息更容易接受。

图1-1:任务切换流程
先强调图1-1中的几个术语,首先是“主堆栈指针MSP”和“进程堆栈指针PSP”。

对于Cortex-M3硬件,当系统复位后,默认使用MSP指针。

MSP指针用于操作系统内核以及处理异常(也就是说中断服务程序中默认强制使用MSP指针,这是硬件自动设置的)。

任务(进程)使用PSP指针,操作系统负责从MSP指针切换到PSP指针。

这个过程在《FreeRTOS高级篇3---启动调度器》一文的最后部分中「进行了讲解」

在SVC中断服务程序中启动第一个任务,当从SVC中断服务退出前,通过向r14寄存器最后4位按位或上0x0D,使得硬件在退出时使用进程堆栈指针PSP完成出栈操作并返回后进入线程模式、返回Thumb状态。

其次,“堆栈”和“任务堆栈”也值得强调一下。

每个任务都有自己的“任务堆栈”,在任务创建时会创建指定大小的任务堆栈,这是任务能够独立运行的前提条件之一。

在任务中定义的局部变量,会优先使用寄存器,寄存器不够时就使用任务堆栈的空间。

如果在任务中调用其它函数,则调用前的保存信息也存到任务堆栈中去。

根据任务代码来估算任务堆栈的大小是件十分重要的技能。

前面也说了,Cortex-M3硬件有两个堆栈指针,操作系统内核以及异常处理程序中使用MSP指针,所以它们也需要一个堆栈空间,我们称之为“堆栈”;

这个堆栈空间和任务堆栈空间在物理上是绝对不可以重叠的,图1-2展示了一个编译好的程序可能的RAM分配情况(堆栈向下生长)。

图1-2:RAM中的变量和堆栈分布示意图
有了上面的基础,接下来我们来分析PendSV中断服务程序。

mrs r0, psp 
是将任务堆栈指针PSP的值保存到寄存器R0中,因为接下来我们会将寄存器R4~R11也保存到任务堆栈中,但是我们没有哪个汇编指令能直接操作PSP完成入栈,所以只能借助R0。

ldr r3, =pxCurrentTCB      /* 当前激活的任务TCB指针存入R2 */
ldr r2, [r3]
这两句代码是获取当前激活的任务TCP指针,指针pxCurrentTCB前面文章已经提到过很多次了,它是位于tasks.c文件中定义的唯一一个全局指针型变量,指向当前激活的任务TCB。

stmdb r0!, {r4-r11}
这句代码用于将寄存器R4~R11保存到当前激活的程序任务堆栈中,并且同步更新寄存器R0的值。

str r0, [r2]
寄存器R2中保存当前激活的任务TCB指针,在《FreeRTOS高级篇2---FreeRTOS任务创建分析》中讲任务TCB数据结构时我们知道,任务TCB数据结构第一个成员一定是指向任务当前堆栈栈顶的指针变量pxTopOfStack

这句代码将R0的内容保存到任务TCB数据结构的第一个成员pxTopOfStack中,也就是将最新的任务堆栈指针保存到任务TCB的pxTopOfStack字段中。

当任务被激活时,就是从这个字段中获取任务堆栈指针,然后完成数据出栈操作的。

stmdb sp!, {r3, r14}
将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext。调用函数时,返回地址自动保存到R14中,所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护。

R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护。

mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY   
msr basepri, r0
这两句代码用来进入临界区,中断优先级号大于等于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断都会被屏蔽。

bl vTaskSwitchContext
调用函数,选择下一个要执行的任务,也就是寻找处于就绪态的最高优先级任务。

变量pxCurrentTCB指向找到的任务TCB。这个函数是核心中的核心,所有的其它代码都是为了保证这个函数能正确运行。

某些运行FreeRTOS的硬件有两种方法:「通用方法和特定于硬件的方法」(以下简称“特殊方法”)。

  1. 对于通用方法:
  • configUSE_PORT_OPTIMISED_TASK_SELECTION设置为0或者硬件不支持这种特殊方法。
  • 可以用于所有FreeRTOS支持的硬件。
  • 完全用C实现,效率略低于特殊方法。
  • 不强制要求限制最大可用优先级数目
  1. 对于特殊方法:
  • 并非所有硬件都支持。
  • 必须将configUSE_PORT_OPTIMISED_TASK_SELECTION设置为1。
  • 依赖一个或多个特定架构的汇编指令(一般是类似计算前导零[CLZ]指令)。
  • 比通用方法更高效。
  • 一般强制限定最大可用优先级数目为32(0~31)。
Cortex-M3即支持通用方法也支持特殊方法,默认的移植层使用特殊方法。我们先来看一下通用方法如何找到下一个要执行的任务。

在函数vTaskSwitchContext中使用宏taskSELECT_HIGHEST_PRIORITY_TASK()完成任务寻址工作,使用通用方法时,这个宏的代码如下所示。

pxReadyTasksLists是定义在tasks.c中的静态列表数组,表示就绪任务列表数组。

在《FreeRTOS高级篇2---FreeRTOS任务创建分析》中讲过这个变量:新创建任务的过程中,任务TCB中的状态列表项xStateListItem会挂接到就绪任务列表数组中。

uxTopReadyPriority也是定义在tasks.c中的静态变量,在此之前,它已经代表处于就绪态任务的最高优先级值;

在FreeRTOS任务创建与分析一文中,我们也讲到了这个变量:每次任务创建,都会判断新任务的优先级是否大于这个变量,如果大于,还会更新这个变量的值。

while()循环从优先级uxTopReadyPriority开始,从就绪列表数组pxReadyTasksLists中找出优先级最高的任务,然后调用宏listGET_OWNER_OF_NEXT_ENTRY获取最高优先级列表中的下一个列表项,并从该列表项中获取任务TCB指针赋给变量pxCurrentTCB

#define taskSELECT_HIGHEST_PRIORITY_TASK()        \
{                 \
  /* 从就绪列表数组中找出最高优先级列表*/    \
  while( listLIST_IS_EMPTY( 
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

上海2023年9月13日 /美通社/ -- 9月5日-6日,第九届CDIE数字化创新博览会(以下简称为"第九届CDIE"),于上海张江科学会堂盛大举行。在当晚的"第三届CDI华鹰数字指数颁奖...

关键字: 数字化 创新奖 CD AI

北京2023年9月4日 /美通社/ -- 2023年中国国际服务贸易交易会(简称服贸会)将于2023年9月2日—6日在北京国家会议中心及首钢园区举办。首次参展服贸会的华夏英泰(北京)生物技术有限公司(简称"华夏...

关键字: ST 管线 TC CD

上海2023年3月6日 /美通社/ -- 致力于与研究人员合作推动科学进步并加速实现突破的全球生命科学公司abcam(NASDAQ:ABCM)近日宣布授权迈杰医学,双方将就Claudin18.2伴随诊断的开发、注册和商业...

关键字: 开发平台 ADC NI CD

该运营商中立都市园区(Carrier-neutral Metro Campus)通过PlatformDIGITALTM为充满活力的互联数据社区提供高达70MW的IT容量和全球...

关键字: 数据中心 CD DIGITAL BSP

美国罗克维尔和中国苏州2023年1月18日 /美通社/ -- 信达生物制药集团(香港联交所股票代码:01801),一家致力于研发、生产和销售用于治疗肿瘤、自免、代谢、眼科等重大疾病领域创新药物的生物制药公司,宣布:公司五...

关键字: PD CD 代码 NI

上海2023年1月18日 /美通社/ -- 武田中国今日宣布,今年新增三款创新药物列入《国家基本医疗保险、工伤保险和生育保险药品目录(2022年)》,包括罕见病全球创新药拉那利尤单抗注射液、肿瘤靶向药物布格替尼...

关键字: BSP NI CD CAN

一线、复发/难治适应症同时获批,将改写20年弥漫大B细胞淋巴瘤治疗标准 上海2023年1月13日 /美通社/ -- 2023年1月13日,罗氏制药中国宣布,旗下全球首个靶向CD79b的抗体药物偶联物(ADC)优罗华&r...

关键字: BSP CD ADC SE

美国罗克维尔和中国苏州2023年1月5日 /美通社/ -- 信达生物制药集团(香港联交所股票代码:01801),一家致力于研发、生产和销售肿瘤、自免、代谢、眼科等重大疾病领域创新药物的生物制药公司,宣布IBI351(GF...

关键字: 控制 CD DC IO

舍弗勒首次入选CDP气候变化和水安全两个领域的“A级”名单,在所有参评企业中位居前1%行列 荣登CDP “A级”名单是对舍弗勒环境透明度及可持续发展战略的高度认可 德国赫尔佐根奥拉赫2022年12月23日...

关键字: CD BSP 可持续发展 COM

净零目标与策略通过科学基础目标倡议组织(SBTi)认可 上海2022年12月19日 /美通社/ -- CDP(全球环境信息研究中心)公布 2022 年气候变化报告,台达在"气候变化"(Climate...

关键字: 安全管理 CD NET BSP
关闭
关闭