你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

调试ucos的时候怎么看这个表的变化?或者说看重点看哪几...

[复制链接]
wwwheihei 提问时间:2018-8-21 15:32 /
调试ucos的时候怎么看这个表的变化?或者说看重点看哪几个寄存器的值变化?高手们指点一二,说其中一两个点也可以,走过路过看看说两句


1; PendSVHandler 异常为什么要将 psp 的值加载到 R0
    MRS R0, PSP
pp.png
收藏 评论4 发布时间:2018-8-21 15:32

举报

4个回答
feixiang20 回答时间:2018-8-22 01:19:04
比如如下
MRS R0, PSP
保存PSP到R0。为什么是PSP而不是MSP。因为在OS启动的时候,我们已经把SP设置为PSP了。这样使得用户程序使用任务堆栈,OS使用主堆栈,不会互相干扰。不会因为用户程序导致OS崩溃。
1.png

评分

参与人数 1蝴蝶豆 +3 收起 理由
zero99 + 3

查看全部评分

wwwheihei 回答时间:2018-8-22 08:03:03
本帖最后由 wwwheihei 于 2018-8-28 08:21 编辑
feixiang20 发表于 2018-8-22 01:19
比如如下
MRS R0, PSP
保存PSP到R0。为什么是PSP而不是MSP。因为在OS启动的时候,我们已经把SP设置为PSP了 ...

明白了点,也是就说os启动以后都用psp作为堆栈指针?msp只是创建任务时候用一下

主栈指针MSP
进程栈指针PSP



hanyc4st 回答时间:2018-8-22 18:25:13

评分

参与人数 1ST金币 -1 收起 理由
zero99 -1 求助帖不要灌水,警告一次

查看全部评分

wwwheihei 回答时间:2018-8-29 15:58:01
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实现,效率略低于特殊方法。
  • 不强制要求限制最大可用优先级数目
      2.对于特殊方法:
  • 并非所有硬件都支持。
  • 必须将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( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ) )                \



  •                 {                                                                                                                                \



  •                         configASSERT( uxTopReadyPriority );                                                                \



  •                         --uxTopReadyPriority;                                                                                        \



  •                 }                                                                                                                                \



  •                                                                                                                                                 \



  •                 /* 相同优先级的任务使用时间片共享处理器就是通过这个宏实现*/          \



  •                 listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &( pxReadyTasksLists[ uxTopReadyPriority ] ) );   \



  •         } /* taskSELECT_HIGHEST_PRIORITY_TASK */


      对于Cortex-M3硬件,还支持特殊方法选择下一个要执行的任务,那就是利用硬件提供的计算前导零指令CLZ。特殊方法时,宏taskSELECT_HIGHEST_PRIORITY_TASK()的代码如下所示。


  •         #define taskSELECT_HIGHEST_PRIORITY_TASK()                                                                \



  •         {                                                                                                                                        \



  •         UBaseType_t uxTopPriority;                                                                                                \



  •                                                                                                                                                 \



  •                 /* 从就绪列表数组中找出最高优先级列表*/                                          \



  •                 portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );                        \



  •                 listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \



  •         } /* taskSELECT_HIGHEST_PRIORITY_TASK() */


      与通用方法相比,可以发现从就绪列表数组中找出最高优先级列表代码不同了,特殊方法使用宏portGET_HIGHEST_PRIORITY来实现,将宏定义替换后,代码为:
uxTopPriority = ( 31UL - ( uint32_t ) __clz( (uxTopReadyPriority) ) )      在此之前,静态变量uxTopReadyPriority同样已经包含处于就绪态任务的最高优先级的信息。与通用方法中使用任务优先级数值不同,在特殊方法中,uxTopReadyPriority使用每一位来表示任务,比如变量uxTopReadyPriority的bit0为1,则表示存在优先级为0的就绪任务,bit10为1则表示存在优先级为10的就绪任务。由于32位整形数最多只有32位,因此使用这种特殊方法限定最大可用优先级数目为32,即优先级0~31。
      我们这来看看__clz( (uxTopReadyPriority)是什么意思,__clz()会被汇编指令CLZ替换掉,这个指令用来计算一个变量从最高位开始的连续零的个数。举个例子,假如变量uxTopReadyPriority为0x09(二进制为:0000 0000 0000 0000 0000 0000 0000 1001),即bit3和bit0为1,表示存在优先级为0和3的就绪任务。则__clz( (uxTopReadyPriority)的值为28,uxTopPriority =31-28=3,即优先级为3的任务是就绪态最高优先级任务。下面的代码跟通用方法一样,调用宏listGET_OWNER_OF_NEXT_ENTRY获取最高优先级列表中的下一个列表项,并从该列表项中获取任务TCB指针赋给变量pxCurrentTCB。


  • mov r0, #0                   /* 退出临界区*/



  • msr basepri, r0


      这两句代码用来退出临界区,通过向寄存器BASEPRI写入数值0来实现。
ldmia sp!, {r3, r14}      这句代码将寄存器R3和R14从堆栈中恢复,现在R3保存变量pxCurrentTCB的地址,需要注意的是,变量pxCurrentTCB在函数vTaskSwitchContext中可能已被修改,指向新的最高优先级就绪任务;R14保存退出异常需要的信息。


  • ldr r1, [r3]



  • ldr r0, [r1]       


      这两句代码获取变量pxCurrentTCB指向的任务TCB指针,并将TCB的第一个成员——当前堆栈栈顶的指针变量pxTopOfStack的值保存到寄存器R0中,也就是将即将运行的任务堆栈栈顶值存入R0。
ldmia r0!, {r4-r11}      将寄存器R4~R11出栈,并同时更新R0的值。
msr psp, r0

      将最新的任务堆栈栈顶赋值给线程堆栈指针PSP。

bx r14
      从异常中断服务程序退出。异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、使用PSP堆栈指针还是MSP堆栈指针。当调用 bx r14指令后,硬件会知道要从异常返回,然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。
      至此,任务切换完成。

所属标签

相似问题

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版